Compare commits

..

48 Commits

Author SHA1 Message Date
Kirill Chibisov
04d8a284a0 Winit version 0.30.4 2024-07-16 21:04:25 +03:00
daxpedda
949cb0f203 Web: fix MouseMotion coordinate space (#3770) 2024-07-16 21:04:25 +03:00
daxpedda
bf68ac0b14 Web: fix WindowEvent::Resized not using rAF (#3790) 2024-07-16 21:04:25 +03:00
Kirill Chibisov
ba188376d1 wayland: bump dependencies
Update SCTK as a follow-up to 1170554dbd (ignore events to dead
objects) fixing it for wl_output.
2024-07-16 21:04:25 +03:00
daxpedda
6509f8a18b Make DeviceId/WindowId::dummy() safe (#3784) 2024-07-16 21:04:25 +03:00
daxpedda
71dea4637d Fix CI (#3775) 2024-07-16 21:04:25 +03:00
Kirill Chibisov
ec24c3efd3 wayland: ignore events for dead objects
Nothing wrong will happen if we ignore events when compositor is at
wrong, at least crashing because compositor is just _wrong_ probably is
not a great option.

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

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

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

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

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

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

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

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

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

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

* Only queue events if necessary

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

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

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

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

View File

@@ -2,8 +2,5 @@
# #
# 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.
[build] [build]
rustdocflags = ["--cfg=unreleased_changelogs"]
rustflags = ["--cfg=unreleased_changelogs"] rustflags = ["--cfg=unreleased_changelogs"]
rustdocflags = ["--cfg=unreleased_changelogs"]
[target.wasm32-unknown-unknown]
runner = "wasm-bindgen-test-runner"

View File

@@ -2,7 +2,3 @@
# chore(rustfmt): use nightly # chore(rustfmt): use nightly
7b0c7b6cb2c62767ca0c73c857b299883f55a883 7b0c7b6cb2c62767ca0c73c857b299883f55a883
# Rustfmt: use `group_imports`
2665c120981af548433645c6383b3580dd8f8fc4
# Use Taplo for TOML formatting
3398ebe467c43ccfd91916c5b81ff3c68f598556

9
.github/CODEOWNERS vendored
View File

@@ -2,10 +2,9 @@
/src/platform/android.rs @MarijnS95 /src/platform/android.rs @MarijnS95
/src/platform_impl/android @MarijnS95 /src/platform_impl/android @MarijnS95
# Apple (AppKit + UIKit) # iOS
/src/platform/ios.rs @madsmtm /src/platform/ios.rs @madsmtm
/src/platform/macos.rs @madsmtm /src/platform_impl/ios @madsmtm
/src/platform_impl/apple @madsmtm
# Unix # Unix
/src/platform_impl/linux/mod.rs @kchibisov /src/platform_impl/linux/mod.rs @kchibisov
@@ -18,6 +17,10 @@
/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

View File

@@ -1,5 +1,5 @@
name: Web bug name: Web bug
description: Create a Web-specific bug report description: Create a web-specific bug report
labels: labels:
- B - bug - B - bug
- DS - web - DS - web

View File

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

View File

@@ -1,22 +0,0 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
groups:
github-actions:
patterns:
- "*"
- package-ecosystem: npm
directory: src/platform_impl/web/script
schedule:
interval: daily
groups:
github-actions:
patterns:
- '*'
labels:
- "DS - web"

View File

@@ -17,19 +17,6 @@ jobs:
- name: Check Formatting - name: Check Formatting
run: cargo fmt -- --check run: cargo fmt -- --check
taplo:
name: Taplo
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: taiki-e/checkout-action@v1
- name: Install Taplo
uses: taiki-e/install-action@v2
with:
tool: taplo-cli
- name: Run Taplo
run: taplo fmt --check
typos: typos:
name: Check for typos name: Check for typos
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -55,7 +42,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
toolchain: [stable, nightly, '1.73'] toolchain: [stable, nightly, '1.70.0']
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, }
@@ -68,35 +55,25 @@ jobs:
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' } - { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } - { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, } - { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
- { name: 'macOS x86_64', target: x86_64-apple-darwin, os: macos-latest, } - { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'macOS Aarch64', target: aarch64-apple-darwin, os: macos-latest, }
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, } - { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, } - { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
- { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, } - { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude: exclude:
# Web on nightly needs extra arguments
- toolchain: nightly
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest }
# Android is tested on stable-3 # Android is tested on stable-3
- toolchain: '1.73' - toolchain: '1.70.0'
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 --' }
# Redox OS doesn't follow MSRV
- toolchain: '1.73'
platform: { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest }
include: include:
- toolchain: '1.73' - toolchain: '1.70.0'
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'
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, test-options: -Zdoctest-xcompile }
- toolchain: 'nightly' - toolchain: 'nightly'
platform: { platform: {
name: 'Web Atomic', name: 'web Atomic',
target: wasm32-unknown-unknown, target: wasm32-unknown-unknown,
os: ubuntu-latest, os: ubuntu-latest,
options: '-Zbuild-std=panic_abort,std', options: '-Zbuild-std=panic_abort,std',
test-options: -Zdoctest-xcompile, rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
rustflags: '-Ctarget-feature=+atomics,+bulk-memory', components: rust-src,
components: rust-src,
} }
env: env:
@@ -106,10 +83,8 @@ jobs:
# Faster compilation and error on warnings # Faster compilation and error on warnings
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}' RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
RUSTDOCFLAGS: ${{ matrix.platform.rustflags }}
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }} OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
TEST_OPTIONS: ${{ matrix.platform.test-options }}
CMD: ${{ matrix.platform.cmd }} CMD: ${{ matrix.platform.cmd }}
steps: steps:
@@ -120,7 +95,7 @@ jobs:
# the cache has been downloaded. # the cache has been downloaded.
# #
# This could be avoided if we added Cargo.lock to the repository. # This could be avoided if we added Cargo.lock to the repository.
uses: actions/cache/restore@v4 uses: actions/cache/restore@v3
with: with:
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci # https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
path: | path: |
@@ -141,7 +116,7 @@ jobs:
- name: Cache cargo-apk - name: Cache cargo-apk
if: contains(matrix.platform.target, 'android') if: contains(matrix.platform.target, 'android')
id: cargo-apk-cache id: cargo-apk-cache
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: ~/.cargo/bin/cargo-apk path: ~/.cargo/bin/cargo-apk
# Change this key if we update the required cargo-apk version # Change this key if we update the required cargo-apk version
@@ -156,11 +131,6 @@ jobs:
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true') if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
run: cargo install cargo-apk --version=^0.9.7 --locked run: cargo install cargo-apk --version=^0.9.7 --locked
- uses: taiki-e/cache-cargo-install-action@v2
if: contains(matrix.platform.target, 'wasm32') && matrix.toolchain == 'nightly'
with:
tool: wasm-bindgen-cli
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }} toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
@@ -179,52 +149,52 @@ 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.73' matrix.toolchain != '1.70.0'
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.73' matrix.toolchain != '1.70.0'
run: cargo $CMD test --no-run $OPTIONS run: cargo $CMD test --no-run $OPTIONS
- name: Run tests - name: Run tests
if: > if: >
!contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73' matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS run: cargo $CMD test $OPTIONS
- name: Lint with clippy - name: Lint with clippy
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features') if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
run: cargo clippy --all-targets $OPTIONS $TEST_OPTIONS -- -Dwarnings run: cargo clippy --all-targets $OPTIONS -- -Dwarnings
- 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.73' matrix.toolchain != '1.70.0'
run: cargo $CMD test --no-run $OPTIONS $TEST_OPTIONS --features serde run: cargo $CMD test --no-run $OPTIONS --features serde
- name: Run tests with serde enabled - name: Run tests with serde enabled
if: > if: >
!contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73' matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS $TEST_OPTIONS --features serde run: cargo $CMD test $OPTIONS --features serde
- name: Check docs.rs documentation - name: Check docs.rs documentation
if: matrix.toolchain == 'nightly' if: matrix.toolchain == 'nightly'
run: cargo doc --no-deps $OPTIONS --features=serde,mint,android-native-activity run: cargo doc --no-deps $OPTIONS --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
env: env:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs --cfg=unreleased_changelogs' RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs --cfg=unreleased_changelogs'
# See restore step above # See restore step above
- name: Save cache of cargo folder - name: Save cache of cargo folder
uses: actions/cache/save@v4 uses: actions/cache/save@v3
with: with:
path: | path: |
~/.cargo/registry/index/ ~/.cargo/registry/index/
@@ -244,42 +214,22 @@ jobs:
- { name: 'Android', target: aarch64-linux-android } - { name: 'Android', target: aarch64-linux-android }
- { name: 'iOS', target: aarch64-apple-ios } - { name: 'iOS', target: aarch64-apple-ios }
- { name: 'Linux', target: x86_64-unknown-linux-gnu } - { name: 'Linux', target: x86_64-unknown-linux-gnu }
- { name: 'macOS', target: aarch64-apple-darwin } - { name: 'macOS', target: x86_64-apple-darwin }
- { name: 'Redox OS', target: x86_64-unknown-redox } - { name: 'Redox OS', target: x86_64-unknown-redox }
- { name: 'Web', target: wasm32-unknown-unknown } - { name: 'web', target: wasm32-unknown-unknown }
- { name: 'Windows GNU', target: x86_64-pc-windows-gnu } - { name: 'Windows', target: x86_64-pc-windows-gnu }
- { name: 'Windows MSVC', target: x86_64-pc-windows-msvc }
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: taiki-e/checkout-action@v1
- uses: EmbarkStudios/cargo-deny-action@v2 - 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 }}
eslint:
name: ESLint
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./src/platform_impl/web/script
steps:
- uses: taiki-e/checkout-action@v1
- name: Setup NPM
run: npm install
- name: Run ESLint
run: npx eslint
swc: swc:
name: Minimize JavaScript name: Minimize JavaScript
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults:
run:
working-directory: ./src/platform_impl/web/script
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: taiki-e/checkout-action@v1
@@ -287,7 +237,7 @@ jobs:
run: sudo npm i -g @swc/cli run: sudo npm i -g @swc/cli
- name: Run SWC - name: Run SWC
run: | run: |
swc . --ignore node_modules,**/*.d.ts --only **/*.ts -d . --out-file-extension min.js swc src/platform_impl/web/web_sys/worker.js -o src/platform_impl/web/web_sys/worker.min.js
- name: Check for diff - name: Check for diff
run: | run: |
[[ -z $(git status -s) ]] [[ -z $(git status -s) ]]

View File

@@ -29,10 +29,10 @@ jobs:
env: env:
RUSTDOCFLAGS: --crate-version master --cfg=docsrs --cfg=unreleased_changelogs RUSTDOCFLAGS: --crate-version master --cfg=docsrs --cfg=unreleased_changelogs
run: | run: |
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=serde,mint,android-native-activity cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@v5 uses: actions/configure-pages@v4
- name: Fix permissions - name: Fix permissions
run: | run: |

3
.gitignore vendored
View File

@@ -5,6 +5,3 @@ rls/
*~ *~
#*# #*#
.DS_Store .DS_Store
# NPM package used to run ESLint.
/src/platform_impl/web/script/node_modules
/src/platform_impl/web/script/package-lock.json

View File

@@ -1,13 +1,6 @@
{ {
"module": {
"type": "es6"
},
"isModule": true,
"minify": true, "minify": true,
"jsc": { "jsc": {
"parser": {
"syntax": "typescript"
},
"target": "es2022", "target": "es2022",
"minify": { "minify": {
"compress": { "compress": {

View File

@@ -1,62 +1,54 @@
[package] [package]
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
categories = ["gui"]
description = "Cross-platform window creation library."
documentation = "https://docs.rs/winit"
edition.workspace = true
include = [
"/build.rs",
"/docs",
"/examples",
"/FEATURES.md",
"/LICENSE",
"/src",
"!/src/platform_impl/web/script",
"/src/platform_impl/web/script/**/*.min.js",
"/tests",
]
keywords = ["windowing"]
license.workspace = true
name = "winit" name = "winit"
version = "0.30.4"
authors = [
"The winit contributors",
"Pierre Krieger <pierre.krieger1708@gmail.com>",
]
description = "Cross-platform window creation library."
keywords = ["windowing"]
readme = "README.md" readme = "README.md"
repository.workspace = true documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version.workspace = true rust-version.workspace = true
version = "0.30.5" repository.workspace = true
license.workspace = true
edition.workspace = true
exclude = ["/.cargo"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = [ features = [
"rwh_04",
"rwh_05",
"rwh_06",
"serde", "serde",
"mint", "mint",
# Enabled to get docs to compile # Enabled to get docs to compile
"android-native-activity", "android-native-activity",
] ]
# These are all tested in CI # These are all tested in CI
rustdoc-args = ["--cfg", "docsrs"]
targets = [ targets = [
# Windows # Windows
"i686-pc-windows-msvc", "i686-pc-windows-msvc",
"x86_64-pc-windows-msvc", "x86_64-pc-windows-msvc",
# macOS # macOS
"aarch64-apple-darwin",
"x86_64-apple-darwin", "x86_64-apple-darwin",
# Unix (X11 & Wayland) # Unix (X11 & Wayland)
"i686-unknown-linux-gnu", "i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu", "x86_64-unknown-linux-gnu",
# iOS # iOS
"aarch64-apple-ios", "x86_64-apple-ios",
# Android # Android
"aarch64-linux-android", "aarch64-linux-android",
# Web # Web
"wasm32-unknown-unknown", "wasm32-unknown-unknown",
] ]
rustdoc-args = ["--cfg", "docsrs"]
# Features are documented in either `lib.rs` or under `winit::platform`. # Features are documented in either `lib.rs` or under `winit::platform`.
[features] [features]
android-game-activity = ["android-activity/game-activity"] default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
android-native-activity = ["android-activity/native-activity"] x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
mint = ["dpi/mint"]
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde", "bitflags/serde"]
wayland = [ wayland = [
"wayland-client", "wayland-client",
"wayland-backend", "wayland-backend",
@@ -66,11 +58,17 @@ wayland = [
"ahash", "ahash",
"memmap2", "memmap2",
] ]
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"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"] wayland-csd-adwaita-notitle = ["sctk-adwaita"]
wayland-dlopen = ["wayland-backend/dlopen"] android-native-activity = ["android-activity/native-activity"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"] android-game-activity = ["android-activity/game-activity"]
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde"]
mint = ["dpi/mint"]
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
[build-dependencies] [build-dependencies]
cfg_aliases = "0.2.1" cfg_aliases = "0.2.1"
@@ -79,39 +77,68 @@ cfg_aliases = "0.2.1"
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_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] } rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = [
"std",
], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = [
"std",
], optional = true }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
smol_str = "0.3" 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.25.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"] }
[target.'cfg(not(target_os = "android"))'.dev-dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
softbuffer = { version = "0.4.6", default-features = false, features = [ softbuffer = { version = "0.4.0", default-features = false, features = [
"x11", "x11",
"x11-dlopen", "x11-dlopen",
"wayland", "wayland",
"wayland-dlopen", "wayland-dlopen",
] } ] }
# Android
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6.0" android-activity = "0.6.0"
ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false } ndk = { version = "0.9.0", default-features = false }
# AppKit or UIKit [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
[target.'cfg(target_vendor = "apple")'.dependencies]
block2 = "0.5.1"
core-foundation = "0.9.3" core-foundation = "0.9.3"
objc2 = "0.5.2" 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"
objc2-app-kit = { version = "0.2.2", features = [ block2 = "0.5.1"
[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
"block2",
"dispatch",
"NSArray",
"NSAttributedString",
"NSData",
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSPathUtilities",
"NSProcessInfo",
"NSRunLoop",
"NSString",
"NSThread",
"NSValue",
]
[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit]
version = "0.2.2"
features = [
"NSAppearance", "NSAppearance",
"NSApplication", "NSApplication",
"NSBitmapImageRep", "NSBitmapImageRep",
@@ -134,57 +161,34 @@ objc2-app-kit = { version = "0.2.2", features = [
"NSScreen", "NSScreen",
"NSTextInputClient", "NSTextInputClient",
"NSTextInputContext", "NSTextInputContext",
"NSToolbar",
"NSView", "NSView",
"NSWindow", "NSWindow",
"NSWindowScripting", "NSWindowScripting",
"NSWindowTabGroup", "NSWindowTabGroup",
] } ]
objc2-foundation = { version = "0.2.2", features = [
"block2",
"dispatch",
"NSArray",
"NSAttributedString",
"NSData",
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSGeometry",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSOperation",
"NSPathUtilities",
"NSProcessInfo",
"NSRunLoop",
"NSString",
"NSThread",
"NSValue",
] }
# UIKit [target.'cfg(target_os = "ios")'.dependencies.objc2-foundation]
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies] version = "0.2.2"
objc2-foundation = { version = "0.2.2", features = [ features = [
"block2",
"dispatch", "dispatch",
"NSArray", "NSArray",
"NSEnumerator", "NSEnumerator",
"NSGeometry", "NSGeometry",
"NSObjCRuntime", "NSObjCRuntime",
"NSOperation",
"NSString", "NSString",
"NSProcessInfo", "NSProcessInfo",
"NSThread", "NSThread",
"NSSet", "NSSet",
] } ]
objc2-ui-kit = { version = "0.2.2", features = [
[target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit]
version = "0.2.2"
features = [
"UIApplication", "UIApplication",
"UIDevice", "UIDevice",
"UIEvent", "UIEvent",
"UIGeometry", "UIGeometry",
"UIGestureRecognizer", "UIGestureRecognizer",
"UITextInput",
"UITextInputTraits",
"UIOrientation", "UIOrientation",
"UIPanGestureRecognizer", "UIPanGestureRecognizer",
"UIPinchGestureRecognizer", "UIPinchGestureRecognizer",
@@ -198,12 +202,14 @@ objc2-ui-kit = { version = "0.2.2", features = [
"UIView", "UIView",
"UIViewController", "UIViewController",
"UIWindow", "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.52.0"
features = [
"Win32_Devices_HumanInterfaceDevice", "Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation", "Win32_Foundation",
"Win32_Globalization", "Win32_Globalization",
@@ -214,7 +220,6 @@ windows-sys = { version = "0.52.0", features = [
"Win32_System_Com", "Win32_System_Com",
"Win32_System_LibraryLoader", "Win32_System_LibraryLoader",
"Win32_System_Ole", "Win32_System_Ole",
"Win32_Security",
"Win32_System_SystemInformation", "Win32_System_SystemInformation",
"Win32_System_SystemServices", "Win32_System_SystemServices",
"Win32_System_Threading", "Win32_System_Threading",
@@ -229,10 +234,9 @@ windows-sys = { version = "0.52.0", features = [
"Win32_UI_Shell", "Win32_UI_Shell",
"Win32_UI_TextServices", "Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging", "Win32_UI_WindowsAndMessaging",
] } ]
# Linux [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
[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.13.0" calloop = "0.13.0"
@@ -253,85 +257,81 @@ wayland-backend = { version = "0.3.5", default-features = false, features = [
"client_system", "client_system",
], optional = true } ], optional = true }
wayland-client = { version = "0.31.4", optional = true } wayland-client = { version = "0.31.4", optional = true }
wayland-protocols = { version = "0.32.2", features = ["staging"], optional = true } wayland-protocols = { version = "0.32.2", features = [
wayland-protocols-plasma = { version = "0.3.2", features = ["client"], optional = true } "staging",
], optional = true }
wayland-protocols-plasma = { version = "0.3.2", 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 = [ x11rb = { version = "0.13.0", default-features = false, features = [
"allow-unsafe-code", "allow-unsafe-code",
"cursor",
"dl-libxcb", "dl-libxcb",
"randr", "randr",
"resource_manager", "resource_manager",
"sync",
"xinput", "xinput",
"xkb", "xkb",
], optional = true } ], 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.5.7" redox_syscall = "0.4.1"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.64"
features = [
'AbortController',
'AbortSignal',
'Blob',
'BlobPropertyBag',
'console',
'CssStyleDeclaration',
'Document',
'DomException',
'DomRect',
'DomRectReadOnly',
'Element',
'Event',
'EventTarget',
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'HtmlImageElement',
'ImageBitmap',
'ImageBitmapOptions',
'ImageBitmapRenderingContext',
'ImageData',
'IntersectionObserver',
'IntersectionObserverEntry',
'KeyboardEvent',
'MediaQueryList',
'MessageChannel',
'MessagePort',
'Navigator',
'Node',
'PageTransitionEvent',
'PointerEvent',
'PremultiplyAlpha',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'VisibilityState',
'Window',
'WheelEvent',
'Worker',
'Url',
]
# Web
[target.'cfg(target_family = "wasm")'.dependencies] [target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.70" js-sys = "0.3.64"
pin-project = "1" pin-project = "1"
wasm-bindgen = "0.2.93" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4.43" wasm-bindgen-futures = "0.4"
web-time = "1" web-time = "1"
web_sys = { package = "web-sys", version = "0.3.70", features = [
"AbortController",
"AbortSignal",
"Blob",
"BlobPropertyBag",
"console",
"CssStyleDeclaration",
"Document",
"DomException",
"DomRect",
"DomRectReadOnly",
"Element",
"Event",
"EventTarget",
"FocusEvent",
"HtmlCanvasElement",
"HtmlElement",
"HtmlHtmlElement",
"HtmlImageElement",
"ImageBitmap",
"ImageBitmapOptions",
"ImageBitmapRenderingContext",
"ImageData",
"IntersectionObserver",
"IntersectionObserverEntry",
"KeyboardEvent",
"MediaQueryList",
"MessageChannel",
"MessagePort",
"Navigator",
"Node",
"OrientationLockType",
"OrientationType",
"PageTransitionEvent",
"Permissions",
"PermissionState",
"PermissionStatus",
"PointerEvent",
"PremultiplyAlpha",
"ResizeObserver",
"ResizeObserverBoxOptions",
"ResizeObserverEntry",
"ResizeObserverOptions",
"ResizeObserverSize",
"Screen",
"ScreenOrientation",
"Url",
"VisibilityState",
"WheelEvent",
"Window",
"Worker",
] }
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies] [target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
atomic-waker = "1" atomic-waker = "1"
@@ -340,25 +340,21 @@ concurrent-queue = { version = "2", default-features = false }
[target.'cfg(target_family = "wasm")'.dev-dependencies] [target.'cfg(target_family = "wasm")'.dev-dependencies]
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
tracing-web = "0.1" tracing-web = "0.1"
wasm-bindgen-test = "0.3"
[[example]] [[example]]
doc-scrape-examples = true doc-scrape-examples = true
name = "window" name = "window"
[[example]]
name = "child_window"
[workspace] [workspace]
members = ["dpi"]
resolver = "2" resolver = "2"
members = ["dpi"]
[workspace.package] [workspace.package]
edition = "2021" rust-version = "1.70.0"
license = "Apache-2.0"
repository = "https://github.com/rust-windowing/winit" repository = "https://github.com/rust-windowing/winit"
rust-version = "1.73" license = "Apache-2.0"
edition = "2021"
[workspace.dependencies] [workspace.dependencies]
mint = "0.5.6"
serde = { version = "1", features = ["serde_derive"] } serde = { version = "1", features = ["serde_derive"] }
mint = "0.5.6"

View File

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

View File

@@ -2,13 +2,13 @@
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit) [![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit) [![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![UNSTABLE docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=UNSTABLE%20docs [![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs
)](https://rust-windowing.github.io/winit/winit/index.html) )](https://rust-windowing.github.io/winit/winit/index.html)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions) [![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
```toml ```toml
[dependencies] [dependencies]
winit = "0.30.5" winit = "0.30.4"
``` ```
## [Documentation](https://docs.rs/winit) ## [Documentation](https://docs.rs/winit)
@@ -19,7 +19,7 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
## Contact Us ## Contact Us
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit).
The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings). The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
@@ -35,7 +35,7 @@ another library.
## MSRV Policy ## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.73**. Changes to This crate's Minimum Supported Rust Version (MSRV) is **1.70**. 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
@@ -50,15 +50,12 @@ Where `sid` is the current version of `rustc` provided by [Debian Sid], and
[Debian Sid]: https://packages.debian.org/sid/rustc [Debian Sid]: https://packages.debian.org/sid/rustc
An exception is made for the Android platform, where a higher Rust version The exception is for the Android platform, where a higher Rust version
must be used for certain Android features. In this case, the MSRV will be must be used for certain Android features. In this case, the MSRV will be
capped at the latest stable version of Rust minus three. This inconsistency is capped at the latest stable version of Rust minus three. This inconsistency is
not reflected in Cargo metadata, as it is not powerful enough to expose this not reflected in Cargo metadata, as it is not powerful enough to expose this
restriction. restriction.
Redox OS is also not covered by this MSRV policy, as it requires a Rust nightly
toolchain to compile.
All crates in the [`rust-windowing`] organizations have the All crates in the [`rust-windowing`] organizations have the
same MSRV policy. same MSRV policy.
@@ -66,4 +63,4 @@ same MSRV policy.
### Platform-specific usage ### Platform-specific usage
Check out the [`winit::platform`](https://docs.rs/winit/latest/winit/platform/index.html) module for platform-specific usage. Check out the [`winit::platform`](https://rust-windowing.github.io/winit/winit/platform/index.html) module for platform-specific usage.

View File

@@ -10,9 +10,10 @@ fn main() {
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: { all(target_vendor = "apple", not(target_os = "macos")) }, ios_platform: { target_os = "ios" },
windows_platform: { target_os = "windows" }, windows_platform: { target_os = "windows" },
free_unix: { all(unix, not(target_vendor = "apple"), not(android_platform), not(target_os = "emscripten")) }, apple: { any(target_os = "ios", target_os = "macos") },
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.

View File

@@ -1,16 +1,15 @@
disallowed-methods = [ disallowed-methods = [
{ 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 = "web_sys::window", reason = "is not available in every context" },
{ path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
{ 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::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" }, { path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" }, { path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" }, { path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" }, { path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" }, { path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::window", reason = "is not available in every context" }, { path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
] ]

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.9 KiB

View File

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

Before

Width:  |  Height:  |  Size: 4.3 KiB

View File

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

View File

@@ -1,40 +1,39 @@
[package] [package]
categories = ["gui"]
description = "Types for handling UI scaling"
edition.workspace = true
keywords = ["DPI", "HiDPI", "scale-factor"]
license.workspace = true
name = "dpi" name = "dpi"
repository.workspace = true
rust-version.workspace = true
version = "0.1.1" version = "0.1.1"
description = "Types for handling UI scaling"
keywords = ["DPI", "HiDPI", "scale-factor"]
categories = ["gui"]
rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
[features] [features]
mint = ["dep:mint"]
serde = ["dep:serde"] serde = ["dep:serde"]
mint = ["dep:mint"]
[dependencies] [dependencies]
mint = { workspace = true, optional = true }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
mint = { workspace = true, optional = true }
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["mint", "serde"] features = ["serde", "mint"]
# These are all tested in CI # These are all tested in CI
rustdoc-args = ["--cfg", "docsrs"]
targets = [ targets = [
# Windows # Windows
"i686-pc-windows-msvc", "i686-pc-windows-msvc",
"x86_64-pc-windows-msvc", "x86_64-pc-windows-msvc",
# macOS # macOS
"aarch64-apple-darwin",
"x86_64-apple-darwin", "x86_64-apple-darwin",
# Unix (X11 & Wayland) # Unix (X11 & Wayland)
"i686-unknown-linux-gnu", "i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu", "x86_64-unknown-linux-gnu",
# iOS # iOS
"aarch64-apple-ios", "x86_64-apple-ios",
# Android # Android
"aarch64-linux-android", "aarch64-linux-android",
# Web # Web
"wasm32-unknown-unknown", "wasm32-unknown-unknown",
] ]
rustdoc-args = ["--cfg", "docsrs"]

View File

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

View File

@@ -1,50 +1,55 @@
#[cfg(any(x11_platform, macos_platform, windows_platform))] #[cfg(all(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, KeyEvent, WindowEvent}; use winit::event::{ElementState, Event, 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, WindowAttributes, WindowId}; use winit::window::Window;
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[derive(Default)] fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window {
struct Application { let parent = parent.raw_window_handle().unwrap();
parent_window_id: Option<WindowId>, let mut window_attributes = Window::default_attributes()
windows: HashMap<WindowId, Box<dyn Window>>, .with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window.
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };
event_loop.create_window(window_attributes).unwrap()
} }
impl ApplicationHandler for Application { let mut windows = HashMap::new();
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let attributes = WindowAttributes::default()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id()); let event_loop: EventLoop<()> = EventLoop::new().unwrap();
self.parent_window_id = Some(window.id()); let mut parent_window_id = None;
self.windows.insert(window.id(), window); 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();
fn window_event( parent_window_id = Some(window.id());
&mut self,
event_loop: &dyn ActiveEventLoop, println!("Parent window id: {parent_window_id:?})");
window_id: winit::window::WindowId, windows.insert(window.id(), window);
event: WindowEvent, },
) { Event::WindowEvent { window_id, event } => match event {
match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
self.windows.clear(); windows.clear();
event_loop.exit(); event_loop.exit();
}, },
WindowEvent::PointerEntered { device_id: _, .. } => { WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window // On x11, println when the cursor entered in a window even if the child window
// is created by some key inputs. // is created by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the // the child windows are always placed at (0, 0) with size (200, 200) in the
@@ -56,43 +61,25 @@ fn main() -> Result<(), impl std::error::Error> {
event: KeyEvent { state: ElementState::Pressed, .. }, event: KeyEvent { state: ElementState::Pressed, .. },
.. ..
} => { } => {
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap(); let parent_window = windows.get(&parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window.as_ref(), event_loop); let child_window = spawn_child_window(parent_window, event_loop);
let child_id = child_window.id(); let child_id = child_window.id();
println!("Child window created with id: {child_id:?}"); println!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, child_window); windows.insert(child_id, child_window);
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get(&window_id) { if let Some(window) = windows.get(&window_id) {
fill::fill_window(window.as_ref()); fill::fill_window(window);
} }
}, },
_ => (), _ => (),
} },
_ => (),
} }
} })
fn spawn_child_window(
parent: &dyn Window,
event_loop: &dyn ActiveEventLoop,
) -> Box<dyn Window> {
let parent = parent.raw_window_handle().unwrap();
let mut window_attributes = WindowAttributes::default()
.with_title("child window")
.with_surface_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window.
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };
event_loop.create_window(window_attributes).unwrap()
}
let event_loop = EventLoop::new().unwrap();
event_loop.run_app(Application::default())
} }
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))] #[cfg(all(feature = "rwh_06", not(any(x11_platform, macos_platform, windows_platform))))]
fn main() { fn main() {
panic!( panic!(
"This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature \ "This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature \

View File

@@ -7,11 +7,12 @@ use std::time;
use ::tracing::{info, warn}; use ::tracing::{info, warn};
#[cfg(web_platform)] #[cfg(web_platform)]
use web_time as time; use web_time as time;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent}; use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::{Key, NamedKey}; use winit::keyboard::{Key, NamedKey};
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
@@ -43,7 +44,8 @@ fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
event_loop.run_app(ControlFlowDemo::default()) let mut app = ControlFlowDemo::default();
event_loop.run_app(&mut app)
} }
#[derive(Default)] #[derive(Default)]
@@ -52,11 +54,11 @@ struct ControlFlowDemo {
request_redraw: bool, request_redraw: bool,
wait_cancelled: bool, wait_cancelled: bool,
close_requested: bool, close_requested: bool,
window: Option<Box<dyn Window>>, window: Option<Window>,
} }
impl ApplicationHandler for ControlFlowDemo { impl ApplicationHandler for ControlFlowDemo {
fn new_events(&mut self, _event_loop: &dyn ActiveEventLoop, cause: StartCause) { fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
info!("new_events: {cause:?}"); info!("new_events: {cause:?}");
self.wait_cancelled = match cause { self.wait_cancelled = match cause {
@@ -65,8 +67,8 @@ impl ApplicationHandler for ControlFlowDemo {
} }
} }
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window_attributes = WindowAttributes::default().with_title( let window_attributes = Window::default_attributes().with_title(
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.", "Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
); );
self.window = Some(event_loop.create_window(window_attributes).unwrap()); self.window = Some(event_loop.create_window(window_attributes).unwrap());
@@ -74,7 +76,7 @@ impl ApplicationHandler for ControlFlowDemo {
fn window_event( fn window_event(
&mut self, &mut self,
_event_loop: &dyn ActiveEventLoop, _event_loop: &ActiveEventLoop,
_window_id: WindowId, _window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
@@ -114,13 +116,13 @@ impl ApplicationHandler for ControlFlowDemo {
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.as_ref()); fill::fill_window(window);
}, },
_ => (), _ => (),
} }
} }
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
if self.request_redraw && !self.wait_cancelled && !self.close_requested { if self.request_redraw && !self.wait_cancelled && !self.close_requested {
self.window.as_ref().unwrap().request_redraw(); self.window.as_ref().unwrap().request_redraw();
} }

View File

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

View File

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

View File

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

View File

@@ -3,40 +3,34 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::fmt::Debug; use std::fmt::Debug;
#[cfg(not(android_platform))] #[cfg(not(any(android_platform, ios_platform)))]
use std::num::NonZeroU32; use std::num::NonZeroU32;
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::Arc; use std::sync::Arc;
use std::{fmt, mem}; use std::{fmt, mem};
use ::tracing::{error, info}; use ::tracing::{error, info};
use cursor_icon::CursorIcon; use cursor_icon::CursorIcon;
#[cfg(not(android_platform))] #[cfg(not(any(android_platform, ios_platform)))]
use rwh_06::{DisplayHandle, HasDisplayHandle}; use rwh_06::{DisplayHandle, HasDisplayHandle};
#[cfg(not(android_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::error::RequestError;
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent}; use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState}; use winit::keyboard::{Key, ModifiersState};
#[cfg(macos_platform)] use winit::window::{
use winit::platform::macos::{ Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection,
ApplicationHandlerExtMacOS, OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS, Theme, Window, WindowId,
}; };
#[cfg(macos_platform)]
use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS};
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
use winit::platform::startup_notify::{ use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify, self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
}; };
#[cfg(web_platform)]
use winit::platform::web::{ActiveEventLoopExtWeb, CustomCursorExtWeb, WindowAttributesExtWeb};
#[cfg(x11_platform)]
use winit::platform::x11::WindowAttributesExtX11;
use winit::window::{
Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection,
Theme, Window, WindowAttributes, WindowId,
};
#[path = "util/tracing.rs"] #[path = "util/tracing.rs"]
mod tracing; mod tracing;
@@ -50,51 +44,50 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing::init(); tracing::init();
let event_loop = EventLoop::new()?; let event_loop = EventLoop::<UserEvent>::with_user_event().build()?;
let (sender, receiver) = mpsc::channel(); let _event_loop_proxy = event_loop.create_proxy();
// Wire the user event from another thread. // Wire the user event from another thread.
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
{ std::thread::spawn(move || {
let event_loop_proxy = event_loop.create_proxy(); // Wake up the `event_loop` once every second and dispatch a custom event
let sender = sender.clone(); // from a different thread.
std::thread::spawn(move || { info!("Starting to send user event every second");
// Wake up the `event_loop` once every second and dispatch a custom event loop {
// from a different thread. let _ = _event_loop_proxy.send_event(UserEvent::WakeUp);
info!("Starting to send user event every second"); std::thread::sleep(std::time::Duration::from_secs(1));
loop { }
let _ = sender.send(Action::Message); });
event_loop_proxy.wake_up();
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
}
let app = Application::new(&event_loop, receiver, sender); let mut state = Application::new(&event_loop);
Ok(event_loop.run_app(app)?)
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 {
/// Trigger actions through proxy wake up.
receiver: Receiver<Action>,
sender: Sender<Action>,
/// Custom cursors assets. /// Custom cursors assets.
custom_cursors: Result<Vec<CustomCursor>, RequestError>, custom_cursors: Vec<CustomCursor>,
/// Application icon. /// Application icon.
icon: Icon, icon: Icon,
windows: HashMap<WindowId, WindowState>, windows: HashMap<WindowId, WindowState>,
/// Drawing context. /// Drawing context.
/// ///
/// With OpenGL it could be EGLDisplay. /// With OpenGL it could be EGLDisplay.
#[cfg(not(android_platform))] #[cfg(not(any(android_platform, ios_platform)))]
context: Option<Context<DisplayHandle<'static>>>, context: Option<Context<DisplayHandle<'static>>>,
} }
impl Application { impl Application {
fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self { fn new<T>(event_loop: &EventLoop<T>) -> 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(android_platform))] #[cfg(not(any(android_platform, ios_platform)))]
let context = Some( let context = Some(
Context::new(unsafe { Context::new(unsafe {
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>( std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
@@ -112,18 +105,14 @@ impl Application {
let icon = load_icon(include_bytes!("data/icon.png")); let icon = load_icon(include_bytes!("data/icon.png"));
info!("Loading cursor assets"); info!("Loading cursor assets");
let custom_cursors = [ 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"))),
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/gradient.png"))), event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/gradient.png"))),
] ];
.into_iter()
.collect();
Self { Self {
receiver, #[cfg(not(any(android_platform, ios_platform)))]
sender,
#[cfg(not(android_platform))]
context, context,
custom_cursors, custom_cursors,
icon, icon,
@@ -133,13 +122,13 @@ impl Application {
fn create_window( fn create_window(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &ActiveEventLoop,
_tab_id: Option<String>, _tab_id: Option<String>,
) -> Result<WindowId, Box<dyn Error>> { ) -> Result<WindowId, Box<dyn Error>> {
// TODO read-out activation token. // TODO read-out activation token.
#[allow(unused_mut)] #[allow(unused_mut)]
let mut window_attributes = WindowAttributes::default() let mut window_attributes = Window::default_attributes()
.with_title("Winit window") .with_title("Winit window")
.with_transparent(true) .with_transparent(true)
.with_window_icon(Some(self.icon.clone())); .with_window_icon(Some(self.icon.clone()));
@@ -151,28 +140,6 @@ impl Application {
window_attributes = window_attributes.with_activation_token(token); window_attributes = window_attributes.with_activation_token(token);
} }
#[cfg(x11_platform)]
match std::env::var("X11_VISUAL_ID") {
Ok(visual_id_str) => {
info!("Using X11 visual id {visual_id_str}");
let visual_id = visual_id_str.parse()?;
window_attributes = window_attributes.with_x11_visual(visual_id);
},
Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"),
}
#[cfg(x11_platform)]
match std::env::var("X11_SCREEN_ID") {
Ok(screen_id_str) => {
info!("Placing the window on X11 screen {screen_id_str}");
let screen_id = screen_id_str.parse()?;
window_attributes = window_attributes.with_x11_screen(screen_id);
},
Err(_) => info!(
"Set the X11_SCREEN_ID env variable to place the window on non-default screen"
),
}
#[cfg(macos_platform)] #[cfg(macos_platform)]
if let Some(tab_id) = _tab_id { if let Some(tab_id) = _tab_id {
window_attributes = window_attributes.with_tabbing_identifier(&tab_id); window_attributes = window_attributes.with_tabbing_identifier(&tab_id);
@@ -180,6 +147,7 @@ impl Application {
#[cfg(web_platform)] #[cfg(web_platform)]
{ {
use winit::platform::web::WindowAttributesExtWebSys;
window_attributes = window_attributes.with_append(true); window_attributes = window_attributes.with_append(true);
} }
@@ -201,23 +169,7 @@ impl Application {
Ok(window_id) Ok(window_id)
} }
fn handle_action_from_proxy(&mut self, _event_loop: &dyn ActiveEventLoop, action: Action) { fn handle_action(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, action: Action) {
match action {
#[cfg(web_platform)]
Action::DumpMonitors => self.dump_monitors(_event_loop),
Action::Message => {
info!("User wake up");
},
_ => unreachable!("Tried to execute invalid action without `WindowId`"),
}
}
fn handle_action_with_window(
&mut self,
event_loop: &dyn 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();
info!("Executing action: {action:?}"); info!("Executing action: {action:?}");
@@ -242,35 +194,16 @@ impl Application {
Action::ToggleResizable => window.toggle_resizable(), Action::ToggleResizable => window.toggle_resizable(),
Action::ToggleDecorations => window.toggle_decorations(), Action::ToggleDecorations => window.toggle_decorations(),
Action::ToggleFullscreen => window.toggle_fullscreen(), Action::ToggleFullscreen => window.toggle_fullscreen(),
#[cfg(macos_platform)]
Action::ToggleSimpleFullscreen => {
window.window.set_simple_fullscreen(!window.window.simple_fullscreen());
},
Action::ToggleMaximize => window.toggle_maximize(), Action::ToggleMaximize => window.toggle_maximize(),
Action::ToggleImeInput => window.toggle_ime(), Action::ToggleImeInput => window.toggle_ime(),
Action::Minimize => window.minimize(), Action::Minimize => window.minimize(),
Action::NextCursor => window.next_cursor(), Action::NextCursor => window.next_cursor(),
Action::NextCustomCursor => { Action::NextCustomCursor => window.next_custom_cursor(&self.custom_cursors),
if let Err(err) = self.custom_cursors.as_ref().map(|c| window.next_custom_cursor(c))
{
error!("Error creating custom cursor: {err}");
}
},
#[cfg(web_platform)] #[cfg(web_platform)]
Action::UrlCustomCursor => { Action::UrlCustomCursor => window.url_custom_cursor(event_loop),
if let Err(err) = window.url_custom_cursor(event_loop) {
error!("Error creating custom cursor from URL: {err}");
}
},
#[cfg(web_platform)] #[cfg(web_platform)]
Action::AnimationCustomCursor => { Action::AnimationCustomCursor => {
if let Err(err) = self window.animation_custom_cursor(event_loop, &self.custom_cursors)
.custom_cursors
.as_ref()
.map(|c| window.animation_custom_cursor(event_loop, c))
{
error!("Error creating animated custom cursor: {err}");
}
}, },
Action::CycleCursorGrab => window.cycle_cursor_grab(), Action::CycleCursorGrab => window.cycle_cursor_grab(),
Action::DragWindow => window.drag_window(), Action::DragWindow => window.drag_window(),
@@ -293,30 +226,10 @@ impl Application {
} }
}, },
Action::RequestResize => window.swap_dimensions(), Action::RequestResize => window.swap_dimensions(),
#[cfg(web_platform)]
Action::DumpMonitors => {
let future = event_loop.request_detailed_monitor_permission();
let proxy = event_loop.create_proxy();
let sender = self.sender.clone();
wasm_bindgen_futures::spawn_local(async move {
if let Err(error) = future.await {
error!("{error}")
}
let _ = sender.send(Action::DumpMonitors);
proxy.wake_up();
});
},
#[cfg(not(web_platform))]
Action::DumpMonitors => self.dump_monitors(event_loop),
Action::Message => {
self.sender.send(Action::Message).unwrap();
event_loop.create_proxy().wake_up();
},
} }
} }
fn dump_monitors(&self, event_loop: &dyn ActiveEventLoop) { fn dump_monitors(&self, event_loop: &ActiveEventLoop) {
info!("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() {
@@ -332,32 +245,27 @@ impl Application {
info!("{intro}: [no name]"); info!("{intro}: [no name]");
} }
if let Some(current_mode) = monitor.current_video_mode() { let PhysicalSize { width, height } = monitor.size();
let PhysicalSize { width, height } = current_mode.size(); info!(
let bits = " Current mode: {width}x{height}{}",
current_mode.bit_depth().map(|bits| format!("x{bits}")).unwrap_or_default(); if let Some(m_hz) = monitor.refresh_rate_millihertz() {
let m_hz = current_mode format!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000)
.refresh_rate_millihertz() } else {
.map(|m_hz| format!(" @ {}.{} Hz", m_hz.get() / 1000, m_hz.get() % 1000)) String::new()
.unwrap_or_default(); }
info!(" {width}x{height}{bits}{m_hz}"); );
}
if let Some(PhysicalPosition { x, y }) = monitor.position() { let PhysicalPosition { x, y } = monitor.position();
info!(" Position: {x},{y}"); info!(" Position: {x},{y}");
}
info!(" Scale factor: {}", monitor.scale_factor()); info!(" Scale factor: {}", monitor.scale_factor());
info!(" 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().map(|bits| format!("x{bits}")).unwrap_or_default(); let bits = mode.bit_depth();
let m_hz = mode let m_hz = mode.refresh_rate_millihertz();
.refresh_rate_millihertz() info!(" {width}x{height}x{bits} @ {}.{} Hz", m_hz / 1000, m_hz % 1000);
.map(|m_hz| format!(" @ {}.{} Hz", m_hz.get() / 1000, m_hz.get() % 1000))
.unwrap_or_default();
info!(" {width}x{height}{bits}{m_hz}");
} }
} }
} }
@@ -400,16 +308,14 @@ impl Application {
} }
} }
impl ApplicationHandler for Application { impl ApplicationHandler<UserEvent> for Application {
fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) { fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
while let Ok(action) = self.receiver.try_recv() { info!("User event: {event:?}");
self.handle_action_from_proxy(event_loop, action)
}
} }
fn window_event( fn window_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &ActiveEventLoop,
window_id: WindowId, window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
@@ -419,7 +325,7 @@ impl ApplicationHandler for Application {
}; };
match event { match event {
WindowEvent::SurfaceResized(size) => { WindowEvent::Resized(size) => {
window.resize(size); window.resize(size);
}, },
WindowEvent::Focused(focused) => { WindowEvent::Focused(focused) => {
@@ -472,27 +378,24 @@ impl ApplicationHandler for Application {
}; };
if let Some(action) = action { if let Some(action) = action {
self.handle_action_with_window(event_loop, window_id, action); self.handle_action(event_loop, window_id, action);
} }
} }
}, },
WindowEvent::PointerButton { button, state, .. } => { WindowEvent::MouseInput { button, state, .. } => {
info!("Pointer button {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.mouse_button(), &mods))
.flatten()
{ {
self.handle_action_with_window(event_loop, window_id, action); self.handle_action(event_loop, window_id, action);
} }
}, },
WindowEvent::PointerLeft { .. } => { WindowEvent::CursorLeft { .. } => {
info!("Pointer left Window={window_id:?}"); info!("Cursor left Window={window_id:?}");
window.cursor_left(); window.cursor_left();
}, },
WindowEvent::PointerMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
info!("Moved pointer to {position:?}"); info!("Moved cursor to {position:?}");
window.cursor_moved(position); window.cursor_moved(position);
}, },
WindowEvent::ActivationTokenDone { token: _token, .. } => { WindowEvent::ActivationTokenDone { token: _token, .. } => {
@@ -543,25 +446,27 @@ impl ApplicationHandler for Application {
WindowEvent::TouchpadPressure { .. } WindowEvent::TouchpadPressure { .. }
| WindowEvent::HoveredFileCancelled | WindowEvent::HoveredFileCancelled
| WindowEvent::KeyboardInput { .. } | WindowEvent::KeyboardInput { .. }
| WindowEvent::PointerEntered { .. } | WindowEvent::CursorEntered { .. }
| WindowEvent::AxisMotion { .. }
| WindowEvent::DroppedFile(_) | WindowEvent::DroppedFile(_)
| WindowEvent::HoveredFile(_) | WindowEvent::HoveredFile(_)
| WindowEvent::Destroyed | WindowEvent::Destroyed
| WindowEvent::Touch(_)
| WindowEvent::Moved(_) => (), | WindowEvent::Moved(_) => (),
} }
} }
fn device_event( fn device_event(
&mut self, &mut self,
_event_loop: &dyn ActiveEventLoop, _event_loop: &ActiveEventLoop,
device_id: Option<DeviceId>, device_id: DeviceId,
event: DeviceEvent, event: DeviceEvent,
) { ) {
info!("Device {device_id:?} event: {event:?}"); info!("Device {device_id:?} event: {event:?}");
} }
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
info!("Ready to create surfaces"); info!("Resumed the event loop");
self.dump_monitors(event_loop); self.dump_monitors(event_loop);
// Create initial window. // Create initial window.
@@ -570,35 +475,18 @@ impl ApplicationHandler for Application {
self.print_help(); self.print_help();
} }
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
if self.windows.is_empty() { if self.windows.is_empty() {
info!("No windows left, exiting..."); info!("No windows left, exiting...");
event_loop.exit(); event_loop.exit();
} }
} }
#[cfg(not(android_platform))] #[cfg(not(any(android_platform, ios_platform)))]
fn exiting(&mut self, _event_loop: &dyn ActiveEventLoop) { fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
// We must drop the context here. // We must drop the context here.
self.context = None; self.context = None;
} }
#[cfg(target_os = "macos")]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
Some(self)
}
}
#[cfg(target_os = "macos")]
impl ApplicationHandlerExtMacOS for Application {
fn standard_key_binding(
&mut self,
_event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
action: &str,
) {
info!(?window_id, ?action, "macOS standard key binding");
}
} }
/// State of the window. /// State of the window.
@@ -608,10 +496,10 @@ struct WindowState {
/// Render surface. /// Render surface.
/// ///
/// NOTE: This surface must be dropped before the `Window`. /// NOTE: This surface must be dropped before the `Window`.
#[cfg(not(android_platform))] #[cfg(not(any(android_platform, ios_platform)))]
surface: Surface<DisplayHandle<'static>, Arc<dyn Window>>, surface: Surface<DisplayHandle<'static>, Arc<Window>>,
/// The actual winit Window. /// The actual winit Window.
window: Arc<dyn 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.
@@ -639,31 +527,31 @@ struct WindowState {
} }
impl WindowState { impl WindowState {
fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> { fn new(app: &Application, window: Window) -> Result<Self, Box<dyn Error>> {
let window: Arc<dyn Window> = Arc::from(window); 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(android_platform))] #[cfg(not(any(android_platform, ios_platform)))]
let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&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);
info!("Theme: {theme:?}"); info!("Theme: {theme:?}");
let named_idx = 0; let named_idx = 0;
window.set_cursor(CURSORS[named_idx].into()); window.set_cursor(CURSORS[named_idx]);
// Allow IME out of the box. // Allow IME out of the box.
let ime = true; let ime = true;
window.set_ime_allowed(ime); window.set_ime_allowed(ime);
let size = window.surface_size(); let size = window.inner_size();
let mut state = Self { let mut state = Self {
#[cfg(macos_platform)] #[cfg(macos_platform)]
option_as_alt: window.option_as_alt(), option_as_alt: window.option_as_alt(),
custom_idx: app.custom_cursors.as_ref().map(Vec::len).unwrap_or(1) - 1, custom_idx: app.custom_cursors.len() - 1,
cursor_grab: CursorGrabMode::None, cursor_grab: CursorGrabMode::None,
named_idx, named_idx,
#[cfg(not(android_platform))] #[cfg(not(any(android_platform, ios_platform)))]
surface, surface,
window, window,
theme, theme,
@@ -685,7 +573,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.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into()); self.window.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
} }
} }
@@ -696,7 +584,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.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into()); self.window.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
} }
} }
@@ -730,12 +618,12 @@ impl WindowState {
/// Toggle resize increments on a window. /// Toggle resize increments on a window.
fn toggle_resize_increments(&mut self) { fn toggle_resize_increments(&mut self) {
let new_increments = match self.window.surface_resize_increments() { let new_increments = match self.window.resize_increments() {
Some(_) => None, Some(_) => None,
None => Some(LogicalSize::new(25.0, 25.0).into()), None => Some(LogicalSize::new(25.0, 25.0)),
}; };
info!("Had increments: {}", new_increments.is_none()); info!("Had increments: {}", new_increments.is_none());
self.window.set_surface_resize_increments(new_increments); self.window.set_resize_increments(new_increments);
} }
/// Toggle fullscreen. /// Toggle fullscreen.
@@ -774,22 +662,22 @@ impl WindowState {
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_surface_size`. /// Swap the window dimensions with `request_inner_size`.
fn swap_dimensions(&mut self) { fn swap_dimensions(&mut self) {
let old_surface_size = self.window.surface_size(); let old_inner_size = self.window.inner_size();
let mut surface_size = old_surface_size; let mut inner_size = old_inner_size;
mem::swap(&mut surface_size.width, &mut surface_size.height); mem::swap(&mut inner_size.width, &mut inner_size.height);
info!("Requesting resize from {old_surface_size:?} to {surface_size:?}"); info!("Requesting resize from {old_inner_size:?} to {inner_size:?}");
if let Some(new_surface_size) = self.window.request_surface_size(surface_size.into()) { if let Some(new_inner_size) = self.window.request_inner_size(inner_size) {
if old_surface_size == new_surface_size { if old_inner_size == new_inner_size {
info!("Inner size change got ignored"); info!("Inner size change got ignored");
} else { } else {
self.resize(new_surface_size); self.resize(new_inner_size);
} }
} else { } else {
info!("Requesting surface size is asynchronous"); info!("Request inner size is asynchronous");
} }
} }
@@ -809,43 +697,37 @@ impl WindowState {
/// Custom cursor from an URL. /// Custom cursor from an URL.
#[cfg(web_platform)] #[cfg(web_platform)]
fn url_custom_cursor( fn url_custom_cursor(&mut self, event_loop: &ActiveEventLoop) {
&mut self, let cursor = event_loop.create_custom_cursor(url_custom_cursor());
event_loop: &dyn ActiveEventLoop,
) -> Result<(), Box<dyn Error>> {
let cursor = event_loop.create_custom_cursor(url_custom_cursor())?;
self.window.set_cursor(cursor.into()); self.window.set_cursor(cursor);
Ok(())
} }
/// Custom cursor from a URL. /// Custom cursor from a URL.
#[cfg(web_platform)] #[cfg(web_platform)]
fn animation_custom_cursor( fn animation_custom_cursor(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &ActiveEventLoop,
custom_cursors: &[CustomCursor], custom_cursors: &[CustomCursor],
) -> Result<(), Box<dyn Error>> { ) {
use std::time::Duration; use std::time::Duration;
use winit::platform::web::CustomCursorExtWebSys;
let cursors = vec![ let cursors = vec![
custom_cursors[0].clone(), custom_cursors[0].clone(),
custom_cursors[1].clone(), custom_cursors[1].clone(),
event_loop.create_custom_cursor(url_custom_cursor())?, event_loop.create_custom_cursor(url_custom_cursor()),
]; ];
let cursor = CustomCursor::from_animation(Duration::from_secs(3), cursors).unwrap(); let cursor = CustomCursor::from_animation(Duration::from_secs(3), cursors).unwrap();
let cursor = event_loop.create_custom_cursor(cursor)?; let cursor = event_loop.create_custom_cursor(cursor);
self.window.set_cursor(cursor.into()); self.window.set_cursor(cursor);
Ok(())
} }
/// Resize the surface 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!("Surface resized to {size:?}"); info!("Resized to {size:?}");
#[cfg(not(android_platform))] #[cfg(not(any(android_platform, ios_platform)))]
{ {
let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{ {
@@ -866,7 +748,7 @@ impl WindowState {
/// Show window menu. /// Show window menu.
fn show_menu(&self) { fn show_menu(&self) {
if let Some(position) = self.cursor_position { if let Some(position) = self.cursor_position {
self.window.show_window_menu(position.into()); self.window.show_window_menu(position);
} }
} }
@@ -889,7 +771,7 @@ impl WindowState {
}, },
}; };
let win_size = self.window.surface_size(); let win_size = self.window.inner_size();
let border_size = BORDER_SIZE * self.window.scale_factor(); let border_size = BORDER_SIZE * self.window.scale_factor();
let x_direction = if position.x < border_size { let x_direction = if position.x < border_size {
@@ -938,49 +820,29 @@ impl WindowState {
} }
/// Draw the window contents. /// Draw the window contents.
#[cfg(not(android_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 {
info!("Skipping drawing occluded window={:?}", self.window.id()); info!("Skipping drawing occluded window={:?}", self.window.id());
return Ok(()); return Ok(());
} }
const WHITE: u32 = 0xffffffff;
const DARK_GRAY: u32 = 0xff181818;
let color = match self.theme {
Theme::Light => WHITE,
Theme::Dark => DARK_GRAY,
};
let mut buffer = self.surface.buffer_mut()?; let mut buffer = self.surface.buffer_mut()?;
buffer.fill(color);
// Draw a different color inside the safe area
let surface_size = self.window.surface_size();
let insets = self.window.safe_area();
for y in 0..surface_size.height {
for x in 0..surface_size.width {
let index = y as usize * surface_size.width as usize + x as usize;
if insets.left <= x
&& x <= (surface_size.width - insets.right)
&& insets.top <= y
&& y <= (surface_size.height - insets.bottom)
{
// In safe area
buffer[index] = match self.theme {
Theme::Light => 0xffe8e8e8, // Light gray
Theme::Dark => 0xff525252, // Medium gray
};
} else {
// Outside safe area
buffer[index] = match self.theme {
Theme::Light => 0xffffffff, // White
Theme::Dark => 0xff181818, // Dark gray
};
}
}
}
// Present the buffer
self.window.pre_present_notify(); self.window.pre_present_notify();
buffer.present()?; buffer.present()?;
Ok(()) Ok(())
} }
#[cfg(android_platform)] #[cfg(any(android_platform, ios_platform))]
fn draw(&mut self) -> Result<(), Box<dyn Error>> { fn draw(&mut self) -> Result<(), Box<dyn Error>> {
info!("Drawing but without rendering..."); info!("Drawing but without rendering...");
Ok(()) Ok(())
@@ -1013,8 +875,6 @@ enum Action {
ToggleDecorations, ToggleDecorations,
ToggleResizable, ToggleResizable,
ToggleFullscreen, ToggleFullscreen,
#[cfg(macos_platform)]
ToggleSimpleFullscreen,
ToggleMaximize, ToggleMaximize,
Minimize, Minimize,
NextCursor, NextCursor,
@@ -1034,8 +894,6 @@ enum Action {
#[cfg(macos_platform)] #[cfg(macos_platform)]
CreateNewTab, CreateNewTab,
RequestResize, RequestResize,
DumpMonitors,
Message,
} }
impl Action { impl Action {
@@ -1048,8 +906,6 @@ impl Action {
Action::ToggleDecorations => "Toggle decorations", Action::ToggleDecorations => "Toggle decorations",
Action::ToggleResizable => "Toggle window resizable state", Action::ToggleResizable => "Toggle window resizable state",
Action::ToggleFullscreen => "Toggle fullscreen", Action::ToggleFullscreen => "Toggle fullscreen",
#[cfg(macos_platform)]
Action::ToggleSimpleFullscreen => "Toggle simple fullscreen",
Action::ToggleMaximize => "Maximize", Action::ToggleMaximize => "Maximize",
Action::Minimize => "Minimize", Action::Minimize => "Minimize",
Action::ToggleResizeIncrements => "Use resize increments when resizing window", Action::ToggleResizeIncrements => "Use resize increments when resizing window",
@@ -1072,14 +928,6 @@ impl Action {
#[cfg(macos_platform)] #[cfg(macos_platform)]
Action::CreateNewTab => "Create new tab", Action::CreateNewTab => "Create new tab",
Action::RequestResize => "Request a resize", Action::RequestResize => "Request a resize",
#[cfg(not(web_platform))]
Action::DumpMonitors => "Dump monitor information",
#[cfg(web_platform)]
Action::DumpMonitors => {
"Request permission to query detailed monitor information and dump monitor \
information"
},
Action::Message => "Prints a message through a user wake up",
} }
} }
} }
@@ -1102,6 +950,8 @@ fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
fn url_custom_cursor() -> CustomCursorSource { fn url_custom_cursor() -> CustomCursorSource {
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use winit::platform::web::CustomCursorExtWebSys;
static URL_COUNTER: AtomicU64 = AtomicU64::new(0); static URL_COUNTER: AtomicU64 = AtomicU64::new(0);
CustomCursor::from_url( CustomCursor::from_url(
@@ -1192,8 +1042,6 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow), Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow),
Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp), Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp),
Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen), Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen),
#[cfg(macos_platform)]
Binding::new("F", ModifiersState::ALT, Action::ToggleSimpleFullscreen),
Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations), Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations),
Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput), Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput),
Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab), Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),
@@ -1201,7 +1049,6 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable), Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
Binding::new("R", ModifiersState::ALT, Action::RequestResize), Binding::new("R", ModifiersState::ALT, Action::RequestResize),
// M. // M.
Binding::new("M", ModifiersState::CONTROL.union(ModifiersState::ALT), Action::DumpMonitors),
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),
// N. // N.
@@ -1230,7 +1077,6 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab), Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
#[cfg(macos_platform)] #[cfg(macos_platform)]
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt), Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
Binding::new("S", ModifiersState::CONTROL, Action::Message),
]; ];
const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[ const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[

View File

@@ -7,21 +7,21 @@ fn main() -> Result<(), Box<dyn Error>> {
use winit::event::WindowEvent; use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::x11::WindowAttributesExtX11; use winit::platform::x11::WindowAttributesExtX11;
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
pub struct XEmbedDemo { pub struct XEmbedDemo {
parent_window_id: u32, parent_window_id: u32,
window: Option<Box<dyn Window>>, window: Option<Window>,
} }
impl ApplicationHandler for XEmbedDemo { impl ApplicationHandler for XEmbedDemo {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window_attributes = WindowAttributes::default() let window_attributes = Window::default_attributes()
.with_title("An embedded window!") .with_title("An embedded window!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(self.parent_window_id); .with_embed_parent_window(self.parent_window_id);
self.window = Some(event_loop.create_window(window_attributes).unwrap()); self.window = Some(event_loop.create_window(window_attributes).unwrap());
@@ -29,7 +29,7 @@ fn main() -> Result<(), Box<dyn Error>> {
fn window_event( fn window_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &ActiveEventLoop,
_window_id: WindowId, _window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
@@ -38,13 +38,13 @@ fn main() -> Result<(), Box<dyn Error>> {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
window.pre_present_notify(); window.pre_present_notify();
fill::fill_window(window.as_ref()); fill::fill_window(window);
}, },
_ => (), _ => (),
} }
} }
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
self.window.as_ref().unwrap().request_redraw(); self.window.as_ref().unwrap().request_redraw();
} }
} }
@@ -58,7 +58,8 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
Ok(event_loop.run_app(XEmbedDemo { parent_window_id, window: None })?) let mut app = XEmbedDemo { parent_window_id, window: None };
event_loop.run_app(&mut app).map_err(Into::into)
} }
#[cfg(not(x11_platform))] #[cfg(not(x11_platform))]

View File

@@ -1,20 +1,19 @@
comment_width = 100
condense_wildcard_suffixes = true
error_on_unformatted = true
format_code_in_doc_comments = true format_code_in_doc_comments = true
format_macro_bodies = true
format_macro_matchers = true
format_strings = true
group_imports = "StdExternalCrate"
hex_literal_case = "Lower"
imports_granularity = "Module"
match_block_trailing_comma = true match_block_trailing_comma = true
newline_style = "Unix" condense_wildcard_suffixes = true
normalize_comments = true use_field_init_shorthand = true
normalize_doc_attributes = true normalize_doc_attributes = true
overflow_delimited_expr = 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. # Some macros break with this.
reorder_impl_items = false reorder_impl_items = false
use_field_init_shorthand = true newline_style = "Unix"
use_small_heuristics = "Max" format_strings = true
wrap_comments = true wrap_comments = true
comment_width = 100

View File

@@ -2,202 +2,97 @@
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::event_loop::ActiveEventLoop; use crate::event_loop::ActiveEventLoop;
#[cfg(any(docsrs, macos_platform))]
use crate::platform::macos::ApplicationHandlerExtMacOS;
use crate::window::WindowId; use crate::window::WindowId;
/// The handler of the application events. /// The handler of the application events.
pub trait ApplicationHandler { pub trait ApplicationHandler<T: 'static = ()> {
/// 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
/// events, such as updating frame timing information for benchmarking or checking the /// events, such as updating frame timing information for benchmarking or checking the
/// [`StartCause`] to see if a timer set by /// [`StartCause`] to see if a timer set by
/// [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil] has elapsed. /// [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil] has elapsed.
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) { fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
let _ = (event_loop, cause); let _ = (event_loop, cause);
} }
/// Emitted when the application has been resumed. /// Emitted when the application has been resumed.
/// ///
/// See [`suspended()`][Self::suspended]. /// For consistency, all platforms emit a `Resumed` event even if they don't themselves have a
/// formal suspend/resume lifecycle. For systems without a formal suspend/resume lifecycle
/// the `Resumed` event is always emitted after the
/// [`NewEvents(StartCause::Init)`][StartCause::Init] event.
/// ///
/// ## Platform-specific /// # Portability
/// ///
/// ### iOS /// It's recommended that applications should only initialize their graphics context and create
/// a window after they have received their first `Resumed` event. Some systems
/// (specifically Android) won't allow applications to create a render surface until they are
/// resumed.
/// ///
/// On iOS, the [`resumed()`] method is called in response to an [`applicationDidBecomeActive`] /// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally
/// callback which means the application is about to transition from the inactive to active /// driven by multiple platform-specific events, and that there may be subtle differences across
/// state (according to the [iOS application lifecycle]). /// 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.
///
/// Also see [`Suspended`] notes.
///
/// ## Android
///
/// On Android, the `Resumed` event is sent when a new [`SurfaceView`] has been created. This is
/// expected to closely correlate with the [`onResume`] lifecycle event but there may
/// technically be a discrepancy.
///
/// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume()
///
/// Applications that need to run on Android must wait until they have been `Resumed`
/// before they will be able to create a render surface (such as an `EGLSurface`,
/// [`VkSurfaceKHR`] or [`wgpu::Surface`]) which depend on having a
/// [`SurfaceView`]. Applications must also assume that if they are [`Suspended`], then their
/// render surfaces are invalid and should be dropped.
///
/// Also see [`Suspended`] notes.
///
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
/// ## iOS
///
/// On iOS, the `Resumed` event is emitted in response to an [`applicationDidBecomeActive`]
/// callback which means the application is "active" (according to the
/// [iOS application lifecycle]).
/// ///
/// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive /// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
/// ///
/// ### Web /// ## Web
/// ///
/// On Web, the [`resumed()`] method is called in response to a [`pageshow`] event if the /// On Web, the `Resumed` event is emitted in response to a [`pageshow`] event
/// page is being restored from the [`bfcache`] (back/forward cache) - an in-memory cache /// with the property [`persisted`] being true, which means that the page is being
/// that stores a complete snapshot of a page (including the JavaScript heap) as the user is /// restored from the [`bfcache`] (back/forward cache) - an in-memory cache that
/// navigating away. /// stores a complete snapshot of a page (including the JavaScript heap) as the
/// user is navigating away.
/// ///
/// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event /// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event
/// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted
/// [`bfcache`]: https://web.dev/bfcache/ /// [`bfcache`]: https://web.dev/bfcache/
/// /// [`Suspended`]: Self::suspended
/// ### Android fn resumed(&mut self, event_loop: &ActiveEventLoop);
///
/// On Android, the [`resumed()`] method is called when the `Activity` is (again, if after a
/// prior [`suspended()`]) being displayed to the user. This is a good place to begin drawing
/// visual elements, running animations, etc. It is driven by Android's [`onStart()`] method.
///
/// [`onStart()`]: https://developer.android.com/reference/android/app/Activity#onStart()
///
/// ### Others
///
/// **macOS / Orbital / Wayland / Windows / X11:** Unsupported.
///
/// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended()
/// [`exiting()`]: Self::exiting()
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted from the point onwards the application should create render surfaces. /// Emitted when an event is sent from [`EventLoopProxy::send_event`].
/// ///
/// See [`destroy_surfaces()`]. /// [`EventLoopProxy::send_event`]: crate::event_loop::EventLoopProxy::send_event
/// fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
/// ## Portability let _ = (event_loop, event);
///
/// It's recommended that applications should only initialize their render surfaces after the
/// [`can_create_surfaces()`] method is called. Some systems (specifically Android) won't allow
/// applications to create a render surface until that point.
///
/// For consistency, all platforms call this method even if they don't themselves have a formal
/// surface destroy/create lifecycle. For systems without a surface destroy/create lifecycle the
/// [`can_create_surfaces()`] event is always emitted after the [`StartCause::Init`] event.
///
/// Applications should be able to gracefully handle back-to-back [`can_create_surfaces()`] and
/// [`destroy_surfaces()`] calls.
///
/// ## Platform-specific
///
/// ### Android
///
/// On Android, the [`can_create_surfaces()`] method is called when a new [`NativeWindow`]
/// (native [`Surface`]) is created which backs the application window. This is expected to
/// closely correlate with the [`onStart`] lifecycle event which typically results in a surface
/// to be created after the app becomes visible.
///
/// Applications that need to run on Android must wait until they have received a surface before
/// they will be able to create a render surface (such as an `EGLSurface`, [`VkSurfaceKHR`]
/// or [`wgpu::Surface`]) which depend on having a [`NativeWindow`]. Applications must handle
/// [`destroy_surfaces()`], where their render surfaces are invalid and should be dropped.
///
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface
/// [`onStart`]: https://developer.android.com/reference/android/app/Activity#onStart()
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
/// [`can_create_surfaces()`]: Self::can_create_surfaces()
/// [`destroy_surfaces()`]: Self::destroy_surfaces()
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop);
/// Called after a wake up is requested using [`EventLoopProxy::wake_up()`].
///
/// Multiple calls to the aforementioned method will be merged, and will only wake the event
/// loop once; however, due to the nature of multi-threading some wake ups may appear
/// 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: &dyn ActiveEventLoop,
/// # _window_id: winit::window::WindowId,
/// # _event: winit::event::WindowEvent,
/// # ) {
/// # }
/// #
/// # fn can_create_surfaces(&mut self, _event_loop: &dyn ActiveEventLoop) {}
/// #
/// fn proxy_wake_up(&mut self, _event_loop: &dyn 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: &dyn 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.
fn window_event( fn window_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &ActiveEventLoop,
window_id: WindowId, window_id: WindowId,
event: WindowEvent, event: WindowEvent,
); );
@@ -205,8 +100,8 @@ pub trait ApplicationHandler {
/// Emitted when the OS sends an event to a device. /// Emitted when the OS sends an event to a device.
fn device_event( fn device_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &ActiveEventLoop,
device_id: Option<DeviceId>, device_id: DeviceId,
event: DeviceEvent, event: DeviceEvent,
) { ) {
let _ = (event_loop, device_id, event); let _ = (event_loop, device_id, event);
@@ -223,90 +118,72 @@ pub trait ApplicationHandler {
/// ///
/// This is not an ideal event to drive application rendering from and instead applications /// This is not an ideal event to drive application rendering from and instead applications
/// should render in response to [`WindowEvent::RedrawRequested`] events. /// should render in response to [`WindowEvent::RedrawRequested`] events.
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
let _ = event_loop; let _ = event_loop;
} }
/// Emitted when the application has been suspended. /// Emitted when the application has been suspended.
/// ///
/// See [`resumed()`][Self::resumed]. /// # Portability
/// ///
/// ## Platform-specific /// Not all platforms support the notion of suspending applications, and there may be no
/// technical way to guarantee being able to emit a `Suspended` event if the OS has
/// no formal application lifecycle (currently only Android, iOS, and Web do). For this reason,
/// Winit does not currently try to emit pseudo `Suspended` events before the application
/// quits on platforms without an application lifecycle.
/// ///
/// ### iOS /// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally
/// driven by multiple platform-specific events, and that there may be subtle differences across
/// platforms with how these internal events are delivered, it's recommended that applications
/// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`]
/// events.
/// ///
/// On iOS, the [`suspended()`] method is called in response to an /// Also see [`Resumed`] notes.
/// [`applicationWillResignActive`] callback which means that the application is about to ///
/// transition from the active to inactive state (according to the [iOS application lifecycle]). /// ## Android
///
/// On Android, the `Suspended` event is only sent when the application's associated
/// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`]
/// lifecycle event but there may technically be a discrepancy.
///
/// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause()
///
/// Applications that need to run on Android should assume their [`SurfaceView`] has been
/// destroyed, which indirectly invalidates any existing render surfaces that may have been
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
///
/// After being `Suspended` on Android applications must drop all render surfaces before
/// the event callback completes, which may be re-created when the application is next
/// [`Resumed`].
///
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
/// ## iOS
///
/// On iOS, the `Suspended` event is currently emitted in response to an
/// [`applicationWillResignActive`] callback which means that the application is
/// about to transition from the active to inactive state (according to the
/// [iOS application lifecycle]).
/// ///
/// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive /// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
/// ///
/// ### Web /// ## Web
/// ///
/// On Web, the [`suspended()`] method is called in response to a [`pagehide`] event if the /// On Web, the `Suspended` event is emitted in response to a [`pagehide`] event
/// page is being stored in the [`bfcache`] (back/forward cache) - an in-memory cache that /// with the property [`persisted`] being true, which means that the page is being
/// stores a complete snapshot of a page (including the JavaScript heap) as the user is /// put in the [`bfcache`] (back/forward cache) - an in-memory cache that stores a
/// complete snapshot of a page (including the JavaScript heap) as the user is
/// navigating away. /// navigating away.
/// ///
/// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event /// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
/// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted
/// [`bfcache`]: https://web.dev/bfcache/ /// [`bfcache`]: https://web.dev/bfcache/
/// /// [`Resumed`]: Self::resumed
/// ### Android fn suspended(&mut self, event_loop: &ActiveEventLoop) {
///
/// On Android, the [`suspended()`] method is called when the `Activity` is no longer visible
/// to the user. This is a good place to stop refreshing UI, running animations and other visual
/// things. It is driven by Android's [`onStop()`] method.
///
/// After this event the application either receives [`resumed()`] again, or [`exiting()`].
///
/// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop()
///
/// ### Others
///
/// **macOS / Orbital / Wayland / Windows / X11:** Unsupported.
///
/// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended()
/// [`exiting()`]: Self::exiting()
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted when the application must destroy its render surfaces.
///
/// See [`can_create_surfaces()`] for more details.
///
/// ## Platform-specific
///
/// ### Android
///
/// On Android, the [`destroy_surfaces()`] method is called when the application's
/// [`NativeWindow`] (native [`Surface`]) is destroyed. This is expected to closely correlate
/// with the [`onStop`] lifecycle event which typically results in the surface to be destroyed
/// after the app becomes invisible.
///
/// Applications that need to run on Android should assume their [`NativeWindow`] has been
/// destroyed, which indirectly invalidates any existing render surfaces that may have been
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
///
/// When receiving [`destroy_surfaces()`] Android applications should drop all render surfaces
/// before the event callback completes, which may be re-created when the application next
/// receives [`can_create_surfaces()`].
///
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface
/// [`onStop`]: https://developer.android.com/reference/android/app/Activity#onStop()
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
/// ### Others
///
/// - **iOS / macOS / Orbital / Wayland / Web / Windows / X11:** Unsupported.
///
/// [`can_create_surfaces()`]: Self::can_create_surfaces()
/// [`destroy_surfaces()`]: Self::destroy_surfaces()
fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop; let _ = event_loop;
} }
@@ -314,7 +191,7 @@ pub trait ApplicationHandler {
/// ///
/// 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 exit right after. /// will exit right after.
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) { fn exiting(&mut self, event_loop: &ActiveEventLoop) {
let _ = event_loop; let _ = event_loop;
} }
@@ -342,46 +219,31 @@ pub trait ApplicationHandler {
/// ### Others /// ### Others
/// ///
/// - **macOS / Orbital / Wayland / Web / Windows:** Unsupported. /// - **macOS / Orbital / Wayland / Web / Windows:** Unsupported.
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) { fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
let _ = event_loop; let _ = event_loop;
} }
/// The macOS-specific handler.
///
/// The return value from this should not change at runtime.
#[cfg(any(docsrs, macos_platform))]
#[inline(always)]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
None
}
} }
#[deny(clippy::missing_trait_methods)] impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for &mut A {
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
#[inline] #[inline]
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) { fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause); (**self).new_events(event_loop, cause);
} }
#[inline] #[inline]
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
(**self).resumed(event_loop); (**self).resumed(event_loop);
} }
#[inline] #[inline]
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
(**self).can_create_surfaces(event_loop); (**self).user_event(event_loop, event);
}
#[inline]
fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
} }
#[inline] #[inline]
fn window_event( fn window_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &ActiveEventLoop,
window_id: WindowId, window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
@@ -391,71 +253,54 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
#[inline] #[inline]
fn device_event( fn device_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &ActiveEventLoop,
device_id: Option<DeviceId>, device_id: DeviceId,
event: DeviceEvent, event: DeviceEvent,
) { ) {
(**self).device_event(event_loop, device_id, event); (**self).device_event(event_loop, device_id, event);
} }
#[inline] #[inline]
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
(**self).about_to_wait(event_loop); (**self).about_to_wait(event_loop);
} }
#[inline] #[inline]
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) { fn suspended(&mut self, event_loop: &ActiveEventLoop) {
(**self).suspended(event_loop); (**self).suspended(event_loop);
} }
#[inline] #[inline]
fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn exiting(&mut self, event_loop: &ActiveEventLoop) {
(**self).destroy_surfaces(event_loop);
}
#[inline]
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).exiting(event_loop); (**self).exiting(event_loop);
} }
#[inline] #[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) { fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
(**self).memory_warning(event_loop); (**self).memory_warning(event_loop);
} }
#[cfg(any(docsrs, macos_platform))]
#[inline]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
(**self).macos_handler()
}
} }
#[deny(clippy::missing_trait_methods)] impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for Box<A> {
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
#[inline] #[inline]
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) { fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause); (**self).new_events(event_loop, cause);
} }
#[inline] #[inline]
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
(**self).resumed(event_loop); (**self).resumed(event_loop);
} }
#[inline] #[inline]
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
(**self).can_create_surfaces(event_loop); (**self).user_event(event_loop, event);
}
#[inline]
fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
} }
#[inline] #[inline]
fn window_event( fn window_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &ActiveEventLoop,
window_id: WindowId, window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
@@ -465,41 +310,30 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
#[inline] #[inline]
fn device_event( fn device_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &ActiveEventLoop,
device_id: Option<DeviceId>, device_id: DeviceId,
event: DeviceEvent, event: DeviceEvent,
) { ) {
(**self).device_event(event_loop, device_id, event); (**self).device_event(event_loop, device_id, event);
} }
#[inline] #[inline]
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
(**self).about_to_wait(event_loop); (**self).about_to_wait(event_loop);
} }
#[inline] #[inline]
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) { fn suspended(&mut self, event_loop: &ActiveEventLoop) {
(**self).suspended(event_loop); (**self).suspended(event_loop);
} }
#[inline] #[inline]
fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn exiting(&mut self, event_loop: &ActiveEventLoop) {
(**self).destroy_surfaces(event_loop);
}
#[inline]
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).exiting(event_loop); (**self).exiting(event_loop);
} }
#[inline] #[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) { fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
(**self).memory_warning(event_loop); (**self).memory_warning(event_loop);
} }
#[cfg(any(docsrs, macos_platform))]
#[inline]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
(**self).macos_handler()
}
} }

View File

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

View File

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

View File

@@ -1,18 +1,3 @@
## 0.30.5
### Added
- Add `ActiveEventLoop::system_theme()`, returning the current system theme.
- On Web, implement `Error` for `platform::web::CustomCursorError`.
- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop.
### Fixed
- On MacOS, fix building with `feature = "rwh_04"`.
- On Web, pen events are now routed through to `WindowEvent::Cursor*`.
- On macOS, fix panic when releasing not available monitor.
- On MacOS, return the system theme in `Window::theme()` if no theme override is set.
## 0.30.4 ## 0.30.4
### Changed ### Changed
@@ -64,7 +49,6 @@
- Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`. - 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`. - 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 ### Fixed
@@ -141,7 +125,7 @@
`Fn`. The semantics are mostly the same, given that the capture list of the `Fn`. The semantics are mostly the same, given that the capture list of the
closure is your new `State`. Consider the following code: closure is your new `State`. Consider the following code:
```rust,no_run,ignore ```rust,no_run
use winit::event::Event; use winit::event::Event;
use winit::event_loop::EventLoop; use winit::event_loop::EventLoop;
use winit::window::Window; use winit::window::Window;
@@ -177,7 +161,7 @@
we move particular `match event` arms into methods on `ApplicationHandler`, we move particular `match event` arms into methods on `ApplicationHandler`,
for example: for example:
```rust,no_run,ignore ```rust,no_run
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId}; use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId};
use winit::event_loop::{EventLoop, ActiveEventLoop}; use winit::event_loop::{EventLoop, ActiveEventLoop};
@@ -240,7 +224,7 @@
Using the migration example from above, you can change your code as follows: Using the migration example from above, you can change your code as follows:
```rust,no_run,ignore ```rust,no_run
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId}; use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId};
use winit::event_loop::{EventLoop, ActiveEventLoop}; use winit::event_loop::{EventLoop, ActiveEventLoop};

View File

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

View File

@@ -50,7 +50,7 @@ impl From<CustomCursor> for Cursor {
/// ```no_run /// ```no_run
/// # use winit::event_loop::ActiveEventLoop; /// # use winit::event_loop::ActiveEventLoop;
/// # use winit::window::Window; /// # use winit::window::Window;
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) { /// # fn scope(event_loop: &ActiveEventLoop, window: &Window) {
/// use winit::window::CustomCursor; /// use winit::window::CustomCursor;
/// ///
/// let w = 10; /// let w = 10;
@@ -62,13 +62,13 @@ impl From<CustomCursor> for Cursor {
/// ///
/// #[cfg(target_family = "wasm")] /// #[cfg(target_family = "wasm")]
/// let source = { /// let source = {
/// use winit::platform::web::CustomCursorExtWeb; /// use winit::platform::web::CustomCursorExtWebSys;
/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0) /// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
/// }; /// };
/// ///
/// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) { /// let custom_cursor = event_loop.create_custom_cursor(source);
/// window.set_cursor(custom_cursor.clone().into()); ///
/// } /// window.set_cursor(custom_cursor.clone());
/// # } /// # }
/// ``` /// ```
#[derive(Clone, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
@@ -107,16 +107,13 @@ impl CustomCursor {
/// Source for [`CustomCursor`]. /// Source for [`CustomCursor`].
/// ///
/// See [`CustomCursor`] for more details. /// See [`CustomCursor`] for more details.
#[derive(Debug, Clone, Eq, Hash, PartialEq)] #[derive(Debug)]
pub struct CustomCursorSource { pub struct CustomCursorSource {
// Some platforms don't support custom cursors.
#[allow(dead_code)]
pub(crate) inner: PlatformCustomCursorSource, pub(crate) inner: PlatformCustomCursorSource,
} }
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments. /// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BadImage { pub enum BadImage {
/// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't /// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't
/// guarantee that the cursor will work, but should avoid many platform and device specific /// guarantee that the cursor will work, but should avoid many platform and device specific
@@ -167,7 +164,7 @@ impl Error for BadImage {}
/// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with /// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with
/// images. /// images.
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug, Clone, Eq, Hash, PartialEq)] #[derive(Debug)]
pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage); pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
#[allow(dead_code)] #[allow(dead_code)]
@@ -202,7 +199,7 @@ impl PartialEq for OnlyCursorImage {
impl Eq for OnlyCursorImage {} impl Eq for OnlyCursorImage {}
#[derive(Debug, Clone, Eq, Hash, PartialEq)] #[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) struct CursorImage { pub(crate) struct CursorImage {
pub(crate) rgba: Vec<u8>, pub(crate) rgba: Vec<u8>,

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -3,30 +3,27 @@
//! //!
//! 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
//! [`wake_up`][EventLoopProxy::wake_up] method. Then during handling the wake up //! [`send_event`][EventLoopProxy::send_event] method.
//! 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::sync::Arc; use std::{error, fmt};
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
#[cfg(web_platform)] #[cfg(web_platform)]
use web_time::{Duration, Instant}; use web_time::{Duration, Instant};
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError}; use crate::error::{EventLoopError, OsError};
use crate::event::Event;
use crate::monitor::MonitorHandle; use crate::monitor::MonitorHandle;
use crate::platform_impl; use crate::platform_impl;
use crate::utils::AsAny; use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
/// Provides a way to retrieve events from the system and from the windows that were registered to /// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop. /// the events loop.
@@ -43,8 +40,17 @@ use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttri
/// [`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 { pub struct EventLoop<T: 'static> {
pub(crate) event_loop: platform_impl::EventLoop, pub(crate) event_loop: platform_impl::EventLoop<T>,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
}
/// Target that associates windows with an [`EventLoop`].
///
/// This type exists to allow you to create new windows while Winit executes
/// your callback.
pub struct ActiveEventLoop {
pub(crate) p: platform_impl::ActiveEventLoop,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
} }
@@ -53,15 +59,25 @@ pub struct EventLoop {
/// 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::builder`]. /// This can be created using [`EventLoop::new`] or [`EventLoop::with_user_event`].
#[derive(Default, PartialEq, Eq, Hash)] #[derive(Default)]
pub struct EventLoopBuilder { pub struct EventLoopBuilder<T: 'static> {
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.
#[inline]
#[deprecated = "use `EventLoop::builder` instead"]
pub fn new() -> Self {
EventLoop::builder()
}
}
impl<T> EventLoopBuilder<T> {
/// 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,
@@ -74,7 +90,7 @@ impl EventLoopBuilder {
/// Attempting to create the event loop off the main thread will panic. This /// Attempting to create the event loop off the main thread will panic. This
/// restriction isn't strictly necessary on all platforms, but is imposed to /// restriction isn't strictly necessary on all platforms, but is imposed to
/// eliminate any nasty surprises when porting to platforms that require it. /// eliminate any nasty surprises when porting to platforms that require it.
/// `EventLoopBuilderExt::with_any_thread` functions are exposed in the relevant /// `EventLoopBuilderExt::any_thread` functions are exposed in the relevant
/// [`platform`] module if the target platform supports creating an event /// [`platform`] module if the target platform supports creating an event
/// loop on any thread. /// loop on any thread.
/// ///
@@ -96,7 +112,7 @@ impl EventLoopBuilder {
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, EventLoopError> { pub fn build(&mut self) -> Result<EventLoop<T>, 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) {
@@ -117,27 +133,26 @@ impl EventLoopBuilder {
} }
} }
impl fmt::Debug for EventLoopBuilder { impl<T> fmt::Debug for EventLoop<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopBuilder").finish_non_exhaustive() f.pad("EventLoop { .. }")
} }
} }
impl fmt::Debug for EventLoop { impl fmt::Debug for ActiveEventLoop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoop").finish_non_exhaustive() f.pad("ActiveEventLoop { .. }")
} }
} }
/// Set through [`ActiveEventLoop::set_control_flow()`]. /// Set through [`ActiveEventLoop::set_control_flow()`].
/// ///
/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called. /// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
/// ///
/// 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, Hash)]
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
/// whether or not new events are available to process. /// whether or not new events are available to process.
@@ -174,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()
} }
@@ -189,12 +204,33 @@ 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<()> {
EventLoopBuilder { platform_specific: Default::default() } Self::with_user_event()
} }
} }
impl EventLoop { impl<T> EventLoop<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
pub fn with_user_event() -> EventLoopBuilder<T> {
EventLoopBuilder { platform_specific: Default::default(), _p: PhantomData }
}
/// 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.
@@ -209,38 +245,37 @@ impl EventLoop {
/// ///
/// Web applications are recommended to use /// Web applications are recommended to use
#[cfg_attr( #[cfg_attr(
any(web_platform, docsrs), web_platform,
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]" doc = "[`EventLoopExtWebSys::spawn_app()`][crate::platform::web::EventLoopExtWebSys::spawn_app()]"
)] )]
#[cfg_attr(not(any(web_platform, docsrs)), doc = " `EventLoopExtWeb::spawn_app()`")] #[cfg_attr(not(web_platform), doc = "`EventLoopExtWebSys::spawn()`")]
/// [^1] instead of [`run_app()`] to avoid the need for the Javascript exception trick, and to /// [^1] instead of [`run_app()`] to avoid the need
/// make it clearer that the event loop runs asynchronously (via the browser's own, /// for the Javascript exception trick, and to make it clearer that the event loop runs
/// internal, event loop) and doesn't block the current thread of execution like it does /// asynchronously (via the browser's own, internal, event loop) and doesn't block the
/// on other platforms. /// current thread of execution like it does on other platforms.
/// ///
/// This function won't be available with `target_feature = "exception-handling"`. /// This function won't be available with `target_feature = "exception-handling"`.
/// ///
/// [^1]: `spawn_app()` is only available on the Web platform.
///
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
/// [`run_app()`]: Self::run_app() /// [`run_app()`]: Self::run_app()
/// [^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>(self, app: A) -> Result<(), EventLoopError> { pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
self.event_loop.run_app(app) self.event_loop.run(|event, event_loop| dispatch_event_for_app(app, event_loop, event))
} }
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events /// 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 { pub fn create_proxy(&self) -> EventLoopProxy<T> {
self.event_loop.window_target().create_proxy() EventLoopProxy { 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 {
self.event_loop.window_target().owned_display_handle() OwnedDisplayHandle { 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.
@@ -254,35 +289,56 @@ impl EventLoop {
allowed = ?allowed allowed = ?allowed
) )
.entered(); .entered();
self.event_loop.window_target().listen_device_events(allowed)
self.event_loop.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.window_target().set_control_flow(control_flow); self.event_loop.window_target().p.set_control_flow(control_flow)
}
/// Create a window.
///
/// Creating window without event loop running often leads to improper window creation;
/// use [`ActiveEventLoop::create_window`] instead.
#[deprecated = "use `ActiveEventLoop::create_window` instead"]
#[inline]
pub fn create_window(&self, window_attributes: WindowAttributes) -> Result<Window, OsError> {
let _span = tracing::debug_span!(
"winit::EventLoop::create_window",
window_attributes = ?window_attributes
)
.entered();
let window =
platform_impl::Window::new(&self.event_loop.window_target().p, window_attributes)?;
Ok(Window { window })
} }
/// Create custom cursor. /// Create custom cursor.
/// pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor {
/// ## Platform-specific self.event_loop.window_target().p.create_custom_cursor(custom_cursor)
///
/// **iOS / Android / Orbital:** Unsupported.
pub fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CustomCursor, RequestError> {
self.event_loop.window_target().create_custom_cursor(custom_cursor)
} }
} }
impl HasDisplayHandle for EventLoop { #[cfg(feature = "rwh_06")]
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> { impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
HasDisplayHandle::display_handle(self.event_loop.window_target().rwh_06_handle()) fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
rwh_06::HasDisplayHandle::display_handle(self.event_loop.window_target())
}
}
#[cfg(feature = "rwh_05")]
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
rwh_05::HasRawDisplayHandle::raw_display_handle(self.event_loop.window_target())
} }
} }
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
impl AsFd for EventLoop { impl<T> AsFd for EventLoop<T> {
/// 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.
@@ -296,7 +352,7 @@ impl AsFd for EventLoop {
} }
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
impl AsRawFd for EventLoop { impl<T> AsRawFd for EventLoop<T> {
/// 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.
@@ -309,45 +365,42 @@ impl AsRawFd for EventLoop {
} }
} }
pub trait ActiveEventLoop: AsAny { impl ActiveEventLoop {
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
fn create_proxy(&self) -> EventLoopProxy;
/// Create the window. /// Create the window.
/// ///
/// Possible causes of error include denied permission, incompatible system, and lack of memory. /// Possible causes of error include denied permission, incompatible system, and lack of memory.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **Web:** The window is created but not inserted into the Web page automatically. Please /// - **Web:** The window is created but not inserted into the web page automatically. Please
/// see the Web platform module for more information. /// see the web platform module for more information.
fn create_window( #[inline]
&self, pub fn create_window(&self, window_attributes: WindowAttributes) -> Result<Window, OsError> {
window_attributes: WindowAttributes, let _span = tracing::debug_span!(
) -> Result<Box<dyn Window>, RequestError>; "winit::ActiveEventLoop::create_window",
window_attributes = ?window_attributes
)
.entered();
let window = platform_impl::Window::new(&self.p, window_attributes)?;
Ok(Window { window })
}
/// Create custom cursor. /// Create custom cursor.
/// pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor {
/// ## Platform-specific let _span = tracing::debug_span!("winit::ActiveEventLoop::create_custom_cursor",).entered();
///
/// **iOS / Android / Orbital:** Unsupported. self.p.create_custom_cursor(custom_cursor)
fn create_custom_cursor( }
&self,
custom_cursor: CustomCursorSource,
) -> Result<CustomCursor, RequestError>;
/// Returns the list of all the monitors available on the system. /// Returns the list of all the monitors available on the system.
/// #[inline]
/// ## Platform-specific pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
/// let _span = tracing::debug_span!("winit::ActiveEventLoop::available_monitors",).entered();
/// **Web:** Only returns the current monitor without
#[cfg_attr( #[allow(clippy::useless_conversion)] // false positive on some platforms
any(web_platform, docsrs), self.p.available_monitors().into_iter().map(|inner| MonitorHandle { inner })
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]." }
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
fn available_monitors(&self) -> Box<dyn Iterator<Item = MonitorHandle>>;
/// Returns the primary monitor of the system. /// Returns the primary monitor of the system.
/// ///
@@ -355,14 +408,13 @@ pub trait ActiveEventLoop: AsAny {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **Wayland:** Always returns `None`. /// **Wayland / Web:** Always returns `None`.
/// - **Web:** Always returns `None` without #[inline]
#[cfg_attr( pub fn primary_monitor(&self) -> Option<MonitorHandle> {
any(web_platform, docsrs), let _span = tracing::debug_span!("winit::ActiveEventLoop::primary_monitor",).entered();
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)] self.p.primary_monitor().map(|inner| MonitorHandle { inner })
#[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")] }
fn primary_monitor(&self) -> Option<MonitorHandle>;
/// Change if or when [`DeviceEvent`]s are captured. /// Change if or when [`DeviceEvent`]s are captured.
/// ///
@@ -375,51 +427,70 @@ pub trait ActiveEventLoop: AsAny {
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported. /// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
/// ///
/// [`DeviceEvent`]: crate::event::DeviceEvent /// [`DeviceEvent`]: crate::event::DeviceEvent
fn listen_device_events(&self, allowed: DeviceEvents); pub fn listen_device_events(&self, allowed: DeviceEvents) {
let _span = tracing::debug_span!(
"winit::ActiveEventLoop::listen_device_events",
allowed = ?allowed
)
.entered();
/// Returns the current system theme. self.p.listen_device_events(allowed);
/// }
/// Returns `None` if it cannot be determined on the current platform.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
fn system_theme(&self) -> Option<Theme>;
/// Sets the [`ControlFlow`]. /// Sets the [`ControlFlow`].
fn set_control_flow(&self, control_flow: ControlFlow); pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.p.set_control_flow(control_flow)
}
/// Gets the current [`ControlFlow`]. /// Gets the current [`ControlFlow`].
fn control_flow(&self) -> ControlFlow; pub fn control_flow(&self) -> ControlFlow {
self.p.control_flow()
}
/// This exits the event loop. /// This exits the event loop.
/// ///
/// See [`exiting`][crate::application::ApplicationHandler::exiting]. /// See [`LoopExiting`][Event::LoopExiting].
fn exit(&self); pub fn exit(&self) {
let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered();
self.p.exit()
}
/// Returns if the [`EventLoop`] is about to stop. /// Returns if the [`EventLoop`] is about to stop.
/// ///
/// See [`exit()`][Self::exit]. /// See [`exit()`][Self::exit].
fn exiting(&self) -> bool; pub fn exiting(&self) -> bool {
self.p.exiting()
}
/// 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.
fn owned_display_handle(&self) -> OwnedDisplayHandle; pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle { platform: self.p.owned_display_handle() }
/// Get the raw-window-handle handle. }
fn rwh_06_handle(&self) -> &dyn HasDisplayHandle;
} }
impl HasDisplayHandle for dyn ActiveEventLoop + '_ { #[cfg(feature = "rwh_06")]
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> { impl rwh_06::HasDisplayHandle for ActiveEventLoop {
self.rwh_06_handle().display_handle() fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.p.raw_display_handle_rwh_06()?;
// SAFETY: The display will never be deallocated while the event loop is alive.
Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) })
}
}
#[cfg(feature = "rwh_05")]
unsafe impl rwh_05::HasRawDisplayHandle for ActiveEventLoop {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
self.p.raw_display_handle_rwh_05()
} }
} }
/// A proxy for the underlying display handle. /// A proxy for the underlying display handle.
/// ///
/// The purpose of this type is to provide a cheaply cloneable handle to the underlying /// The purpose of this type is to provide a cheaply clonable handle to the underlying
/// display handle. This is often used by graphics APIs to connect to the underlying APIs. /// display handle. This is often used by graphics APIs to connect to the underlying APIs.
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`] /// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`]
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as /// type. In contrast, this type involves no lifetimes and can be persisted for as long as
@@ -431,83 +502,87 @@ impl HasDisplayHandle for dyn ActiveEventLoop + '_ {
/// - A reference-counted pointer to the underlying type. /// - A reference-counted pointer to the underlying type.
#[derive(Clone)] #[derive(Clone)]
pub struct OwnedDisplayHandle { pub struct OwnedDisplayHandle {
pub(crate) handle: Arc<dyn HasDisplayHandle>, #[cfg_attr(not(any(feature = "rwh_05", feature = "rwh_06")), allow(dead_code))]
} platform: platform_impl::OwnedDisplayHandle,
impl OwnedDisplayHandle {
pub(crate) fn new(handle: Arc<dyn HasDisplayHandle>) -> Self {
Self { handle }
}
}
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.handle.display_handle()
}
} }
impl fmt::Debug for OwnedDisplayHandle { impl fmt::Debug for OwnedDisplayHandle {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive() f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive()
} }
} }
impl PartialEq for OwnedDisplayHandle { #[cfg(feature = "rwh_06")]
fn eq(&self, other: &Self) -> bool { impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
match (self.display_handle(), other.display_handle()) { #[inline]
(Ok(lhs), Ok(rhs)) => lhs == rhs, fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
_ => false, let raw = self.platform.raw_display_handle_rwh_06()?;
}
// SAFETY: The underlying display handle should be safe.
let handle = unsafe { rwh_06::DisplayHandle::borrow_raw(raw) };
Ok(handle)
} }
} }
impl Eq for OwnedDisplayHandle {} #[cfg(feature = "rwh_05")]
unsafe impl rwh_05::HasRawDisplayHandle for OwnedDisplayHandle {
pub(crate) trait EventLoopProxyProvider: Send + Sync { #[inline]
/// See [`EventLoopProxy::wake_up`] for details. fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
fn wake_up(&self); self.platform.raw_display_handle_rwh_05()
}
} }
/// Control the [`EventLoop`], possibly from a different thread, without referencing it directly. /// Used to send custom events to [`EventLoop`].
#[derive(Clone)] pub struct EventLoopProxy<T: 'static> {
pub struct EventLoopProxy { event_loop_proxy: platform_impl::EventLoopProxy<T>,
pub(crate) proxy: Arc<dyn EventLoopProxyProvider>,
} }
impl fmt::Debug for EventLoopProxy { impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self { event_loop_proxy: self.event_loop_proxy.clone() }
}
}
impl<T: 'static> EventLoopProxy<T> {
/// 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.debug_struct("EventLoopProxy").finish_non_exhaustive() f.pad("EventLoopProxy { .. }")
} }
} }
impl EventLoopProxy { /// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that
/// Wake up the [`EventLoop`], resulting in [`ApplicationHandler::proxy_wake_up()`] being /// no longer exists.
/// called. ///
/// /// Contains the original event given to [`EventLoopProxy::send_event`].
/// Calls to this method are coalesced into a single call to [`proxy_wake_up`], see the #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
/// documentation on that for details. pub struct EventLoopClosed<T>(pub T);
///
/// 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.proxy.wake_up();
}
pub(crate) fn new(proxy: Arc<dyn EventLoopProxyProvider>) -> Self { impl<T> fmt::Display for EventLoopClosed<T> {
Self { proxy } 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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DeviceEvents { pub enum DeviceEvents {
/// Report device events regardless of window focus. /// Report device events regardless of window focus.
Always, Always,
@@ -527,7 +602,7 @@ pub enum DeviceEvents {
/// containing [`AsyncRequestSerial`] and some closure associated with it. /// containing [`AsyncRequestSerial`] and some closure associated with it.
/// Then once event is arriving the working list is being traversed and a job /// Then once event is arriving the working list is being traversed and a job
/// executed and removed from the list. /// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AsyncRequestSerial { pub struct AsyncRequestSerial {
serial: usize, serial: usize,
} }
@@ -543,3 +618,23 @@ 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,8 +1,7 @@
use crate::platform_impl::PlatformIcon;
use std::error::Error; use std::error::Error;
use std::{fmt, io, mem}; use std::{fmt, io, mem};
use crate::platform_impl::PlatformIcon;
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Pixel { pub(crate) struct Pixel {
@@ -50,15 +49,15 @@ impl fmt::Display for BadIcon {
impl Error for BadIcon {} impl Error for BadIcon {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RgbaIcon { pub(crate) struct RgbaIcon {
pub(crate) rgba: Vec<u8>, pub(crate) rgba: Vec<u8>,
pub(crate) width: u32, pub(crate) width: u32,
pub(crate) height: u32, pub(crate) height: u32,
} }
/// For platforms which don't have window icons (e.g. Web) /// For platforms which don't have window icons (e.g. web)
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct NoIcon; pub(crate) struct NoIcon;
#[allow(dead_code)] // These are not used on every platform #[allow(dead_code)] // These are not used on every platform
@@ -94,7 +93,7 @@ mod constructors {
} }
/// An icon used for the window titlebar, taskbar, etc. /// An icon used for the window titlebar, taskbar, etc.
#[derive(Clone, Eq, Hash, PartialEq)] #[derive(Clone)]
pub struct Icon { pub struct Icon {
pub(crate) inner: PlatformIcon, pub(crate) inner: PlatformIcon,
} }

View File

@@ -84,7 +84,7 @@ pub use smol_str::SmolStr;
/// haven't mapped for you yet, this lets you use use [`KeyCode`] to: /// haven't mapped for you yet, this lets you use use [`KeyCode`] to:
/// ///
/// - Correctly match key press and release events. /// - Correctly match key press and release events.
/// - On non-Web platforms, support assigning keybinds to virtually any key through a UI. /// - On non-web platforms, support assigning keybinds to virtually any key through a UI.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NativeKeyCode { pub enum NativeKeyCode {
@@ -1568,15 +1568,10 @@ impl NamedKey {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// # #[cfg(web_platform)]
/// # wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
/// # #[cfg_attr(web_platform, wasm_bindgen_test::wasm_bindgen_test)]
/// # fn main() {
/// use winit::keyboard::NamedKey; /// use winit::keyboard::NamedKey;
/// ///
/// assert_eq!(NamedKey::Enter.to_text(), Some("\r")); /// assert_eq!(NamedKey::Enter.to_text(), Some("\r"));
/// assert_eq!(NamedKey::F20.to_text(), None); /// assert_eq!(NamedKey::F20.to_text(), None);
/// # }
/// ``` /// ```
pub fn to_text(&self) -> Option<&str> { pub fn to_text(&self) -> Option<&str> {
match self { match self {
@@ -1596,16 +1591,11 @@ impl Key {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// # #[cfg(web_platform)]
/// # wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
/// # #[cfg_attr(web_platform, wasm_bindgen_test::wasm_bindgen_test)]
/// # fn main() {
/// use winit::keyboard::{Key, NamedKey}; /// 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"));
/// assert_eq!(Key::Named(NamedKey::F20).to_text(), None); /// assert_eq!(Key::Named(NamedKey::F20).to_text(), None);
/// # }
/// ``` /// ```
pub fn to_text(&self) -> Option<&str> { pub fn to_text(&self) -> Option<&str> {
match self { match self {
@@ -1628,7 +1618,7 @@ impl Key {
/// ///
/// [`location`]: ../event/struct.KeyEvent.html#structfield.location /// [`location`]: ../event/struct.KeyEvent.html#structfield.location
/// [`KeyEvent`]: crate::event::KeyEvent /// [`KeyEvent`]: crate::event::KeyEvent
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
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.
@@ -1700,7 +1690,6 @@ bitflags! {
/// ///
/// Each flag represents a modifier and is set if this modifier is active. /// Each flag represents a modifier and is set if this modifier is active.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ModifiersState: u32 { pub struct ModifiersState: u32 {
/// The "shift" key. /// The "shift" key.
const SHIFT = 0b100; const SHIFT = 0b100;
@@ -1736,8 +1725,7 @@ impl ModifiersState {
} }
/// The state of the particular modifiers key. /// The state of the particular modifiers key.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ModifiersKeyState { pub enum ModifiersKeyState {
/// The particular key is pressed. /// The particular key is pressed.
Pressed, Pressed,
@@ -1756,7 +1744,6 @@ pub enum ModifiersKeyState {
// on macOS due to their AltGr/Option situation. // on macOS due to their AltGr/Option situation.
bitflags! { bitflags! {
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub(crate) struct ModifiersKeys: u8 { pub(crate) struct ModifiersKeys: u8 {
const LSHIFT = 0b0000_0001; const LSHIFT = 0b0000_0001;
const RSHIFT = 0b0000_0010; const RSHIFT = 0b0000_0010;
@@ -1768,3 +1755,50 @@ bitflags! {
const RSUPER = 0b1000_0000; const RSUPER = 0b1000_0000;
} }
} }
#[cfg(feature = "serde")]
mod modifiers_serde {
use super::ModifiersState;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Default, Serialize, Deserialize)]
#[serde(default)]
#[serde(rename = "ModifiersState")]
pub struct ModifiersStateSerialize {
pub shift_key: bool,
pub control_key: bool,
pub alt_key: bool,
pub super_key: bool,
}
impl Serialize for ModifiersState {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = ModifiersStateSerialize {
shift_key: self.shift_key(),
control_key: self.control_key(),
alt_key: self.alt_key(),
super_key: self.super_key(),
};
s.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ModifiersState {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let ModifiersStateSerialize { shift_key, control_key, alt_key, super_key } =
ModifiersStateSerialize::deserialize(deserializer)?;
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, shift_key);
m.set(ModifiersState::CONTROL, control_key);
m.set(ModifiersState::ALT, alt_key);
m.set(ModifiersState::SUPER, super_key);
Ok(m)
}
}
}

View File

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

View File

@@ -1,12 +1,20 @@
//! Types useful for interacting with a user's monitors. //! Types useful for interacting with a user's monitors.
use std::num::{NonZeroU16, NonZeroU32}; //!
//! If you want to get basic information about a monitor, you can use the
//! [`MonitorHandle`] type. This is retrieved from one of the following
//! methods, which return an iterator of [`MonitorHandle`]:
//! - [`ActiveEventLoop::available_monitors`][crate::event_loop::ActiveEventLoop::available_monitors].
//! - [`Window::available_monitors`][crate::window::Window::available_monitors].
use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform_impl; use crate::platform_impl;
/// A handle to a fullscreen video mode of a specific monitor. /// Deprecated! Use `VideoModeHandle` instead.
#[deprecated = "Renamed to `VideoModeHandle`"]
pub type VideoMode = VideoModeHandle;
/// Describes a fullscreen video mode of a monitor.
/// ///
/// This can be acquired with [`MonitorHandle::video_modes`]. /// Can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle { pub struct VideoModeHandle {
pub(crate) video_mode: platform_impl::VideoModeHandle, pub(crate) video_mode: platform_impl::VideoModeHandle,
@@ -40,10 +48,7 @@ impl Ord for VideoModeHandle {
} }
impl VideoModeHandle { impl VideoModeHandle {
/// Returns the resolution of this video mode. This **must not** be used to create your /// Returns the resolution of this video mode.
/// rendering surface. Use [`Window::surface_size()`] instead.
///
/// [`Window::surface_size()`]: crate::window::Window::surface_size
#[inline] #[inline]
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
self.video_mode.size() self.video_mode.size()
@@ -52,14 +57,19 @@ impl VideoModeHandle {
/// Returns the bit depth of this video mode, as in how many bits you have /// Returns the bit depth of this video mode, as in how many bits you have
/// available per color. This is generally 24 bits or 32 bits on modern /// available per color. This is generally 24 bits or 32 bits on modern
/// systems, depending on whether the alpha channel is counted or not. /// systems, depending on whether the alpha channel is counted or not.
///
/// ## Platform-specific
///
/// - **Wayland / Orbital:** Always returns 32.
/// - **iOS:** Always returns 32.
#[inline] #[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> { pub fn bit_depth(&self) -> u16 {
self.video_mode.bit_depth() self.video_mode.bit_depth()
} }
/// Returns the refresh rate of this video mode in mHz. /// Returns the refresh rate of this video mode in mHz.
#[inline] #[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> { pub fn refresh_rate_millihertz(&self) -> u32 {
self.video_mode.refresh_rate_millihertz() self.video_mode.refresh_rate_millihertz()
} }
@@ -75,93 +85,59 @@ impl std::fmt::Display for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"{}x{} {}{}", "{}x{} @ {} mHz ({} bpp)",
self.size().width, self.size().width,
self.size().height, self.size().height,
self.refresh_rate_millihertz().map(|rate| format!("@ {rate} mHz ")).unwrap_or_default(), self.refresh_rate_millihertz(),
self.bit_depth().map(|bit_depth| format!("({bit_depth} bpp)")).unwrap_or_default(), self.bit_depth()
) )
} }
} }
/// Handle to a monitor. /// Handle to a monitor.
/// ///
/// Allows you to retrieve basic information and metadata about a monitor. /// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
///
/// Can be used in [`Window`] creation to place the window on a specific
/// monitor.
///
/// This can be retrieved from one of the following methods, which return an
/// iterator of [`MonitorHandle`]s:
/// - [`ActiveEventLoop::available_monitors`](crate::event_loop::ActiveEventLoop::available_monitors).
/// - [`Window::available_monitors`](crate::window::Window::available_monitors).
///
/// ## Platform-specific
///
/// **Web:** A [`MonitorHandle`] created without
#[cfg_attr(
any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
/// will always represent the current monitor the browser window is in instead of a specific
/// monitor. See
#[cfg_attr(
any(web_platform, docsrs),
doc = "[`MonitorHandleExtWeb::is_detailed()`][crate::platform::web::MonitorHandleExtWeb::is_detailed]"
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "`MonitorHandleExtWeb::is_detailed()`")]
/// to check.
/// ///
/// [`Window`]: crate::window::Window /// [`Window`]: crate::window::Window
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct MonitorHandle { pub struct MonitorHandle {
pub(crate) inner: platform_impl::MonitorHandle, pub(crate) inner: platform_impl::MonitorHandle,
} }
impl std::fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
}
}
impl MonitorHandle { impl MonitorHandle {
/// Returns a human-readable name of the monitor. /// Returns a human-readable name of the monitor.
/// ///
/// Returns `None` if the monitor doesn't exist anymore. /// Returns `None` if the monitor doesn't exist anymore.
///
/// ## Platform-specific
///
/// **Web:** Always returns [`None`] without
#[cfg_attr(
any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
#[inline] #[inline]
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
self.inner.name() self.inner.name()
} }
/// Returns the top-left corner position of the monitor in desktop coordinates. /// Returns the monitor's resolution.
///
/// This position is in the same coordinate system as [`Window::outer_position`].
///
/// [`Window::outer_position`]: crate::window::Window::outer_position
///
/// ## Platform-specific
///
/// **Web:** Always returns [`None`] without
#[cfg_attr(
any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
#[inline] #[inline]
pub fn position(&self) -> Option<PhysicalPosition<i32>> { pub fn size(&self) -> PhysicalSize<u32> {
self.inner.size()
}
/// Returns the top-left corner position of the monitor relative to the larger full
/// screen area.
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
self.inner.position() self.inner.position()
} }
/// The monitor refresh rate used by the system.
///
/// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor
/// the window is on is removed.
///
/// When using exclusive fullscreen, the refresh rate of the [`VideoModeHandle`] that was
/// used to enter fullscreen should be used instead.
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
self.inner.refresh_rate_millihertz()
}
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical /// Returns the scale factor of the underlying monitor. To map logical pixels to physical
/// pixels and vice versa, use [`Window::scale_factor`]. /// pixels and vice versa, use [`Window::scale_factor`].
/// ///
@@ -172,27 +148,18 @@ impl MonitorHandle {
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable. /// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **Wayland:** May differ from [`Window::scale_factor`]. /// - **Wayland:** May differ from [`Window::scale_factor`].
/// - **Android:** Always returns 1.0. /// - **Android:** Always returns 1.0.
/// - **Web:** Always returns `0.0` without
#[cfg_attr(
any(web_platform, docsrs),
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")]
/// ///
#[rustfmt::skip]
/// [`Window::scale_factor`]: crate::window::Window::scale_factor /// [`Window::scale_factor`]: crate::window::Window::scale_factor
#[inline] #[inline]
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor() self.inner.scale_factor()
} }
/// Returns the currently active video mode of this monitor.
#[inline]
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
self.inner.current_video_mode().map(|video_mode| VideoModeHandle { video_mode })
}
/// Returns all fullscreen video modes supported by this monitor. /// Returns all fullscreen video modes supported by this monitor.
///
/// ## Platform-specific
///
/// - **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.video_modes().map(|video_mode| VideoModeHandle { video_mode }) self.inner.video_modes().map(|video_mode| VideoModeHandle { video_mode })

View File

@@ -62,7 +62,7 @@
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building //! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
//! with `cargo apk`, then the minimal changes would be: //! 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.30.5", //! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.4",
//! features = [ "android-native-activity" ] }` //! features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc //! 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 //! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
@@ -70,27 +70,18 @@
//! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your //! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your
//! event loop (as shown above). //! event loop (as shown above).
use self::activity::{AndroidApp, ConfigurationRef, Rect};
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder}; use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::window::{Window, WindowAttributes}; use crate::window::{Window, WindowAttributes};
/// Additional methods on [`EventLoop`] that are specific to Android. use self::activity::{AndroidApp, ConfigurationRef, Rect};
pub trait EventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
impl EventLoopExtAndroid for EventLoop { /// Additional methods on [`EventLoop`] that are specific to Android.
fn android_app(&self) -> &AndroidApp { pub trait EventLoopExtAndroid {}
&self.event_loop.android_app
} impl<T> EventLoopExtAndroid for EventLoop<T> {}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android. /// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid { pub trait ActiveEventLoopExtAndroid {}
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
/// Additional methods on [`Window`] that are specific to Android. /// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid { pub trait WindowExtAndroid {
@@ -99,25 +90,17 @@ pub trait WindowExtAndroid {
fn config(&self) -> ConfigurationRef; fn config(&self) -> ConfigurationRef;
} }
impl WindowExtAndroid for dyn Window + '_ { impl WindowExtAndroid for Window {
fn content_rect(&self) -> Rect { fn content_rect(&self) -> Rect {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); self.window.content_rect()
window.content_rect()
} }
fn config(&self) -> ConfigurationRef { fn config(&self) -> ConfigurationRef {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); self.window.config()
window.config()
} }
} }
impl ActiveEventLoopExtAndroid for dyn ActiveEventLoop + '_ { impl ActiveEventLoopExtAndroid for ActiveEventLoop {}
fn android_app(&self) -> &AndroidApp {
let event_loop =
self.as_any().downcast_ref::<crate::platform_impl::ActiveEventLoop>().unwrap();
&event_loop.app
}
}
/// Additional methods on [`WindowAttributes`] that are specific to Android. /// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowAttributesExtAndroid {} pub trait WindowAttributesExtAndroid {}
@@ -125,9 +108,9 @@ pub trait WindowAttributesExtAndroid {}
impl WindowAttributesExtAndroid for WindowAttributes {} impl WindowAttributesExtAndroid for WindowAttributes {}
pub trait EventLoopBuilderExtAndroid { pub trait EventLoopBuilderExtAndroid {
/// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop /// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
/// ///
/// This must be called on Android since the [`AndroidApp`] is not global state. /// This must be called on Android since the `AndroidApp` is not global state.
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self; fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
/// Calling this will mark the volume keys to be manually handled by the application /// Calling this will mark the volume keys to be manually handled by the application
@@ -136,7 +119,7 @@ pub trait EventLoopBuilderExtAndroid {
fn handle_volume_keys(&mut self) -> &mut Self; fn handle_volume_keys(&mut self) -> &mut Self;
} }
impl EventLoopBuilderExtAndroid for EventLoopBuilder { impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
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
@@ -164,7 +147,7 @@ impl EventLoopBuilderExtAndroid for EventLoopBuilder {
/// depending on the `android_activity` crate, and instead consume the API that /// depending on the `android_activity` crate, and instead consume the API that
/// is re-exported by Winit. /// is re-exported by Winit.
/// ///
/// For compatibility applications should then import the [`AndroidApp`] type for /// For compatibility applications should then import the `AndroidApp` type for
/// their `android_main(app: AndroidApp)` function like: /// their `android_main(app: AndroidApp)` function like:
/// ```rust /// ```rust
/// #[cfg(target_os = "android")] /// #[cfg(target_os = "android")]

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,24 @@
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::error::EventLoopError; use crate::error::EventLoopError;
use crate::event_loop::EventLoop; use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
#[cfg(doc)] #[cfg(doc)]
use crate::{ use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
event_loop::ActiveEventLoop, 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`) /// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
@@ -31,13 +42,9 @@ pub trait EventLoopExtRunOnDemand {
/// # Caveats /// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to return /// - This extension isn't available on all platforms, since it's not always possible to return
/// to the caller (specifically this is impossible on iOS and Web - though with the Web /// to the caller (specifically this is impossible on iOS and Web - though with the Web
/// backend it is possible to use /// backend it is possible to use `EventLoopExtWebSys::spawn()`
#[cfg_attr( #[cfg_attr(not(web_platform), doc = "[^1]")]
any(web_platform, docsrs), /// more than once instead).
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^1] more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop. /// - No [`Window`] state can be carried between separate runs of the event loop.
/// ///
/// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you /// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
@@ -55,17 +62,38 @@ pub trait EventLoopExtRunOnDemand {
/// block the browser and there is nothing that can be polled to ask for new events. Events /// block the browser and there is nothing that can be polled to ask for new events. Events
/// are delivered via callbacks based on an event loop that is internal to the browser itself. /// are delivered via callbacks based on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS. /// - **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.")]
/// [^1]: `spawn_app()` is only available on the Web platforms. #[rustfmt::skip]
/// ///
/// [`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>(&mut self, app: A) -> Result<(), EventLoopError>; fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
#[allow(deprecated)]
self.run_on_demand(|event, event_loop| {
event_loop::dispatch_event_for_app(app, event_loop, event)
})
}
} }
impl EventLoopExtRunOnDemand for EventLoop { impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError> { type UserEvent = T;
self.event_loop.run_app_on_demand(app)
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.window_target().clear_exit();
self.event_loop.run_on_demand(event_handler)
}
}
impl ActiveEventLoop {
/// Clear exit status.
pub(crate) fn clear_exit(&self) {
self.p.clear_exit()
} }
} }

View File

@@ -23,10 +23,8 @@
use std::env; use std::env;
use crate::error::{NotSupportedError, RequestError}; use crate::error::NotSupportedError;
use crate::event_loop::{ActiveEventLoop, AsyncRequestSerial}; use crate::event_loop::{ActiveEventLoop, AsyncRequestSerial};
#[cfg(wayland_platform)]
use crate::platform::wayland::ActiveEventLoopExtWayland;
use crate::window::{ActivationToken, Window, WindowAttributes}; use crate::window::{ActivationToken, Window, WindowAttributes};
/// The variable which is used mostly on X11. /// The variable which is used mostly on X11.
@@ -46,7 +44,7 @@ pub trait WindowExtStartupNotify {
/// Request a new activation token. /// Request a new activation token.
/// ///
/// The token will be delivered inside /// The token will be delivered inside
fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError>; fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError>;
} }
pub trait WindowAttributesExtStartupNotify { pub trait WindowAttributesExtStartupNotify {
@@ -57,37 +55,22 @@ pub trait WindowAttributesExtStartupNotify {
fn with_activation_token(self, token: ActivationToken) -> Self; fn with_activation_token(self, token: ActivationToken) -> Self;
} }
impl EventLoopExtStartupNotify for dyn ActiveEventLoop + '_ { impl EventLoopExtStartupNotify for ActiveEventLoop {
fn read_token_from_env(&self) -> Option<ActivationToken> { fn read_token_from_env(&self) -> Option<ActivationToken> {
#[cfg(x11_platform)] match self.p {
let _is_wayland = false; #[cfg(wayland_platform)]
#[cfg(wayland_platform)] crate::platform_impl::ActiveEventLoop::Wayland(_) => env::var(WAYLAND_VAR),
let _is_wayland = self.is_wayland(); #[cfg(x11_platform)]
crate::platform_impl::ActiveEventLoop::X(_) => env::var(X11_VAR),
if _is_wayland {
env::var(WAYLAND_VAR).ok().map(ActivationToken::_new)
} else {
env::var(X11_VAR).ok().map(ActivationToken::_new)
} }
.ok()
.map(ActivationToken::_new)
} }
} }
impl WindowExtStartupNotify for dyn Window + '_ { impl WindowExtStartupNotify for Window {
fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> { fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
#[cfg(wayland_platform)] self.window.request_activation_token()
if let Some(window) = self.as_any().downcast_ref::<crate::platform_impl::wayland::Window>()
{
return window.request_activation_token();
}
#[cfg(x11_platform)]
if let Some(window) =
self.as_any().downcast_ref::<crate::platform_impl::x11::window::Window>()
{
return window.request_activation_token();
}
Err(NotSupportedError::new("startup notify is not supported").into())
} }
} }

View File

@@ -15,8 +15,9 @@
//! * `wayland-csd-adwaita-notitle`. //! * `wayland-csd-adwaita-notitle`.
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder}; use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle; use crate::monitor::MonitorHandle;
use crate::window::{Window, WindowAttributes};
pub use crate::window::Theme; pub use crate::window::Theme;
use crate::window::{Window as CoreWindow, WindowAttributes};
/// Additional methods on [`ActiveEventLoop`] that are specific to Wayland. /// Additional methods on [`ActiveEventLoop`] that are specific to Wayland.
pub trait ActiveEventLoopExtWayland { pub trait ActiveEventLoopExtWayland {
@@ -24,10 +25,10 @@ pub trait ActiveEventLoopExtWayland {
fn is_wayland(&self) -> bool; fn is_wayland(&self) -> bool;
} }
impl ActiveEventLoopExtWayland for dyn ActiveEventLoop + '_ { impl ActiveEventLoopExtWayland for ActiveEventLoop {
#[inline] #[inline]
fn is_wayland(&self) -> bool { fn is_wayland(&self) -> bool {
self.as_any().downcast_ref::<crate::platform_impl::wayland::ActiveEventLoop>().is_some() self.p.is_wayland()
} }
} }
@@ -37,7 +38,7 @@ pub trait EventLoopExtWayland {
fn is_wayland(&self) -> bool; fn is_wayland(&self) -> bool;
} }
impl EventLoopExtWayland for EventLoop { impl<T: 'static> EventLoopExtWayland for EventLoop<T> {
#[inline] #[inline]
fn is_wayland(&self) -> bool { fn is_wayland(&self) -> bool {
self.event_loop.is_wayland() self.event_loop.is_wayland()
@@ -56,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 EventLoopBuilderExtWayland for EventLoopBuilder { impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
#[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);
@@ -71,11 +72,9 @@ impl EventLoopBuilderExtWayland for EventLoopBuilder {
} }
/// Additional methods on [`Window`] that are specific to Wayland. /// Additional methods on [`Window`] that are specific to Wayland.
///
/// [`Window`]: crate::window::Window
pub trait WindowExtWayland {} pub trait WindowExtWayland {}
impl WindowExtWayland for dyn CoreWindow + '_ {} impl WindowExtWayland for Window {}
/// Additional methods on [`WindowAttributes`] that are specific to Wayland. /// Additional methods on [`WindowAttributes`] that are specific to Wayland.
pub trait WindowAttributesExtWayland { pub trait WindowAttributesExtWayland {

View File

@@ -1,23 +1,24 @@
//! # Web //! # Web
//! //!
//! Winit supports running in Browsers by compiling to WebAssembly with //! The officially supported browsers are Chrome, Firefox and Safari 13.1+,
//! [`wasm-bindgen`][wasm_bindgen]. For information on using Rust on WebAssembly, check out the //! though forks of these should work fine.
//! [Rust and WebAssembly book].
//! //!
//! The officially supported browsers are Chrome, Firefox and Safari 13.1+, though forks of these //! Winit supports compiling to the `wasm32-unknown-unknown` target with
//! should work fine. //! `web-sys`.
//! //!
//! On the Web platform, a Winit [`Window`] is backed by a [`HTMLCanvasElement`][canvas]. Winit will //! On the web platform, a Winit window is backed by a `<canvas>` element. You
//! create that canvas for you or you can [provide your own][with_canvas]. Then you can either let //! can either [provide Winit with a `<canvas>` element][with_canvas], or
//! Winit [insert it into the DOM for you][insert], or [retrieve the canvas][get] and insert it //! [let Winit create a `<canvas>` element which you can then retrieve][get]
//! yourself. //! and insert it into the DOM yourself.
//! //!
//! [canvas]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement //! Currently, there is no example code using Winit on Web, see [#3473]. For
//! [with_canvas]: WindowAttributesExtWeb::with_canvas //! information on using Rust on WebAssembly, check out the [Rust and
//! [get]: WindowExtWeb::canvas //! WebAssembly book].
//! [insert]: WindowAttributesExtWeb::with_append //!
#![cfg_attr(not(web_platform), doc = "[wasm_bindgen]: https://docs.rs/wasm-bindgen")] //! [with_canvas]: WindowAttributesExtWebSys::with_canvas
//! [Rust and WebAssembly book]: https://rustwasm.github.io/book //! [get]: WindowExtWebSys::canvas
//! [#3473]: https://github.com/rust-windowing/winit/issues/3473
//! [Rust and WebAssembly book]: https://rustwasm.github.io/book/
//! //!
//! ## CSS properties //! ## CSS properties
//! //!
@@ -27,21 +28,21 @@
//! - [`padding`](https://developer.mozilla.org/en-US/docs/Web/CSS/padding) //! - [`padding`](https://developer.mozilla.org/en-US/docs/Web/CSS/padding)
//! //!
//! The following APIs can't take them into account and will therefore provide inaccurate results: //! The following APIs can't take them into account and will therefore provide inaccurate results:
//! - [`WindowEvent::SurfaceResized`] and [`Window::(set_)surface_size()`] //! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`]
//! - [`WindowEvent::Occluded`] //! - [`WindowEvent::Occluded`]
//! - [`WindowEvent::PointerMoved`], [`WindowEvent::PointerEntered`] and //! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`], and
//! [`WindowEvent::PointerLeft`]. //! [`WindowEvent::Touch`].
//! - [`Window::set_outer_position()`] //! - [`Window::set_outer_position()`]
//! //!
//! [`WindowEvent::SurfaceResized`]: crate::event::WindowEvent::SurfaceResized //! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized
//! [`Window::(set_)surface_size()`]: crate::window::Window::surface_size //! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size
//! [`WindowEvent::Occluded`]: crate::event::WindowEvent::Occluded //! [`WindowEvent::Occluded`]: crate::event::WindowEvent::Occluded
//! [`WindowEvent::PointerMoved`]: crate::event::WindowEvent::PointerMoved //! [`WindowEvent::CursorMoved`]: crate::event::WindowEvent::CursorMoved
//! [`WindowEvent::PointerEntered`]: crate::event::WindowEvent::PointerEntered //! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered
//! [`WindowEvent::PointerLeft`]: crate::event::WindowEvent::PointerLeft //! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft
//! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch
//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position //! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position
use std::cell::Ref;
use std::error::Error; use std::error::Error;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::future::Future; use std::future::Future;
@@ -49,38 +50,30 @@ use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::Duration; use std::time::Duration;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(web_platform)] #[cfg(web_platform)]
use web_sys::HtmlCanvasElement; use web_sys::HtmlCanvasElement;
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::cursor::CustomCursorSource; use crate::cursor::CustomCursorSource;
use crate::error::NotSupportedError; use crate::event::Event;
use crate::event_loop::{ActiveEventLoop, EventLoop}; use crate::event_loop::{self, ActiveEventLoop, EventLoop};
use crate::monitor::MonitorHandle;
use crate::platform_impl::PlatformCustomCursorSource;
#[cfg(web_platform)] #[cfg(web_platform)]
use crate::platform_impl::{ use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture;
CustomCursorFuture as PlatformCustomCursorFuture, use crate::platform_impl::PlatformCustomCursorSource;
HasMonitorPermissionFuture as PlatformHasMonitorPermissionFuture,
MonitorPermissionFuture as PlatformMonitorPermissionFuture,
OrientationLockFuture as PlatformOrientationLockFuture,
};
use crate::window::{CustomCursor, Window, WindowAttributes}; use crate::window::{CustomCursor, Window, WindowAttributes};
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
#[doc(hidden)] #[doc(hidden)]
pub struct HtmlCanvasElement; pub struct HtmlCanvasElement;
pub trait WindowExtWeb { pub trait WindowExtWebSys {
/// Only returns the canvas if called from inside the window context (the /// Only returns the canvas if called from inside the window context (the
/// main thread). /// main thread).
fn canvas(&self) -> Option<Ref<'_, HtmlCanvasElement>>; fn canvas(&self) -> Option<HtmlCanvasElement>;
/// Returns [`true`] if calling `event.preventDefault()` is enabled. /// Returns [`true`] if calling `event.preventDefault()` is enabled.
/// ///
/// See [`WindowExtWeb::set_prevent_default()`] for more details. /// See [`Window::set_prevent_default()`] for more details.
fn prevent_default(&self) -> bool; fn prevent_default(&self) -> bool;
/// Sets whether `event.preventDefault()` should be called on events on the /// Sets whether `event.preventDefault()` should be called on events on the
@@ -92,52 +85,28 @@ pub trait WindowExtWeb {
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser /// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
/// context menu with Shift+Rightclick. /// context menu with Shift+Rightclick.
fn set_prevent_default(&self, prevent_default: bool); fn set_prevent_default(&self, prevent_default: bool);
/// Returns whether using [`CursorGrabMode::Locked`] returns raw, un-accelerated mouse input.
///
/// This is the same as [`ActiveEventLoopExtWeb::is_cursor_lock_raw()`], and is provided for
/// convenience.
///
/// [`CursorGrabMode::Locked`]: crate::window::CursorGrabMode::Locked
fn is_cursor_lock_raw(&self) -> bool;
} }
impl WindowExtWeb for dyn Window + '_ { impl WindowExtWebSys for Window {
#[inline] #[inline]
fn canvas(&self) -> Option<Ref<'_, HtmlCanvasElement>> { fn canvas(&self) -> Option<HtmlCanvasElement> {
self.as_any() self.window.canvas()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.canvas()
} }
fn prevent_default(&self) -> bool { fn prevent_default(&self) -> bool {
self.as_any() self.window.prevent_default()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.prevent_default()
} }
fn set_prevent_default(&self, prevent_default: bool) { fn set_prevent_default(&self, prevent_default: bool) {
self.as_any() self.window.set_prevent_default(prevent_default)
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.set_prevent_default(prevent_default)
}
fn is_cursor_lock_raw(&self) -> bool {
self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.is_cursor_lock_raw()
} }
} }
pub trait WindowAttributesExtWeb { pub trait WindowAttributesExtWebSys {
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`], /// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`],
/// [`WindowAttributes::default()`] will create one. /// [`WindowAttributes::default()`] will create one.
/// ///
/// 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(not(web_platform), doc = "", doc = "[`HtmlCanvasElement`]: #only-available-on-wasm")] #[cfg_attr(not(web_platform), doc = "", doc = "[`HtmlCanvasElement`]: #only-available-on-wasm")]
@@ -146,7 +115,7 @@ pub trait WindowAttributesExtWeb {
/// Sets whether `event.preventDefault()` should be called on events on the /// Sets whether `event.preventDefault()` should be called on events on the
/// canvas that have side effects. /// canvas that have side effects.
/// ///
/// See [`WindowExtWeb::set_prevent_default()`] for more details. /// See [`Window::set_prevent_default()`] for more details.
/// ///
/// Enabled by default. /// Enabled by default.
fn with_prevent_default(self, prevent_default: bool) -> Self; fn with_prevent_default(self, prevent_default: bool) -> Self;
@@ -157,13 +126,13 @@ pub trait WindowAttributesExtWeb {
/// Enabled by default. /// Enabled by default.
fn with_focusable(self, focusable: bool) -> Self; fn with_focusable(self, focusable: bool) -> Self;
/// On window creation, append the canvas element to the Web page if it isn't already. /// On window creation, append the canvas element to the web page if it isn't already.
/// ///
/// Disabled by default. /// Disabled by default.
fn with_append(self, append: bool) -> Self; fn with_append(self, append: bool) -> Self;
} }
impl WindowAttributesExtWeb for WindowAttributes { impl WindowAttributesExtWebSys for WindowAttributes {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self { fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.set_canvas(canvas); self.platform_specific.set_canvas(canvas);
self self
@@ -185,8 +154,11 @@ impl WindowAttributesExtWeb for WindowAttributes {
} }
} }
/// Additional methods on `EventLoop` that are specific to the Web. /// Additional methods on `EventLoop` that are specific to the web.
pub trait EventLoopExtWeb { 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
@@ -208,8 +180,16 @@ pub trait EventLoopExtWeb {
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 + 'static>(self, app: A); fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A);
/// See [`spawn_app`].
///
/// [`spawn_app`]: Self::spawn_app
#[deprecated = "use EventLoopExtWebSys::spawn_app"]
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
/// Sets the strategy for [`ControlFlow::Poll`]. /// Sets the strategy for [`ControlFlow::Poll`].
/// ///
@@ -238,33 +218,22 @@ pub trait EventLoopExtWeb {
/// ///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn wait_until_strategy(&self) -> WaitUntilStrategy; fn wait_until_strategy(&self) -> WaitUntilStrategy;
/// Returns if the users device has multiple screens. Useful to check before prompting the user
/// with [`EventLoopExtWeb::request_detailed_monitor_permission()`].
///
/// Browsers might always return [`false`] to reduce fingerprinting.
fn has_multiple_screens(&self) -> Result<bool, NotSupportedError>;
/// Prompts the user for permission to query detailed information about available monitors. The
/// returned [`MonitorPermissionFuture`] can be dropped without aborting the request.
///
/// Check [`EventLoopExtWeb::has_multiple_screens()`] before unnecessarily prompting the user
/// for such permissions.
///
/// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New
/// [`MonitorHandle`]s have to be created instead.
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture;
/// Returns whether the user has given permission to access detailed monitor information.
///
/// [`MonitorHandle`]s don't automatically make use of detailed monitor information after
/// permission is granted. New [`MonitorHandle`]s have to be created instead.
fn has_detailed_monitor_permission(&self) -> HasMonitorPermissionFuture;
} }
impl EventLoopExtWeb for EventLoop { impl<T> EventLoopExtWebSys for EventLoop<T> {
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) { type UserEvent = T;
self.event_loop.spawn_app(app);
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, mut app: A) {
self.event_loop.spawn(move |event, event_loop| {
event_loop::dispatch_event_for_app(&mut app, event_loop, event)
});
}
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.spawn(event_handler)
} }
fn set_poll_strategy(&self, strategy: PollStrategy) { fn set_poll_strategy(&self, strategy: PollStrategy) {
@@ -282,21 +251,9 @@ impl EventLoopExtWeb for EventLoop {
fn wait_until_strategy(&self) -> WaitUntilStrategy { fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.event_loop.wait_until_strategy() self.event_loop.wait_until_strategy()
} }
fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> {
self.event_loop.has_multiple_screens()
}
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture {
MonitorPermissionFuture(self.event_loop.request_detailed_monitor_permission())
}
fn has_detailed_monitor_permission(&self) -> HasMonitorPermissionFuture {
HasMonitorPermissionFuture(self.event_loop.has_detailed_monitor_permission())
}
} }
pub trait ActiveEventLoopExtWeb { pub trait ActiveEventLoopExtWebSys {
/// Sets the strategy for [`ControlFlow::Poll`]. /// Sets the strategy for [`ControlFlow::Poll`].
/// ///
/// See [`PollStrategy`]. /// See [`PollStrategy`].
@@ -328,121 +285,37 @@ pub trait ActiveEventLoopExtWeb {
/// 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;
/// Returns whether using [`CursorGrabMode::Locked`] returns raw, un-accelerated mouse input.
///
/// [`CursorGrabMode::Locked`]: crate::window::CursorGrabMode::Locked
fn is_cursor_lock_raw(&self) -> bool;
/// Returns if the users device has multiple screens. Useful to check before prompting the user
/// with [`EventLoopExtWeb::request_detailed_monitor_permission()`].
///
/// Browsers might always return [`false`] to reduce fingerprinting.
fn has_multiple_screens(&self) -> Result<bool, NotSupportedError>;
/// Prompts the user for permission to query detailed information about available monitors. The
/// returned [`MonitorPermissionFuture`] can be dropped without aborting the request.
///
/// Check [`EventLoopExtWeb::has_multiple_screens()`] before unnecessarily prompting the user
/// for such permissions.
///
/// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New
/// [`MonitorHandle`]s have to be created instead.
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture;
/// Returns whether the user has given permission to access detailed monitor information.
///
/// [`MonitorHandle`]s don't automatically make use of detailed monitor information after
/// permission is granted. New [`MonitorHandle`]s have to be created instead.
fn has_detailed_monitor_permission(&self) -> bool;
} }
impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ { impl ActiveEventLoopExtWebSys for ActiveEventLoop {
#[inline] #[inline]
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture { fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture {
let event_loop = self self.p.create_custom_cursor_async(source)
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.create_custom_cursor_async(source)
} }
#[inline] #[inline]
fn set_poll_strategy(&self, strategy: PollStrategy) { fn set_poll_strategy(&self, strategy: PollStrategy) {
let event_loop = self self.p.set_poll_strategy(strategy);
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.set_poll_strategy(strategy);
} }
#[inline] #[inline]
fn poll_strategy(&self) -> PollStrategy { fn poll_strategy(&self) -> PollStrategy {
let event_loop = self self.p.poll_strategy()
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.poll_strategy()
} }
#[inline] #[inline]
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
let event_loop = self self.p.set_wait_until_strategy(strategy);
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.set_wait_until_strategy(strategy);
} }
#[inline] #[inline]
fn wait_until_strategy(&self) -> WaitUntilStrategy { fn wait_until_strategy(&self) -> WaitUntilStrategy {
let event_loop = self self.p.wait_until_strategy()
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.wait_until_strategy()
}
#[inline]
fn is_cursor_lock_raw(&self) -> bool {
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.is_cursor_lock_raw()
}
#[inline]
fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> {
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.has_multiple_screens()
}
#[inline]
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture {
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
MonitorPermissionFuture(event_loop.request_detailed_monitor_permission())
}
#[inline]
fn has_detailed_monitor_permission(&self) -> bool {
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.has_detailed_monitor_permission()
} }
} }
/// Strategy used for [`ControlFlow::Poll`][crate::event_loop::ControlFlow::Poll]. /// Strategy used for [`ControlFlow::Poll`][crate::event_loop::ControlFlow::Poll].
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PollStrategy { pub enum PollStrategy {
/// Uses [`Window.requestIdleCallback()`] to queue the next event loop. If not available /// Uses [`Window.requestIdleCallback()`] to queue the next event loop. If not available
/// this will fallback to [`setTimeout()`]. /// this will fallback to [`setTimeout()`].
@@ -468,8 +341,7 @@ pub enum PollStrategy {
} }
/// Strategy used for [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil]. /// Strategy used for [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil].
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum WaitUntilStrategy { pub enum WaitUntilStrategy {
/// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available /// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available
/// this will fallback to [`setTimeout()`]. /// this will fallback to [`setTimeout()`].
@@ -491,7 +363,7 @@ pub enum WaitUntilStrategy {
Worker, Worker,
} }
pub trait CustomCursorExtWeb { 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;
@@ -510,7 +382,7 @@ pub trait CustomCursorExtWeb {
) -> Result<CustomCursorSource, BadAnimation>; ) -> Result<CustomCursorSource, BadAnimation>;
} }
impl CustomCursorExtWeb for CustomCursor { impl CustomCursorExtWebSys for CustomCursor {
fn is_animation(&self) -> bool { fn is_animation(&self) -> bool {
self.inner.animation self.inner.animation
} }
@@ -538,8 +410,7 @@ impl CustomCursorExtWeb for CustomCursor {
} }
/// An error produced when using [`CustomCursor::from_animation`] with invalid arguments. /// An error produced when using [`CustomCursor::from_animation`] with invalid arguments.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum BadAnimation { pub enum BadAnimation {
/// Produced when no cursors were supplied. /// Produced when no cursors were supplied.
Empty, Empty,
@@ -551,7 +422,7 @@ impl fmt::Display for BadAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Empty => write!(f, "No cursors supplied"), Self::Empty => write!(f, "No cursors supplied"),
Self::Animation => write!(f, "A supplied cursor is an animation"), Self::Animation => write!(f, "A supplied cursor is an animtion"),
} }
} }
} }
@@ -572,11 +443,11 @@ impl Future for CustomCursorFuture {
} }
} }
#[derive(Clone, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CustomCursorError { pub enum CustomCursorError {
Blob, Blob,
Decode(String), Decode(String),
Animation,
} }
impl Display for CustomCursorError { impl Display for CustomCursorError {
@@ -584,198 +455,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!(f, "found `CustomCursor` that is an animation when building an animation")
}
impl Error for CustomCursorError {}
#[cfg(not(web_platform))]
struct PlatformMonitorPermissionFuture;
/// Can be dropped without aborting the request for detailed monitor permissions.
#[derive(Debug)]
pub struct MonitorPermissionFuture(pub(crate) PlatformMonitorPermissionFuture);
impl Future for MonitorPermissionFuture {
type Output = Result<(), MonitorPermissionError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum MonitorPermissionError {
/// User has explicitly denied permission to query detailed monitor information.
Denied,
/// User has not decided to give permission to query detailed monitor information.
Prompt,
/// Browser does not support detailed monitor information.
Unsupported,
}
impl Display for MonitorPermissionError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
MonitorPermissionError::Denied => write!(
f,
"User has explicitly denied permission to query detailed monitor information"
),
MonitorPermissionError::Prompt => write!(
f,
"User has not decided to give permission to query detailed monitor information"
),
MonitorPermissionError::Unsupported => {
write!(f, "Browser does not support detailed monitor information")
}, },
} }
} }
} }
impl Error for MonitorPermissionError {}
#[cfg(not(web_platform))]
struct PlatformHasMonitorPermissionFuture;
#[derive(Debug)]
pub struct HasMonitorPermissionFuture(PlatformHasMonitorPermissionFuture);
impl Future for HasMonitorPermissionFuture {
type Output = bool;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx)
}
}
/// Additional methods on [`MonitorHandle`] that are specific to the Web.
pub trait MonitorHandleExtWeb {
/// Returns whether the screen is internal to the device or external.
///
/// External devices are generally manufactured separately from the device they are attached to
/// and can be connected and disconnected as needed, whereas internal screens are part of
/// the device and not intended to be disconnected.
fn is_internal(&self) -> Option<bool>;
/// Returns screen orientation data for this monitor.
fn orientation(&self) -> OrientationData;
/// Lock the screen orientation. The returned [`OrientationLockFuture`] can be dropped without
/// aborting the request.
///
/// Will fail if another locking call is in progress.
fn request_lock(&self, orientation: OrientationLock) -> OrientationLockFuture;
/// Unlock the screen orientation.
///
/// Will fail if a locking call is in progress.
fn unlock(&self) -> Result<(), OrientationLockError>;
/// Returns whether this [`MonitorHandle`] was created using detailed monitor permissions. If
/// [`false`] will always represent the current monitor the browser window is in instead of a
/// specific monitor.
///
/// See [`ActiveEventLoopExtWeb::request_detailed_monitor_permission()`].
fn is_detailed(&self) -> bool;
}
impl MonitorHandleExtWeb for MonitorHandle {
fn is_internal(&self) -> Option<bool> {
self.inner.is_internal()
}
fn orientation(&self) -> OrientationData {
self.inner.orientation()
}
fn request_lock(&self, orientation_lock: OrientationLock) -> OrientationLockFuture {
OrientationLockFuture(self.inner.request_lock(orientation_lock))
}
fn unlock(&self) -> Result<(), OrientationLockError> {
self.inner.unlock()
}
fn is_detailed(&self) -> bool {
self.inner.is_detailed()
}
}
/// Screen orientation data.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct OrientationData {
/// The orientation.
pub orientation: Orientation,
/// [`true`] if the [`orientation`](Self::orientation) is flipped upside down.
pub flipped: bool,
/// The most natural orientation for the screen. Computer monitors are commonly naturally
/// landscape mode, while mobile phones are commonly naturally portrait mode.
pub natural: Orientation,
}
/// Screen orientation.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Orientation {
/// The screen's aspect ratio has a width greater than the height.
Landscape,
/// The screen's aspect ratio has a height greater than the width.
Portrait,
}
/// Screen orientation lock options. Reoresents which orientations a user can use.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum OrientationLock {
/// User is free to use any orientation.
Any,
/// User is locked to the most upright natural orientation for the screen. Computer monitors
/// are commonly naturally landscape mode, while mobile phones are commonly
/// naturally portrait mode.
Natural,
/// User is locked to landscape mode.
Landscape {
/// - [`None`]: User is locked to both upright or upside down landscape mode.
/// - [`true`]: User is locked to upright landscape mode.
/// - [`false`]: User is locked to upside down landscape mode.
flipped: Option<bool>,
},
/// User is locked to portrait mode.
Portrait {
/// - [`None`]: User is locked to both upright or upside down portrait mode.
/// - [`true`]: User is locked to upright portrait mode.
/// - [`false`]: User is locked to upside down portrait mode.
flipped: Option<bool>,
},
}
#[cfg(not(web_platform))]
struct PlatformOrientationLockFuture;
/// Can be dropped without aborting the request to lock the screen.
#[derive(Debug)]
pub struct OrientationLockFuture(PlatformOrientationLockFuture);
impl Future for OrientationLockFuture {
type Output = Result<(), OrientationLockError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum OrientationLockError {
Unsupported,
Busy,
}
impl Display for OrientationLockError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Unsupported => write!(f, "Locking the screen orientation is not supported"),
Self::Busy => write!(f, "Another locking call is in progress"),
}
}
}
impl Error for OrientationLockError {}

View File

@@ -4,14 +4,8 @@
//! tested regularly. //! tested regularly.
use std::borrow::Borrow; use std::borrow::Borrow;
use std::ffi::c_void; use std::ffi::c_void;
use std::ops::Deref;
use std::path::Path; use std::path::Path;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(windows_platform)]
use windows_sys::Win32::Foundation::HANDLE;
use crate::dpi::PhysicalSize; use crate::dpi::PhysicalSize;
use crate::event::DeviceId; use crate::event::DeviceId;
use crate::event_loop::EventLoopBuilder; use crate::event_loop::EventLoopBuilder;
@@ -31,7 +25,6 @@ pub type HMONITOR = isize;
/// ///
/// [`DWM_SYSTEMBACKDROP_TYPE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type /// [`DWM_SYSTEMBACKDROP_TYPE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum BackdropType { pub enum BackdropType {
/// Corresponds to `DWMSBT_AUTO`. /// Corresponds to `DWMSBT_AUTO`.
/// ///
@@ -61,7 +54,6 @@ pub enum BackdropType {
/// Describes a color used by Windows /// Describes a color used by Windows
#[repr(transparent)] #[repr(transparent)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Color(u32); pub struct Color(u32);
impl Color { impl Color {
@@ -90,7 +82,6 @@ impl Default for Color {
/// [`DWM_WINDOW_CORNER_PREFERENCE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference /// [`DWM_WINDOW_CORNER_PREFERENCE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
#[repr(i32)] #[repr(i32)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CornerPreference { pub enum CornerPreference {
/// Corresponds to `DWMWCP_DEFAULT`. /// Corresponds to `DWMWCP_DEFAULT`.
/// ///
@@ -117,25 +108,51 @@ pub enum CornerPreference {
/// A wrapper around a [`Window`] that ignores thread-specific window handle limitations. /// A wrapper around a [`Window`] that ignores thread-specific window handle limitations.
/// ///
/// See [`WindowBorrowExtWindows::any_thread`] for more information. /// See [`WindowBorrowExtWindows::any_thread`] for more information.
#[derive(Clone, Debug)] #[derive(Debug)]
pub struct AnyThread<W: Window>(W); pub struct AnyThread<W>(W);
impl<W: Window> AnyThread<W> { impl<W: Borrow<Window>> AnyThread<W> {
/// Get a reference to the inner window. /// Get a reference to the inner window.
#[inline] #[inline]
pub fn get_ref(&self) -> &dyn Window { pub fn get_ref(&self) -> &Window {
self.0.borrow()
}
/// Get a reference to the inner object.
#[inline]
pub fn inner(&self) -> &W {
&self.0 &self.0
} }
/// Unwrap and get the inner window.
#[inline]
pub fn into_inner(self) -> W {
self.0
}
} }
impl<W: Window> Deref for AnyThread<W> { impl<W: Borrow<Window>> AsRef<Window> for AnyThread<W> {
type Target = 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 { fn deref(&self) -> &Self::Target {
&self.0 self.get_ref()
} }
} }
impl<W: Window> rwh_06::HasWindowHandle for AnyThread<W> { #[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> { fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
// SAFETY: The top level user has asserted this is only used safely. // SAFETY: The top level user has asserted this is only used safely.
unsafe { self.get_ref().window_handle_any_thread() } unsafe { self.get_ref().window_handle_any_thread() }
@@ -166,11 +183,11 @@ pub trait EventLoopBuilderExtWindows {
/// Disable process-wide DPI awareness. /// Disable process-wide DPI awareness.
/// ///
/// ``` /// ```
/// use winit::event_loop::EventLoop; /// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "windows")] /// #[cfg(target_os = "windows")]
/// use winit::platform::windows::EventLoopBuilderExtWindows; /// use winit::platform::windows::EventLoopBuilderExtWindows;
/// ///
/// let mut builder = EventLoop::builder(); /// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "windows")] /// #[cfg(target_os = "windows")]
/// builder.with_dpi_aware(false); /// builder.with_dpi_aware(false);
/// # if false { // We can't test this part /// # if false { // We can't test this part
@@ -186,11 +203,11 @@ pub trait EventLoopBuilderExtWindows {
/// ///
/// ``` /// ```
/// # use windows_sys::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG}; /// # use windows_sys::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG};
/// use winit::event_loop::EventLoop; /// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "windows")] /// #[cfg(target_os = "windows")]
/// use winit::platform::windows::EventLoopBuilderExtWindows; /// use winit::platform::windows::EventLoopBuilderExtWindows;
/// ///
/// let mut builder = EventLoop::builder(); /// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "windows")] /// #[cfg(target_os = "windows")]
/// builder.with_msg_hook(|msg|{ /// builder.with_msg_hook(|msg|{
/// let msg = msg as *const MSG; /// let msg = msg as *const MSG;
@@ -210,7 +227,7 @@ pub trait EventLoopBuilderExtWindows {
F: FnMut(*const c_void) -> bool + 'static; F: FnMut(*const c_void) -> bool + 'static;
} }
impl EventLoopBuilderExtWindows for EventLoopBuilder { impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
#[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;
@@ -318,9 +335,8 @@ pub trait WindowExtWindows {
/// ///
/// ```no_run /// ```no_run
/// # use winit::window::Window; /// # use winit::window::Window;
/// # fn scope(window: Box<dyn Window>) { /// # fn scope(window: Window) {
/// use std::thread; /// use std::thread;
///
/// use winit::platform::windows::WindowExtWindows; /// use winit::platform::windows::WindowExtWindows;
/// use winit::raw_window_handle::HasWindowHandle; /// use winit::raw_window_handle::HasWindowHandle;
/// ///
@@ -336,46 +352,41 @@ pub trait WindowExtWindows {
/// }); /// });
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "rwh_06")]
unsafe fn window_handle_any_thread( unsafe fn window_handle_any_thread(
&self, &self,
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError>; ) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError>;
} }
impl WindowExtWindows for dyn Window + '_ { impl WindowExtWindows for Window {
#[inline] #[inline]
fn set_enable(&self, enabled: bool) { fn set_enable(&self, enabled: bool) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); self.window.set_enable(enabled)
window.set_enable(enabled)
} }
#[inline] #[inline]
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) { fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); self.window.set_taskbar_icon(taskbar_icon)
window.set_taskbar_icon(taskbar_icon)
} }
#[inline] #[inline]
fn set_skip_taskbar(&self, skip: bool) { fn set_skip_taskbar(&self, skip: bool) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); self.window.set_skip_taskbar(skip)
window.set_skip_taskbar(skip)
} }
#[inline] #[inline]
fn set_undecorated_shadow(&self, shadow: bool) { fn set_undecorated_shadow(&self, shadow: bool) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); self.window.set_undecorated_shadow(shadow)
window.set_undecorated_shadow(shadow)
} }
#[inline] #[inline]
fn set_system_backdrop(&self, backdrop_type: BackdropType) { fn set_system_backdrop(&self, backdrop_type: BackdropType) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); self.window.set_system_backdrop(backdrop_type)
window.set_system_backdrop(backdrop_type)
} }
#[inline] #[inline]
fn set_border_color(&self, color: Option<Color>) { fn set_border_color(&self, color: Option<Color>) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); self.window.set_border_color(color.unwrap_or(Color::NONE))
window.set_border_color(color.unwrap_or(Color::NONE))
} }
#[inline] #[inline]
@@ -383,28 +394,25 @@ impl WindowExtWindows for dyn Window + '_ {
// The windows docs don't mention NONE as a valid options but it works in practice and is // The windows docs don't mention NONE as a valid options but it works in practice and is
// useful to circumvent the Windows option "Show accent color on title bars and // useful to circumvent the Windows option "Show accent color on title bars and
// window borders" // window borders"
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); self.window.set_title_background_color(color.unwrap_or(Color::NONE))
window.set_title_background_color(color.unwrap_or(Color::NONE))
} }
#[inline] #[inline]
fn set_title_text_color(&self, color: Color) { fn set_title_text_color(&self, color: Color) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); self.window.set_title_text_color(color)
window.set_title_text_color(color)
} }
#[inline] #[inline]
fn set_corner_preference(&self, preference: CornerPreference) { fn set_corner_preference(&self, preference: CornerPreference) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); self.window.set_corner_preference(preference)
window.set_corner_preference(preference)
} }
#[cfg(feature = "rwh_06")]
unsafe fn window_handle_any_thread( unsafe fn window_handle_any_thread(
&self, &self,
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> { ) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
unsafe { unsafe {
let handle = window.rwh_06_no_thread_check()?; let handle = self.window.rwh_06_no_thread_check()?;
// SAFETY: The handle is valid in this context. // SAFETY: The handle is valid in this context.
Ok(rwh_06::WindowHandle::borrow_raw(handle)) Ok(rwh_06::WindowHandle::borrow_raw(handle))
@@ -415,7 +423,7 @@ impl WindowExtWindows for dyn Window + '_ {
/// Additional methods for anything that dereference to [`Window`]. /// Additional methods for anything that dereference to [`Window`].
/// ///
/// [`Window`]: crate::window::Window /// [`Window`]: crate::window::Window
pub trait WindowBorrowExtWindows: Borrow<dyn Window> + Sized { pub trait WindowBorrowExtWindows: Borrow<Window> + Sized {
/// Create an object that allows accessing the inner window handle in a thread-unsafe way. /// 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 /// It is possible to call [`window_handle_any_thread`] to get around Windows's thread
@@ -431,18 +439,15 @@ pub trait WindowBorrowExtWindows: Borrow<dyn Window> + Sized {
/// It is the responsibility of the user to only pass the window handle into thread-safe /// It is the responsibility of the user to only pass the window handle into thread-safe
/// Win32 APIs. /// Win32 APIs.
/// ///
/// [`window_handle_any_thread`]: WindowExtWindows::window_handle_any_thread
/// [`Window`]: crate::window::Window /// [`Window`]: crate::window::Window
/// [`HasWindowHandle`]: rwh_06::HasWindowHandle /// [`HasWindowHandle`]: rwh_06::HasWindowHandle
/// [`window_handle_any_thread`]: WindowExtWindows::window_handle_any_thread unsafe fn any_thread(self) -> AnyThread<Self> {
unsafe fn any_thread(self) -> AnyThread<Self>
where
Self: Window,
{
AnyThread(self) AnyThread(self)
} }
} }
impl<W: Borrow<dyn Window> + Sized> WindowBorrowExtWindows for W {} 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)]
@@ -647,15 +652,10 @@ pub trait DeviceIdExtWindows {
fn persistent_identifier(&self) -> Option<String>; fn persistent_identifier(&self) -> Option<String>;
} }
#[cfg(windows_platform)]
impl DeviceIdExtWindows for DeviceId { impl DeviceIdExtWindows for DeviceId {
#[inline]
fn persistent_identifier(&self) -> Option<String> { fn persistent_identifier(&self) -> Option<String> {
let raw_id = self.into_raw(); self.0.persistent_identifier()
if raw_id != 0 {
crate::platform_impl::raw_input::get_raw_input_device_name(raw_id as HANDLE)
} else {
None
}
} }
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +0,0 @@
#[macro_use]
mod util;
mod app;
mod app_state;
mod cursor;
mod event;
mod event_loop;
mod ffi;
mod menu;
mod monitor;
mod observer;
mod view;
mod window;
mod window_delegate;
pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey, KeyEventExtra};
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::window::Window;
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;

View File

@@ -1,367 +0,0 @@
#![allow(clippy::unnecessary_cast)]
use dpi::{Position, Size};
use objc2::rc::{autoreleasepool, Retained};
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSResponder, NSWindow};
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate;
use crate::error::RequestError;
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::window::{
Cursor, Fullscreen, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
pub(crate) struct Window {
window: MainThreadBound<Retained<WinitWindow>>,
/// The window only keeps a weak reference to this, so we must keep it around here.
delegate: MainThreadBound<Retained<WindowDelegate>>,
}
impl Window {
pub(crate) fn new(
window_target: &ActiveEventLoop,
attributes: WindowAttributes,
) -> Result<Self, RequestError> {
let mtm = window_target.mtm;
let delegate =
autoreleasepool(|_| WindowDelegate::new(&window_target.app_state, attributes, mtm))?;
Ok(Window {
window: MainThreadBound::new(delegate.window().retain(), mtm),
delegate: MainThreadBound::new(delegate, mtm),
})
}
pub(crate) fn maybe_wait_on_main<R: Send>(
&self,
f: impl FnOnce(&WindowDelegate) -> R + Send,
) -> R {
self.delegate.get_on_main(|delegate| f(delegate))
}
#[inline]
pub(crate) fn raw_window_handle_rwh_06(
&self,
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
if let Some(mtm) = MainThreadMarker::new() {
Ok(self.delegate.get(mtm).raw_window_handle_rwh_06())
} else {
Err(rwh_06::HandleError::Unavailable)
}
}
#[inline]
pub(crate) fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new()))
}
}
impl Drop for Window {
fn drop(&mut self) {
// Restore the video mode.
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_))) {
self.set_fullscreen(None);
}
self.window.get_on_main(|window| autoreleasepool(|_| window.close()))
}
}
impl rwh_06::HasDisplayHandle for Window {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_display_handle_rwh_06()?;
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
impl rwh_06::HasWindowHandle for Window {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_window_handle_rwh_06()?;
unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) }
}
}
impl CoreWindow for Window {
fn id(&self) -> crate::window::WindowId {
self.maybe_wait_on_main(|delegate| delegate.id())
}
fn scale_factor(&self) -> f64 {
self.maybe_wait_on_main(|delegate| delegate.scale_factor())
}
fn request_redraw(&self) {
self.maybe_wait_on_main(|delegate| delegate.request_redraw());
}
fn pre_present_notify(&self) {
self.maybe_wait_on_main(|delegate| delegate.pre_present_notify());
}
fn reset_dead_keys(&self) {
self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys());
}
fn surface_position(&self) -> dpi::PhysicalPosition<i32> {
self.maybe_wait_on_main(|delegate| delegate.surface_position())
}
fn outer_position(&self) -> Result<dpi::PhysicalPosition<i32>, RequestError> {
self.maybe_wait_on_main(|delegate| delegate.outer_position())
}
fn set_outer_position(&self, position: Position) {
self.maybe_wait_on_main(|delegate| delegate.set_outer_position(position));
}
fn surface_size(&self) -> dpi::PhysicalSize<u32> {
self.maybe_wait_on_main(|delegate| delegate.surface_size())
}
fn request_surface_size(&self, size: Size) -> Option<dpi::PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.request_surface_size(size))
}
fn outer_size(&self) -> dpi::PhysicalSize<u32> {
self.maybe_wait_on_main(|delegate| delegate.outer_size())
}
fn safe_area(&self) -> dpi::PhysicalInsets<u32> {
self.maybe_wait_on_main(|delegate| delegate.safe_area())
}
fn set_min_surface_size(&self, min_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size))
}
fn set_max_surface_size(&self, max_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_max_surface_size(max_size));
}
fn surface_resize_increments(&self) -> Option<dpi::PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.surface_resize_increments())
}
fn set_surface_resize_increments(&self, increments: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_surface_resize_increments(increments));
}
fn set_title(&self, title: &str) {
self.maybe_wait_on_main(|delegate| delegate.set_title(title));
}
fn set_transparent(&self, transparent: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_transparent(transparent));
}
fn set_blur(&self, blur: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_blur(blur));
}
fn set_visible(&self, visible: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_visible(visible));
}
fn is_visible(&self) -> Option<bool> {
self.maybe_wait_on_main(|delegate| delegate.is_visible())
}
fn set_resizable(&self, resizable: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_resizable(resizable))
}
fn is_resizable(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_resizable())
}
fn set_enabled_buttons(&self, buttons: WindowButtons) {
self.maybe_wait_on_main(|delegate| delegate.set_enabled_buttons(buttons))
}
fn enabled_buttons(&self) -> WindowButtons {
self.maybe_wait_on_main(|delegate| delegate.enabled_buttons())
}
fn set_minimized(&self, minimized: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_minimized(minimized));
}
fn is_minimized(&self) -> Option<bool> {
self.maybe_wait_on_main(|delegate| delegate.is_minimized())
}
fn set_maximized(&self, maximized: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_maximized(maximized));
}
fn is_maximized(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_maximized())
}
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen.map(Into::into)))
}
fn fullscreen(&self) -> Option<Fullscreen> {
self.maybe_wait_on_main(|delegate| delegate.fullscreen().map(Into::into))
}
fn set_decorations(&self, decorations: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_decorations(decorations));
}
fn is_decorated(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_decorated())
}
fn set_window_level(&self, level: WindowLevel) {
self.maybe_wait_on_main(|delegate| delegate.set_window_level(level));
}
fn set_window_icon(&self, window_icon: Option<Icon>) {
self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon));
}
fn set_ime_cursor_area(&self, position: Position, size: Size) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_cursor_area(position, size));
}
fn set_ime_allowed(&self, allowed: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_allowed(allowed));
}
fn set_ime_purpose(&self, purpose: ImePurpose) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_purpose(purpose));
}
fn focus_window(&self) {
self.maybe_wait_on_main(|delegate| delegate.focus_window());
}
fn has_focus(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.has_focus())
}
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
self.maybe_wait_on_main(|delegate| delegate.request_user_attention(request_type));
}
fn set_theme(&self, theme: Option<Theme>) {
self.maybe_wait_on_main(|delegate| delegate.set_theme(theme));
}
fn theme(&self) -> Option<Theme> {
self.maybe_wait_on_main(|delegate| delegate.theme())
}
fn set_content_protected(&self, protected: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_content_protected(protected));
}
fn title(&self) -> String {
self.maybe_wait_on_main(|delegate| delegate.title())
}
fn set_cursor(&self, cursor: Cursor) {
self.maybe_wait_on_main(|delegate| delegate.set_cursor(cursor));
}
fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position))
}
fn set_cursor_grab(&self, mode: crate::window::CursorGrabMode) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode))
}
fn set_cursor_visible(&self, visible: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_visible(visible))
}
fn drag_window(&self) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.drag_window())
}
fn drag_resize_window(
&self,
direction: crate::window::ResizeDirection,
) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?)
}
fn show_window_menu(&self, position: Position) {
self.maybe_wait_on_main(|delegate| delegate.show_window_menu(position))
}
fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_hittest(hittest));
Ok(())
}
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.current_monitor().map(|inner| CoreMonitorHandle { inner })
})
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
self.maybe_wait_on_main(|delegate| {
Box::new(
delegate.available_monitors().into_iter().map(|inner| CoreMonitorHandle { inner }),
)
})
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.primary_monitor().map(|inner| CoreMonitorHandle { inner })
})
}
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
self
}
}
declare_class!(
#[derive(Debug)]
pub struct WinitWindow;
unsafe impl ClassType for WinitWindow {
#[inherits(NSResponder, NSObject)]
type Super = NSWindow;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitWindow";
}
impl DeclaredClass for WinitWindow {}
unsafe impl WinitWindow {
#[method(canBecomeMainWindow)]
fn can_become_main_window(&self) -> bool {
trace_scope!("canBecomeMainWindow");
true
}
#[method(canBecomeKeyWindow)]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
true
}
}
);
impl WinitWindow {
pub(super) fn id(&self) -> WindowId {
WindowId::from_raw(self as *const Self as usize)
}
}

View File

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

View File

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

View File

@@ -1,714 +0,0 @@
#![deny(unused_results)]
use std::cell::{OnceCell, RefCell, RefMut};
use std::collections::HashSet;
use std::os::raw::c_void;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Instant;
use std::{mem, ptr};
use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2::rc::Retained;
use objc2::sel;
use objc2_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
NSProcessInfo,
};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
use super::super::event_handler::EventHandler;
use super::window::WinitUIWindow;
use super::{ActiveEventLoop, EventLoopProxy};
use crate::application::ApplicationHandler;
use crate::dpi::PhysicalSize;
use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
macro_rules! bug {
($($msg:tt)*) => {
panic!("winit iOS bug, file an issue: {}", format!($($msg)*))
};
}
macro_rules! bug_assert {
($test:expr, $($msg:tt)*) => {
assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*))
};
}
/// Get the global event handler for the application.
///
/// This is stored separately from AppState, since AppState needs to be accessible while the handler
/// is executing.
fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler {
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
struct StaticMainThreadBound<T>(T);
impl<T> StaticMainThreadBound<T> {
const fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}
}
unsafe impl<T> Send for StaticMainThreadBound<T> {}
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept
// of the main thread.
static GLOBAL: StaticMainThreadBound<OnceCell<EventHandler>> =
StaticMainThreadBound(OnceCell::new());
GLOBAL.get(mtm).get_or_init(EventHandler::new)
}
#[derive(Debug)]
pub(crate) enum EventWrapper {
Window { window_id: WindowId, event: WindowEvent },
ScaleFactorChanged(ScaleFactorChanged),
}
#[derive(Debug)]
pub struct ScaleFactorChanged {
pub(super) window: Retained<WinitUIWindow>,
pub(super) suggested_size: PhysicalSize<u32>,
pub(super) scale_factor: f64,
}
impl EventWrapper {
fn is_redraw(&self) -> bool {
matches!(self, Self::Window { event: WindowEvent::RedrawRequested, .. })
}
}
// this is the state machine for the app lifecycle
#[derive(Debug)]
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
Initial {
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingEvents {
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
active_control_flow: ControlFlow,
},
ProcessingRedraws {
active_control_flow: ControlFlow,
},
Waiting {
start: Instant,
},
PollFinished,
Terminated,
}
pub(crate) struct AppState {
// This should never be `None`, except for briefly during a state transition.
app_state: Option<AppStateImpl>,
control_flow: ControlFlow,
waker: EventLoopWaker,
event_loop_proxy: Arc<EventLoopProxy>,
queued_events: Vec<EventWrapper>,
}
impl AppState {
pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> {
// basically everything in UIKit requires the main thread, so it's pointless to use the
// std::sync APIs.
// must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
#[allow(unknown_lints)] // New lint below
#[allow(static_mut_refs)] // TODO: Use `MainThreadBound` instead.
let mut guard = unsafe { APP_STATE.borrow_mut() };
if guard.is_none() {
#[inline(never)]
#[cold]
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
**guard = Some(AppState {
app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
control_flow: ControlFlow::default(),
waker,
event_loop_proxy: Arc::new(EventLoopProxy::new()),
queued_events: Vec::new(),
});
}
init_guard(&mut guard);
}
RefMut::map(guard, |state| state.as_mut().unwrap())
}
fn state(&self) -> &AppStateImpl {
match &self.app_state {
Some(ref state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn state_mut(&mut self) -> &mut AppStateImpl {
match &mut self.app_state {
Some(ref mut state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn take_state(&mut self) -> AppStateImpl {
match self.app_state.take() {
Some(state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn set_state(&mut self, new_state: AppStateImpl) {
bug_assert!(
self.app_state.is_none(),
"attempted to set an `AppState` without calling `take_state` first {:?}",
self.app_state
);
self.app_state = Some(new_state)
}
fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl {
match &mut self.app_state {
Some(ref mut state) => mem::replace(state, new_state),
None => bug!("`AppState` previously failed a state transition"),
}
}
fn has_launched(&self) -> bool {
!matches!(self.state(), AppStateImpl::Initial { .. })
}
fn has_terminated(&self) -> bool {
matches!(self.state(), AppStateImpl::Terminated)
}
fn did_finish_launching_transition(&mut self) {
let queued_gpu_redraws = match self.take_state() {
AppStateImpl::Initial { queued_gpu_redraws } => queued_gpu_redraws,
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
active_control_flow: self.control_flow,
queued_gpu_redraws,
});
}
fn wakeup_transition(&mut self) -> Option<StartCause> {
// before `AppState::did_finish_launching` is called, pretend there is no running
// event loop.
if !self.has_launched() || self.has_terminated() {
return None;
}
let start_cause = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished) => StartCause::Poll,
(ControlFlow::Wait, AppStateImpl::Waiting { start }) => {
StartCause::WaitCancelled { start, requested_resume: None }
},
(ControlFlow::WaitUntil(requested_resume), AppStateImpl::Waiting { start }) => {
if Instant::now() >= requested_resume {
StartCause::ResumeTimeReached { start, requested_resume }
} else {
StartCause::WaitCancelled { start, requested_resume: Some(requested_resume) }
}
},
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
queued_gpu_redraws: Default::default(),
active_control_flow: self.control_flow,
});
Some(start_cause)
}
fn main_events_cleared_transition(&mut self) -> HashSet<Retained<WinitUIWindow>> {
let (queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow } => {
(queued_gpu_redraws, active_control_flow)
},
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingRedraws { active_control_flow });
queued_gpu_redraws
}
fn events_cleared_transition(&mut self) {
if !self.has_launched() || self.has_terminated() {
return;
}
let old = match self.take_state() {
AppStateImpl::ProcessingRedraws { active_control_flow } => active_control_flow,
s => bug!("unexpected state {:?}", s),
};
let new = self.control_flow;
match (old, new) {
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start });
},
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant =>
{
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start });
},
(_, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start });
self.waker.stop()
},
(_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start });
self.waker.start_at(new_instant)
},
// Unlike on macOS, handle Poll to Poll transition here to call the waker
(_, ControlFlow::Poll) => {
self.set_state(AppStateImpl::PollFinished);
self.waker.start()
},
}
}
fn terminated_transition(&mut self) {
match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { .. } => {},
s => bug!("terminated while not processing events {:?}", s),
}
}
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
&self.event_loop_proxy
}
pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) {
self.control_flow = control_flow;
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow
}
}
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::Initial { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. } => {
let _ = queued_gpu_redraws.insert(window);
},
s @ &mut AppStateImpl::ProcessingRedraws { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
&mut AppStateImpl::Terminated => {
panic!("Attempt to create a `Window` after the app has terminated")
},
}
}
pub(crate) fn launch(mtm: MainThreadMarker, app: &mut dyn ApplicationHandler, run: impl FnOnce()) {
get_handler(mtm).set(app, run)
}
pub fn did_finish_launching(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
this.waker.start();
// have to drop RefMut because the window setup code below can trigger new events
drop(this);
AppState::get_mut(mtm).did_finish_launching_transition();
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, StartCause::Init));
get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
// AppState::did_finish_launching handles the special transition `Init`
pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let cause = match this.wakeup_transition() {
None => return,
Some(cause) => cause,
};
drop(this);
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, cause));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) {
handle_nonuser_events(mtm, std::iter::once(event))
}
pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
mtm: MainThreadMarker,
events: I,
) {
let mut this = AppState::get_mut(mtm);
if this.has_terminated() {
return;
}
if !get_handler(mtm).ready() {
// Prevent re-entrancy; queue the events up for once we're done handling the event instead.
this.queued_events.extend(events);
return;
}
let processing_redraws = matches!(this.state(), AppStateImpl::ProcessingRedraws { .. });
drop(this);
for event in events {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handle_wrapped_event(mtm, event)
}
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = mem::take(&mut this.queued_events);
if queued_events.is_empty() {
break;
}
drop(this);
for event in queued_events {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handle_wrapped_event(mtm, event);
}
}
}
fn handle_user_events(mtm: MainThreadMarker) {
let this = AppState::get_mut(mtm);
if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) {
bug!("user events attempted to be sent out while `ProcessingRedraws`");
}
let event_loop_proxy = this.event_loop_proxy().clone();
drop(this);
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
}
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = mem::take(&mut this.queued_events);
if queued_events.is_empty() {
break;
}
drop(this);
for event in queued_events {
handle_wrapped_event(mtm, event);
}
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
}
}
}
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::Window {
window_id: window.id(),
event: WindowEvent::Occluded(occluded),
});
}
}
handle_nonuser_events(mtm, events);
}
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
if !this.has_launched() || this.has_terminated() {
return;
}
match this.state_mut() {
AppStateImpl::ProcessingEvents { .. } => {},
_ => bug!("`ProcessingRedraws` happened unexpectedly"),
};
drop(this);
handle_user_events(mtm);
let mut this = AppState::get_mut(mtm);
let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
})
.collect();
drop(this);
handle_nonuser_events(mtm, redraw_events);
get_handler(mtm).handle(|app| app.about_to_wait(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
pub(crate) fn handle_resumed(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.resumed(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_suspended(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.suspended(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_memory_warning(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.memory_warning(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn terminated(application: &UIApplication) {
let mtm = MainThreadMarker::from(application);
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::Window {
window_id: window.id(),
event: WindowEvent::Destroyed,
});
}
}
handle_nonuser_events(mtm, events);
let mut this = AppState::get_mut(mtm);
this.terminated_transition();
drop(this);
get_handler(mtm).handle(|app| app.exiting(&ActiveEventLoop { mtm }));
}
fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) {
match event {
EventWrapper::Window { window_id, event } => get_handler(mtm)
.handle(|app| app.window_event(&ActiveEventLoop { mtm }, window_id, event)),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
}
}
fn handle_hidpi_proxy(mtm: MainThreadMarker, event: ScaleFactorChanged) {
let ScaleFactorChanged { suggested_size, scale_factor, window } = event;
let new_surface_size = Arc::new(Mutex::new(suggested_size));
get_handler(mtm).handle(|app| {
app.window_event(&ActiveEventLoop { mtm }, window.id(), WindowEvent::ScaleFactorChanged {
scale_factor,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)),
});
});
let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_surface_size.lock().unwrap();
drop(new_surface_size);
let logical_size = physical_size.to_logical(scale_factor);
let size = CGSize::new(logical_size.width, logical_size.height);
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
view.setFrame(new_frame);
}
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRect) {
let view_controller = window.rootViewController().unwrap();
let view = view_controller.view().unwrap();
let bounds = window.bounds();
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = window.convertRect_toCoordinateSpace(bounds, &screen_space);
(view, screen_frame)
}
struct EventLoopWaker {
timer: CFRunLoopTimerRef,
}
impl Drop for EventLoopWaker {
fn drop(&mut self) {
unsafe {
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
}
}
impl EventLoopWaker {
fn new(rl: CFRunLoopRef) -> EventLoopWaker {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
// It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
f64::MAX,
0.000_000_1,
0,
0,
wakeup_main_loop,
ptr::null_mut(),
);
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
EventLoopWaker { timer }
}
}
fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
}
fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
}
fn start_at(&mut self, instant: Instant) {
let now = Instant::now();
if now >= instant {
self.start();
} else {
unsafe {
let current = CFAbsoluteTimeGetCurrent();
let duration = instant - now;
let fsecs =
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
}
}
}
}
macro_rules! os_capabilities {
(
$(
$(#[$attr:meta])*
$error_name:ident: $objc_call:literal,
$name:ident: $major:literal-$minor:literal
),*
$(,)*
) => {
#[derive(Clone, Debug)]
pub struct OSCapabilities {
$(
pub $name: bool,
)*
os_version: NSOperatingSystemVersion,
}
impl OSCapabilities {
fn from_os_version(os_version: NSOperatingSystemVersion) -> Self {
$(let $name = meets_requirements(os_version, $major, $minor);)*
Self { $($name,)* os_version, }
}
}
impl OSCapabilities {$(
$(#[$attr])*
pub fn $error_name(&self, extra_msg: &str) {
tracing::warn!(
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
extra_msg
)
}
)*}
};
}
os_capabilities! {
/// <https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc>
#[allow(unused)] // error message unused
safe_area_err_msg: "-[UIView safeAreaInsets]",
safe_area: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc>
home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
home_indicator_hidden: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc>
defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
defer_system_gestures: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc>
maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
maximum_frames_per_second: 10-3,
/// <https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc>
#[allow(unused)] // error message unused
force_touch_err_msg: "-[UITouch force]",
force_touch: 9-0,
}
fn meets_requirements(
version: NSOperatingSystemVersion,
required_major: NSInteger,
required_minor: NSInteger,
) -> bool {
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
}
fn get_version() -> NSOperatingSystemVersion {
let process_info = NSProcessInfo::processInfo();
let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion));
// Winit requires atleast iOS 8 because no one has put the time into supporting earlier os
// versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does
// not support debugging on devices with an iOS version of less than 8. Another example, in
// order to use an iOS simulator older than iOS 8, you must download an older version of Xcode
// (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
process_info.operatingSystemVersion()
}
pub fn os_capabilities() -> OSCapabilities {
// Cache the version lookup for efficiency
static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new();
OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone()
}

View File

@@ -1,428 +0,0 @@
use std::ffi::{c_char, c_int, c_void};
use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
use objc2_ui_kit::{
UIApplication, UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain,
UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification,
UIApplicationWillTerminateNotification, UIScreen,
};
use rwh_06::HasDisplayHandle;
use super::super::notification_center::create_observer;
use super::app_state::{send_occluded_event_for_all_windows, AppState};
use super::{app_state, monitor, MonitorHandle};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform_impl::Window;
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window as CoreWindow};
#[derive(Debug)]
pub(crate) struct ActiveEventLoop {
pub(super) mtm: MainThreadMarker,
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> CoreEventLoopProxy {
CoreEventLoopProxy::new(AppState::get_mut(self.mtm).event_loop_proxy().clone())
}
fn create_window(
&self,
window_attributes: crate::window::WindowAttributes,
) -> Result<Box<dyn CoreWindow>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
_source: CustomCursorSource,
) -> Result<CustomCursor, RequestError> {
Err(NotSupportedError::new("create_custom_cursor is not supported").into())
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new(monitor::uiscreens(self.mtm).into_iter().map(|inner| RootMonitorHandle { inner }))
}
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
#[allow(deprecated)]
let monitor = MonitorHandle::new(UIScreen::mainScreen(self.mtm));
Some(RootMonitorHandle { inner: monitor })
}
fn listen_device_events(&self, _allowed: DeviceEvents) {}
fn set_control_flow(&self, control_flow: ControlFlow) {
AppState::get_mut(self.mtm).set_control_flow(control_flow)
}
fn system_theme(&self) -> Option<Theme> {
None
}
fn control_flow(&self) -> ControlFlow {
AppState::get_mut(self.mtm).control_flow()
}
fn exit(&self) {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programmatically
tracing::warn!("`ControlFlow::Exit` ignored on iOS");
}
fn exiting(&self) -> bool {
false
}
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
}
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
}
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct OwnedDisplayHandle;
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
pub struct EventLoop {
mtm: MainThreadMarker,
window_target: ActiveEventLoop,
// Since iOS 9.0, we no longer need to remove the observers before they are deallocated; the
// system instead cleans it up next time it would have posted a notification to it.
//
// Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<NSObject>,
_did_become_active_observer: Retained<NSObject>,
_will_resign_active_observer: Retained<NSObject>,
_will_enter_foreground_observer: Retained<NSObject>,
_did_enter_background_observer: Retained<NSObject>,
_will_terminate_observer: Retained<NSObject>,
_did_receive_memory_warning_observer: Retained<NSObject>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl EventLoop {
pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread");
static mut SINGLETON_INIT: bool = false;
unsafe {
assert!(
!SINGLETON_INIT,
"Only one `EventLoop` is supported on iOS. `EventLoopProxy` might be helpful"
);
SINGLETON_INIT = true;
}
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
let center = unsafe { NSNotificationCenter::defaultCenter() };
let _did_finish_launching_observer = create_observer(
&center,
// `application:didFinishLaunchingWithOptions:`
unsafe { UIApplicationDidFinishLaunchingNotification },
move |_| {
app_state::did_finish_launching(mtm);
},
);
let _did_become_active_observer = create_observer(
&center,
// `applicationDidBecomeActive:`
unsafe { UIApplicationDidBecomeActiveNotification },
move |_| app_state::handle_resumed(mtm),
);
let _will_resign_active_observer = create_observer(
&center,
// `applicationWillResignActive:`
unsafe { UIApplicationWillResignActiveNotification },
move |_| app_state::handle_suspended(mtm),
);
let _will_enter_foreground_observer = create_observer(
&center,
// `applicationWillEnterForeground:`
unsafe { UIApplicationWillEnterForegroundNotification },
move |notification| {
let app = unsafe { notification.object() }.expect(
"UIApplicationWillEnterForegroundNotification to have application object",
);
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, false);
},
);
let _did_enter_background_observer = create_observer(
&center,
// `applicationDidEnterBackground:`
unsafe { UIApplicationDidEnterBackgroundNotification },
move |notification| {
let app = unsafe { notification.object() }.expect(
"UIApplicationDidEnterBackgroundNotification to have application object",
);
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, true);
},
);
let _will_terminate_observer = create_observer(
&center,
// `applicationWillTerminate:`
unsafe { UIApplicationWillTerminateNotification },
move |notification| {
let app = unsafe { notification.object() }
.expect("UIApplicationWillTerminateNotification to have application object");
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
// (somewhat) documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
app_state::terminated(&app);
},
);
let _did_receive_memory_warning_observer = create_observer(
&center,
// `applicationDidReceiveMemoryWarning:`
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
move |_| app_state::handle_memory_warning(mtm),
);
Ok(EventLoop {
mtm,
window_target: ActiveEventLoop { mtm },
_did_finish_launching_observer,
_did_become_active_observer,
_will_resign_active_observer,
_will_enter_foreground_observer,
_did_enter_background_observer,
_will_terminate_observer,
_did_receive_memory_warning_observer,
})
}
pub fn run_app<A: ApplicationHandler>(self, mut app: A) -> ! {
let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!(
application.is_none(),
"\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\nNote: \
`EventLoop::run_app` calls `UIApplicationMain` on iOS",
);
extern "C" {
// These functions are in crt_externs.h.
fn _NSGetArgc() -> *mut c_int;
fn _NSGetArgv() -> *mut *mut *mut c_char;
}
app_state::launch(self.mtm, &mut app, || unsafe {
UIApplicationMain(
*_NSGetArgc(),
NonNull::new(*_NSGetArgv()).unwrap(),
// We intentionally override neither the application nor the delegate, to allow
// the user to do so themselves!
None,
None,
);
});
unreachable!()
}
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
&self.window_target
}
}
pub struct EventLoopProxy {
pub(crate) wake_up: AtomicBool,
source: CFRunLoopSourceRef,
}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl Drop for EventLoopProxy {
fn drop(&mut self) {
unsafe {
CFRunLoopSourceInvalidate(self.source);
CFRelease(self.source as _);
}
}
}
impl EventLoopProxy {
pub(crate) fn new() -> EventLoopProxy {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
let mut context = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copyDescription: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { wake_up: AtomicBool::new(false), source }
}
}
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.wake_up.store(true, AtomicOrdering::Relaxed);
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}
}
fn setup_control_flow_observers() {
unsafe {
// begin is queued with the highest priority to ensure it is processed before other
// observers
extern "C" fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(),
}
}
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send AboutToWait before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `AboutToWait`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
//
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
extern "C" fn control_flow_main_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
let main_loop = CFRunLoopGetMain();
let begin_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopAfterWaiting,
1, // repeat = true
CFIndex::MIN,
control_flow_begin_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
let main_end_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
0, // see comment on `control_flow_main_end_handler`
control_flow_main_end_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
CFIndex::MAX,
control_flow_end_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
}
}

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

@@ -0,0 +1,922 @@
#![deny(unused_results)]
use std::cell::{RefCell, RefMut};
use std::collections::HashSet;
use std::os::raw::c_void;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Instant;
use std::{fmt, mem, ptr};
use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2::{msg_send, sel};
use objc2_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
NSProcessInfo,
};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
use super::window::WinitUIWindow;
use crate::dpi::PhysicalSize;
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
use crate::window::WindowId as RootWindowId;
macro_rules! bug {
($($msg:tt)*) => {
panic!("winit iOS bug, file an issue: {}", format!($($msg)*))
};
}
macro_rules! bug_assert {
($test:expr, $($msg:tt)*) => {
assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*))
};
}
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;
pub(crate) struct EventLoopHandler {
#[allow(clippy::type_complexity)]
pub(crate) handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
pub(crate) event_loop: RootActiveEventLoop,
}
impl fmt::Debug for EventLoopHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopHandler")
.field("handler", &"...")
.field("event_loop", &self.event_loop)
.finish()
}
}
impl EventLoopHandler {
fn handle_event(&mut self, event: Event<HandlePendingUserEvents>) {
(self.handler)(event, &self.event_loop)
}
}
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event<HandlePendingUserEvents>),
ScaleFactorChanged(ScaleFactorChanged),
}
#[derive(Debug)]
pub struct ScaleFactorChanged {
pub(super) window: Retained<WinitUIWindow>,
pub(super) suggested_size: PhysicalSize<u32>,
pub(super) scale_factor: f64,
}
enum UserCallbackTransitionResult<'a> {
Success {
handler: EventLoopHandler,
active_control_flow: ControlFlow,
processing_redraws: bool,
},
ReentrancyPrevented {
queued_events: &'a mut Vec<EventWrapper>,
},
}
impl Event<HandlePendingUserEvents> {
fn is_redraw(&self) -> bool {
matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. })
}
}
// this is the state machine for the app lifecycle
#[derive(Debug)]
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
NotLaunched {
queued_windows: Vec<Retained<WinitUIWindow>>,
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
Launching {
queued_windows: Vec<Retained<WinitUIWindow>>,
queued_events: Vec<EventWrapper>,
queued_handler: EventLoopHandler,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingEvents {
handler: EventLoopHandler,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
active_control_flow: ControlFlow,
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingRedraws {
handler: EventLoopHandler,
active_control_flow: ControlFlow,
},
Waiting {
waiting_handler: EventLoopHandler,
start: Instant,
},
PollFinished {
waiting_handler: EventLoopHandler,
},
Terminated,
}
pub(crate) struct AppState {
// This should never be `None`, except for briefly during a state transition.
app_state: Option<AppStateImpl>,
control_flow: ControlFlow,
waker: EventLoopWaker,
}
impl AppState {
pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> {
// basically everything in UIKit requires the main thread, so it's pointless to use the
// std::sync APIs.
// must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
let mut guard = unsafe { APP_STATE.borrow_mut() };
if guard.is_none() {
#[inline(never)]
#[cold]
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
**guard = Some(AppState {
app_state: Some(AppStateImpl::NotLaunched {
queued_windows: Vec::new(),
queued_events: Vec::new(),
queued_gpu_redraws: HashSet::new(),
}),
control_flow: ControlFlow::default(),
waker,
});
}
init_guard(&mut guard);
}
RefMut::map(guard, |state| state.as_mut().unwrap())
}
fn state(&self) -> &AppStateImpl {
match &self.app_state {
Some(ref state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn state_mut(&mut self) -> &mut AppStateImpl {
match &mut self.app_state {
Some(ref mut state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn take_state(&mut self) -> AppStateImpl {
match self.app_state.take() {
Some(state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn set_state(&mut self, new_state: AppStateImpl) {
bug_assert!(
self.app_state.is_none(),
"attempted to set an `AppState` without calling `take_state` first {:?}",
self.app_state
);
self.app_state = Some(new_state)
}
fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl {
match &mut self.app_state {
Some(ref mut state) => mem::replace(state, new_state),
None => bug!("`AppState` previously failed a state transition"),
}
}
fn has_launched(&self) -> bool {
!matches!(self.state(), AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. })
}
fn has_terminated(&self) -> bool {
matches!(self.state(), AppStateImpl::Terminated)
}
fn will_launch_transition(&mut self, queued_handler: EventLoopHandler) {
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::NotLaunched { queued_windows, queued_events, queued_gpu_redraws } => {
(queued_windows, queued_events, queued_gpu_redraws)
},
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::Launching {
queued_windows,
queued_events,
queued_handler,
queued_gpu_redraws,
});
}
fn did_finish_launching_transition(
&mut self,
) -> (Vec<Retained<WinitUIWindow>>, Vec<EventWrapper>) {
let (windows, events, handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching {
queued_windows,
queued_events,
queued_handler,
queued_gpu_redraws,
} => (queued_windows, queued_events, queued_handler, queued_gpu_redraws),
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
handler,
active_control_flow: self.control_flow,
queued_gpu_redraws,
});
(windows, events)
}
fn wakeup_transition(&mut self) -> Option<EventWrapper> {
// before `AppState::did_finish_launching` is called, pretend there is no running
// event loop.
if !self.has_launched() || self.has_terminated() {
return None;
}
let (handler, event) = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => {
(waiting_handler, EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)))
},
(ControlFlow::Wait, AppStateImpl::Waiting { waiting_handler, start }) => (
waiting_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: None,
})),
),
(
ControlFlow::WaitUntil(requested_resume),
AppStateImpl::Waiting { waiting_handler, start },
) => {
let event = if Instant::now() >= requested_resume {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume,
}))
} else {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(requested_resume),
}))
};
(waiting_handler, event)
},
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
handler,
queued_gpu_redraws: Default::default(),
active_control_flow: self.control_flow,
});
Some(event)
}
fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> {
// If we're not able to process an event due to recursion or `Init` not having been sent out
// yet, then queue the events up.
match self.state_mut() {
&mut AppStateImpl::Launching { ref mut queued_events, .. }
| &mut AppStateImpl::NotLaunched { ref mut queued_events, .. }
| &mut AppStateImpl::InUserCallback { ref mut queued_events, .. } => {
// A lifetime cast: early returns are not currently handled well with NLL, but
// polonius handles them well. This transmute is a safe workaround.
return unsafe {
mem::transmute::<
UserCallbackTransitionResult<'_>,
UserCallbackTransitionResult<'_>,
>(UserCallbackTransitionResult::ReentrancyPrevented {
queued_events,
})
};
},
&mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {},
s @ &mut AppStateImpl::PollFinished { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::Terminated => {
bug!("unexpected attempted to process an event {:?}", s)
},
}
let (handler, queued_gpu_redraws, active_control_flow, processing_redraws) = match self
.take_state()
{
AppStateImpl::Launching { .. }
| AppStateImpl::NotLaunched { .. }
| AppStateImpl::InUserCallback { .. } => unreachable!(),
AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => {
(handler, queued_gpu_redraws, active_control_flow, false)
},
AppStateImpl::ProcessingRedraws { handler, active_control_flow } => {
(handler, Default::default(), active_control_flow, true)
},
AppStateImpl::PollFinished { .. }
| AppStateImpl::Waiting { .. }
| AppStateImpl::Terminated => unreachable!(),
};
self.set_state(AppStateImpl::InUserCallback {
queued_events: Vec::new(),
queued_gpu_redraws,
});
UserCallbackTransitionResult::Success { handler, active_control_flow, processing_redraws }
}
fn main_events_cleared_transition(&mut self) -> HashSet<Retained<WinitUIWindow>> {
let (handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => {
(handler, queued_gpu_redraws, active_control_flow)
},
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingRedraws { handler, active_control_flow });
queued_gpu_redraws
}
fn events_cleared_transition(&mut self) {
if !self.has_launched() || self.has_terminated() {
return;
}
let (waiting_handler, old) = match self.take_state() {
AppStateImpl::ProcessingRedraws { handler, active_control_flow } => {
(handler, active_control_flow)
},
s => bug!("unexpected state {:?}", s),
};
let new = self.control_flow;
match (old, new) {
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { waiting_handler, start });
},
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant =>
{
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { waiting_handler, start });
},
(_, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { waiting_handler, start });
self.waker.stop()
},
(_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { waiting_handler, start });
self.waker.start_at(new_instant)
},
// Unlike on macOS, handle Poll to Poll transition here to call the waker
(_, ControlFlow::Poll) => {
self.set_state(AppStateImpl::PollFinished { waiting_handler });
self.waker.start()
},
}
}
fn terminated_transition(&mut self) -> EventLoopHandler {
match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { handler, .. } => handler,
s => bug!("`LoopExiting` happened while not processing events {:?}", s),
}
}
pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) {
self.control_flow = control_flow;
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow
}
}
pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Retained<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } => {
return queued_windows.push(window.clone())
},
&mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::InUserCallback { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {},
s @ &mut AppStateImpl::Launching { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
&mut AppStateImpl::Terminated => {
panic!("Attempt to create a `Window` after the app has terminated")
},
}
drop(this);
window.makeKeyAndVisible();
}
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::NotLaunched { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::Launching { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::InUserCallback { ref mut queued_gpu_redraws, .. } => {
let _ = queued_gpu_redraws.insert(window);
},
s @ &mut AppStateImpl::ProcessingRedraws { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
&mut AppStateImpl::Terminated => {
panic!("Attempt to create a `Window` after the app has terminated")
},
}
}
pub(crate) fn will_launch(mtm: MainThreadMarker, queued_handler: EventLoopHandler) {
AppState::get_mut(mtm).will_launch_transition(queued_handler)
}
pub fn did_finish_launching(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let windows = match this.state_mut() {
AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows),
s => bug!("unexpected state {:?}", s),
};
this.waker.start();
// have to drop RefMut because the window setup code below can trigger new events
drop(this);
for window in windows {
// Do a little screen dance here to account for windows being created before
// `UIApplicationMain` is called. This fixes visual issues such as being
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we
// gotta reset the `rootViewController`.
//
// relevant iOS log:
// ```
// [ApplicationLifecycle] Windows were created before application initialization
// completed. This may result in incorrect visual appearance.
// ```
let screen = window.screen();
let _: () = unsafe { msg_send![&window, setScreen: ptr::null::<AnyObject>()] };
window.setScreen(&screen);
let controller = window.rootViewController();
window.setRootViewController(None);
window.setRootViewController(controller.as_deref());
window.makeKeyAndVisible();
}
let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition();
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(StartCause::Init)))
.chain(events);
handle_nonuser_events(mtm, events);
// the above window dance hack, could possibly trigger new windows to be created.
// we can just set those windows up normally, as they were created after didFinishLaunching
for window in windows {
window.makeKeyAndVisible();
}
}
// AppState::did_finish_launching handles the special transition `Init`
pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let wakeup_event = match this.wakeup_transition() {
None => return,
Some(wakeup_event) => wakeup_event,
};
drop(this);
handle_nonuser_event(mtm, wakeup_event)
}
pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) {
handle_nonuser_events(mtm, std::iter::once(event))
}
pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
mtm: MainThreadMarker,
events: I,
) {
let mut this = AppState::get_mut(mtm);
if this.has_terminated() {
return;
}
let (mut handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
queued_events.extend(events);
return;
},
UserCallbackTransitionResult::Success {
handler,
active_control_flow,
processing_redraws,
} => (handler, active_control_flow, processing_redraws),
};
drop(this);
for wrapper in events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handler.handle_event(event)
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
}
}
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state = Some(if processing_redraws {
bug_assert!(
queued_gpu_redraws.is_empty(),
"redraw queued while processing redraws"
);
AppStateImpl::ProcessingRedraws { handler, active_control_flow }
} else {
AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow }
});
break;
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non-`RedrawRequested` event after the main event loop: \
{:#?}",
event
);
}
handler.handle_event(event)
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
}
}
}
}
fn handle_user_events(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let (mut handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
bug!("unexpected attempted to process an event")
},
UserCallbackTransitionResult::Success {
handler,
active_control_flow,
processing_redraws,
} => (handler, active_control_flow, processing_redraws),
};
if processing_redraws {
bug!("user events attempted to be sent out while `ProcessingRedraws`");
}
drop(this);
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state = Some(AppStateImpl::ProcessingEvents {
handler,
queued_gpu_redraws,
active_control_flow,
});
break;
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => handler.handle_event(event),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
}
}
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
}
}
pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, occluded: bool) {
let mtm = MainThreadMarker::from(application);
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Occluded(occluded),
}));
}
}
handle_nonuser_events(mtm, events);
}
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
if !this.has_launched() || this.has_terminated() {
return;
}
match this.state_mut() {
AppStateImpl::ProcessingEvents { .. } => {},
_ => bug!("`ProcessingRedraws` happened unexpectedly"),
};
drop(this);
handle_user_events(mtm);
let mut this = AppState::get_mut(mtm);
let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::RedrawRequested,
})
})
.collect();
drop(this);
handle_nonuser_events(mtm, redraw_events);
handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait));
}
pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
pub(crate) fn terminated(application: &UIApplication) {
let mtm = MainThreadMarker::from(application);
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Destroyed,
}));
}
}
handle_nonuser_events(mtm, events);
let mut this = AppState::get_mut(mtm);
let mut handler = this.terminated_transition();
drop(this);
handler.handle_event(Event::LoopExiting)
}
fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) {
let ScaleFactorChanged { suggested_size, scale_factor, window } = event;
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
},
};
handler.handle_event(event);
let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let logical_size = physical_size.to_logical(scale_factor);
let size = CGSize::new(logical_size.width, logical_size.height);
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
view.setFrame(new_frame);
}
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRect) {
let view_controller = window.rootViewController().unwrap();
let view = view_controller.view().unwrap();
let bounds = window.bounds();
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = window.convertRect_toCoordinateSpace(bounds, &screen_space);
(view, screen_frame)
}
struct EventLoopWaker {
timer: CFRunLoopTimerRef,
}
impl Drop for EventLoopWaker {
fn drop(&mut self) {
unsafe {
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
}
}
impl EventLoopWaker {
fn new(rl: CFRunLoopRef) -> EventLoopWaker {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
// It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
f64::MAX,
0.000_000_1,
0,
0,
wakeup_main_loop,
ptr::null_mut(),
);
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
EventLoopWaker { timer }
}
}
fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
}
fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
}
fn start_at(&mut self, instant: Instant) {
let now = Instant::now();
if now >= instant {
self.start();
} else {
unsafe {
let current = CFAbsoluteTimeGetCurrent();
let duration = instant - now;
let fsecs =
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
}
}
}
}
macro_rules! os_capabilities {
(
$(
$(#[$attr:meta])*
$error_name:ident: $objc_call:literal,
$name:ident: $major:literal-$minor:literal
),*
$(,)*
) => {
#[derive(Clone, Debug)]
pub struct OSCapabilities {
$(
pub $name: bool,
)*
os_version: NSOperatingSystemVersion,
}
impl OSCapabilities {
fn from_os_version(os_version: NSOperatingSystemVersion) -> Self {
$(let $name = meets_requirements(os_version, $major, $minor);)*
Self { $($name,)* os_version, }
}
}
impl OSCapabilities {$(
$(#[$attr])*
pub fn $error_name(&self, extra_msg: &str) {
tracing::warn!(
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
extra_msg
)
}
)*}
};
}
os_capabilities! {
/// <https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc>
#[allow(unused)] // error message unused
safe_area_err_msg: "-[UIView safeAreaInsets]",
safe_area: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc>
home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
home_indicator_hidden: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc>
defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
defer_system_gestures: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc>
maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
maximum_frames_per_second: 10-3,
/// <https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc>
#[allow(unused)] // error message unused
force_touch_err_msg: "-[UITouch force]",
force_touch: 9-0,
}
fn meets_requirements(
version: NSOperatingSystemVersion,
required_major: NSInteger,
required_minor: NSInteger,
) -> bool {
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
}
fn get_version() -> NSOperatingSystemVersion {
let process_info = NSProcessInfo::processInfo();
let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion));
// Winit requires atleast iOS 8 because no one has put the time into supporting earlier os
// versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does
// not support debugging on devices with an iOS version of less than 8. Another example, in
// order to use an iOS simulator older than iOS 8, you must download an older version of Xcode
// (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
process_info.operatingSystemVersion()
}
pub fn os_capabilities() -> OSCapabilities {
// Cache the version lookup for efficiency
static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new();
OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone()
}

View File

@@ -0,0 +1,386 @@
use std::collections::VecDeque;
use std::ffi::{c_char, c_int, c_void};
use std::marker::PhantomData;
use std::ptr::{self, NonNull};
use std::sync::mpsc::{self, Receiver, Sender};
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSString};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom};
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::platform::ios::Idiom;
use crate::platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents};
use crate::window::{CustomCursor, CustomCursorSource};
use super::app_delegate::AppDelegate;
use super::app_state::AppState;
use super::{app_state, monitor, MonitorHandle};
#[derive(Debug)]
pub struct ActiveEventLoop {
pub(super) mtm: MainThreadMarker,
}
impl ActiveEventLoop {
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor {
let _ = source.inner;
CustomCursor { inner: super::PlatformCustomCursor }
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::uiscreens(self.mtm)
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
#[allow(deprecated)]
Some(MonitorHandle::new(UIScreen::mainScreen(self.mtm)))
}
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new()))
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
AppState::get_mut(self.mtm).set_control_flow(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
AppState::get_mut(self.mtm).control_flow()
}
pub(crate) fn exit(&self) {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programmatically
tracing::warn!("`ControlFlow::Exit` ignored on iOS");
}
pub(crate) fn exiting(&self) -> bool {
false
}
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle
}
}
#[derive(Clone)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::UiKitDisplayHandle::empty().into()
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::UiKitDisplayHandle::new().into())
}
}
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootActiveEventLoop),
receiver: mpsc::Receiver<T>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
}
},
}
}
pub struct EventLoop<T: 'static> {
mtm: MainThreadMarker,
sender: Sender<T>,
receiver: Receiver<T>,
window_target: RootActiveEventLoop,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop<T>, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread");
static mut SINGLETON_INIT: bool = false;
unsafe {
assert!(
!SINGLETON_INIT,
"Only one `EventLoop` is supported on iOS. `EventLoopProxy` might be helpful"
);
SINGLETON_INIT = true;
}
let (sender, receiver) = mpsc::channel();
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
Ok(EventLoop {
mtm,
sender,
receiver,
window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData },
})
}
pub fn run<F>(self, handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!(
application.is_none(),
"\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\nNote: \
`EventLoop::run_app` calls `UIApplicationMain` on iOS",
);
let handler = map_user_event(handler, self.receiver);
let handler = unsafe {
std::mem::transmute::<
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
>(Box::new(handler))
};
let handler = EventLoopHandler { handler, event_loop: self.window_target };
app_state::will_launch(self.mtm, handler);
// Ensure application delegate is initialized
let _ = AppDelegate::class();
extern "C" {
// These functions are in crt_externs.h.
fn _NSGetArgc() -> *mut c_int;
fn _NSGetArgv() -> *mut *mut *mut c_char;
}
unsafe {
UIApplicationMain(
*_NSGetArgc(),
NonNull::new(*_NSGetArgv()).unwrap(),
None,
Some(&NSString::from_str(AppDelegate::NAME)),
)
};
unreachable!()
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
}
pub fn window_target(&self) -> &RootActiveEventLoop {
&self.window_target
}
}
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
match UIDevice::currentDevice(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,
}
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
unsafe impl<T: Send> Sync for EventLoopProxy<T> {}
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
}
}
impl<T> Drop for EventLoopProxy<T> {
fn drop(&mut self) {
unsafe {
CFRunLoopSourceInvalidate(self.source);
CFRelease(self.source as _);
}
}
}
impl<T> EventLoopProxy<T> {
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
let mut context = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copyDescription: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { sender, source }
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender.send(event).map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
Ok(())
}
}
fn setup_control_flow_observers() {
unsafe {
// begin is queued with the highest priority to ensure it is processed before other
// observers
extern "C" fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(),
}
}
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send AboutToWait before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `AboutToWait`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
//
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
extern "C" fn control_flow_main_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
let main_loop = CFRunLoopGetMain();
let begin_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopAfterWaiting,
1, // repeat = true
CFIndex::MIN,
control_flow_begin_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
let main_end_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
0, // see comment on `control_flow_main_end_handler`
control_flow_main_end_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
CFIndex::MAX,
control_flow_end_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
}
}

View File

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

View File

@@ -1,7 +1,6 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::collections::{BTreeSet, VecDeque}; use std::collections::{BTreeSet, VecDeque};
use std::num::{NonZeroU16, NonZeroU32};
use std::{fmt, hash, ptr}; use std::{fmt, hash, ptr};
use objc2::mutability::IsRetainable; use objc2::mutability::IsRetainable;
@@ -10,9 +9,9 @@ use objc2::Message;
use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger}; use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger};
use objc2_ui_kit::{UIScreen, UIScreenMode}; use objc2_ui_kit::{UIScreen, UIScreenMode};
use super::app_state;
use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::VideoModeHandle as RootVideoModeHandle; use crate::monitor::VideoModeHandle as RootVideoModeHandle;
use crate::platform_impl::platform::app_state;
// Workaround for `MainThreadBound` implementing almost no traits // Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug)] #[derive(Debug)]
@@ -45,7 +44,8 @@ impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoModeHandle { pub struct VideoModeHandle {
pub(crate) size: (u32, u32), pub(crate) size: (u32, u32),
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>, pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32,
screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>, screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>,
pub(crate) monitor: MonitorHandle, pub(crate) monitor: MonitorHandle,
} }
@@ -60,6 +60,7 @@ impl VideoModeHandle {
let size = screen_mode.size(); let size = screen_mode.size();
VideoModeHandle { VideoModeHandle {
size: (size.width as u32, size.height as u32), size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate_millihertz, refresh_rate_millihertz,
screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)), screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)),
monitor: MonitorHandle::new(uiscreen), monitor: MonitorHandle::new(uiscreen),
@@ -70,11 +71,11 @@ impl VideoModeHandle {
self.size.into() self.size.into()
} }
pub fn bit_depth(&self) -> Option<NonZeroU16> { pub fn bit_depth(&self) -> u16 {
None self.bit_depth
} }
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> { pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz self.refresh_rate_millihertz
} }
@@ -101,20 +102,13 @@ impl Clone for MonitorHandle {
impl hash::Hash for MonitorHandle { impl hash::Hash for MonitorHandle {
fn hash<H: hash::Hasher>(&self, state: &mut H) { fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Only getting the pointer. (self as *const Self).hash(state);
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).hash(state);
} }
} }
impl PartialEq for MonitorHandle { impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// SAFETY: Only getting the pointer. ptr::eq(self, other)
let mtm = unsafe { MainThreadMarker::new_unchecked() };
ptr::eq(
Retained::as_ptr(self.ui_screen.get(mtm)),
Retained::as_ptr(other.ui_screen.get(mtm)),
)
} }
} }
@@ -128,10 +122,8 @@ impl PartialOrd for MonitorHandle {
impl Ord for MonitorHandle { impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// SAFETY: Only getting the pointer.
// TODO: Make a better ordering // TODO: Make a better ordering
let mtm = unsafe { MainThreadMarker::new_unchecked() }; (self as *const Self).cmp(&(other as *const Self))
Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm)))
} }
} }
@@ -139,8 +131,10 @@ impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MonitorHandle") f.debug_struct("MonitorHandle")
.field("name", &self.name()) .field("name", &self.name())
.field("size", &self.size())
.field("position", &self.position()) .field("position", &self.position())
.field("scale_factor", &self.scale_factor()) .field("scale_factor", &self.scale_factor())
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz())
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }
@@ -170,23 +164,22 @@ impl MonitorHandle {
}) })
} }
pub fn position(&self) -> Option<PhysicalPosition<i32>> { pub fn size(&self) -> PhysicalSize<u32> {
let bounds = self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeBounds()); let bounds = self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeBounds());
Some((bounds.origin.x as f64, bounds.origin.y as f64).into()) PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
}
pub fn position(&self) -> PhysicalPosition<i32> {
let bounds = self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeBounds());
(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.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64 self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
} }
pub fn current_video_mode(&self) -> Option<VideoModeHandle> { pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(run_on_main(|mtm| { Some(self.ui_screen.get_on_main(|ui_screen| refresh_rate_millihertz(ui_screen)))
VideoModeHandle::new(
self.ui_screen(mtm).clone(),
self.ui_screen(mtm).currentMode().unwrap(),
mtm,
)
}))
} }
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> { pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
@@ -221,7 +214,7 @@ impl MonitorHandle {
} }
} }
fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> { fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
let refresh_rate_millihertz: NSInteger = { let refresh_rate_millihertz: NSInteger = {
let os_capabilities = app_state::os_capabilities(); let os_capabilities = app_state::os_capabilities();
if os_capabilities.maximum_frames_per_second { if os_capabilities.maximum_frames_per_second {
@@ -242,34 +235,10 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
} }
}; };
NonZeroU32::new(refresh_rate_millihertz as u32 * 1000) refresh_rate_millihertz as u32 * 1000
} }
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> { pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
#[allow(deprecated)] #[allow(deprecated)]
UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect() UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect()
} }
#[cfg(test)]
mod tests {
use objc2_foundation::NSSet;
use super::*;
// Test that UIScreen pointer comparisons are correct.
#[test]
#[allow(deprecated)]
fn screen_comparisons() {
// Test code, doesn't matter that it's not thread safe
let mtm = unsafe { MainThreadMarker::new_unchecked() };
assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm)));
let main = UIScreen::mainScreen(mtm);
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main)));
assert!(unsafe {
NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm))
});
}
}

View File

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

View File

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

View File

@@ -6,14 +6,13 @@ use std::ops::Deref;
use std::os::unix::ffi::OsStringExt; use std::os::unix::ffi::OsStringExt;
use std::ptr::NonNull; use std::ptr::NonNull;
use super::{XkbContext, XKBCH};
use smol_str::SmolStr; use smol_str::SmolStr;
use xkbcommon_dl::{ use xkbcommon_dl::{
xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags, xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags,
xkb_compose_status, xkb_compose_table, xkb_keysym_t, xkb_compose_status, xkb_compose_table, xkb_keysym_t,
}; };
use super::{XkbContext, XKBCH};
#[derive(Debug)] #[derive(Debug)]
pub struct XkbComposeTable { pub struct XkbComposeTable {
table: NonNull<xkb_compose_table>, table: NonNull<xkb_compose_table>,

View File

@@ -6,13 +6,14 @@ use std::ptr::{self, NonNull};
#[cfg(x11_platform)] #[cfg(x11_platform)]
use x11_dl::xlib_xcb::xcb_connection_t; use x11_dl::xlib_xcb::xcb_connection_t;
#[cfg(wayland_platform)]
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
use xkb::XKB_MOD_INVALID; use xkb::XKB_MOD_INVALID;
use xkbcommon_dl::{ use xkbcommon_dl::{
self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t, self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t,
xkb_layout_index_t, xkb_mod_index_t, xkb_layout_index_t, xkb_mod_index_t,
}; };
#[cfg(wayland_platform)]
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
#[cfg(x11_platform)] #[cfg(x11_platform)]
@@ -35,9 +36,6 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes, // are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
// I can only hope they agree on what the keycodes mean. // I can only hope they agree on what the keycodes mean.
// //
// The mapping here is heavily influenced by Firefox' source:
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
//
// Some of the keycodes are likely superfluous for our purposes, and some are ones which are // Some of the keycodes are likely superfluous for our purposes, and some are ones which are
// difficult to test the correctness of, or discover the purpose of. Because of this, they've // difficult to test the correctness of, or discover the purpose of. Because of this, they've
// either been commented out here, or not included at all. // either been commented out here, or not included at all.
@@ -169,23 +167,23 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
125 => KeyCode::SuperLeft, 125 => KeyCode::SuperLeft,
126 => KeyCode::SuperRight, 126 => KeyCode::SuperRight,
127 => KeyCode::ContextMenu, 127 => KeyCode::ContextMenu,
128 => KeyCode::BrowserStop, // 128 => KeyCode::STOP,
129 => KeyCode::Again, // 129 => KeyCode::AGAIN,
130 => KeyCode::Props, // 130 => KeyCode::PROPS,
131 => KeyCode::Undo, // 131 => KeyCode::UNDO,
132 => KeyCode::Select, // FRONT // 132 => KeyCode::FRONT,
133 => KeyCode::Copy, // 133 => KeyCode::COPY,
134 => KeyCode::Open, // 134 => KeyCode::OPEN,
135 => KeyCode::Paste, // 135 => KeyCode::PASTE,
136 => KeyCode::Find, // 136 => KeyCode::FIND,
137 => KeyCode::Cut, // 137 => KeyCode::CUT,
138 => KeyCode::Help, // 138 => KeyCode::HELP,
// 139 => KeyCode::MENU, // 139 => KeyCode::MENU,
140 => KeyCode::LaunchApp2, // CALC // 140 => KeyCode::CALC,
// 141 => KeyCode::SETUP, // 141 => KeyCode::SETUP,
// 142 => KeyCode::SLEEP, // 142 => KeyCode::SLEEP,
143 => KeyCode::WakeUp, // 143 => KeyCode::WAKEUP,
144 => KeyCode::LaunchApp1, // FILE // 144 => KeyCode::FILE,
// 145 => KeyCode::SENDFILE, // 145 => KeyCode::SENDFILE,
// 146 => KeyCode::DELETEFILE, // 146 => KeyCode::DELETEFILE,
// 147 => KeyCode::XFER, // 147 => KeyCode::XFER,
@@ -196,13 +194,13 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 152 => KeyCode::COFFEE, // 152 => KeyCode::COFFEE,
// 153 => KeyCode::ROTATE_DISPLAY, // 153 => KeyCode::ROTATE_DISPLAY,
// 154 => KeyCode::CYCLEWINDOWS, // 154 => KeyCode::CYCLEWINDOWS,
155 => KeyCode::LaunchMail, // 155 => KeyCode::MAIL,
156 => KeyCode::BrowserFavorites, // BOOKMARKS // 156 => KeyCode::BOOKMARKS,
// 157 => KeyCode::COMPUTER, // 157 => KeyCode::COMPUTER,
158 => KeyCode::BrowserBack, // 158 => KeyCode::BACK,
159 => KeyCode::BrowserForward, // 159 => KeyCode::FORWARD,
// 160 => KeyCode::CLOSECD, // 160 => KeyCode::CLOSECD,
161 => KeyCode::Eject, // EJECTCD // 161 => KeyCode::EJECTCD,
// 162 => KeyCode::EJECTCLOSECD, // 162 => KeyCode::EJECTCLOSECD,
163 => KeyCode::MediaTrackNext, 163 => KeyCode::MediaTrackNext,
164 => KeyCode::MediaPlayPause, 164 => KeyCode::MediaPlayPause,
@@ -212,9 +210,9 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 168 => KeyCode::REWIND, // 168 => KeyCode::REWIND,
// 169 => KeyCode::PHONE, // 169 => KeyCode::PHONE,
// 170 => KeyCode::ISO, // 170 => KeyCode::ISO,
171 => KeyCode::MediaSelect, // CONFIG // 171 => KeyCode::CONFIG,
172 => KeyCode::BrowserHome, // 172 => KeyCode::HOMEPAGE,
173 => KeyCode::BrowserRefresh, // 173 => KeyCode::REFRESH,
// 174 => KeyCode::EXIT, // 174 => KeyCode::EXIT,
// 175 => KeyCode::MOVE, // 175 => KeyCode::MOVE,
// 176 => KeyCode::EDIT, // 176 => KeyCode::EDIT,
@@ -253,7 +251,7 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 214 => KeyCode::QUESTION, // 214 => KeyCode::QUESTION,
// 215 => KeyCode::EMAIL, // 215 => KeyCode::EMAIL,
// 216 => KeyCode::CHAT, // 216 => KeyCode::CHAT,
217 => KeyCode::BrowserSearch, // 217 => KeyCode::SEARCH,
// 218 => KeyCode::CONNECT, // 218 => KeyCode::CONNECT,
// 219 => KeyCode::FINANCE, // 219 => KeyCode::FINANCE,
// 220 => KeyCode::SPORT, // 220 => KeyCode::SPORT,
@@ -422,32 +420,10 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
KeyCode::SuperLeft => Some(125), KeyCode::SuperLeft => Some(125),
KeyCode::SuperRight => Some(126), KeyCode::SuperRight => Some(126),
KeyCode::ContextMenu => Some(127), KeyCode::ContextMenu => Some(127),
KeyCode::BrowserStop => Some(128),
KeyCode::Again => Some(129),
KeyCode::Props => Some(130),
KeyCode::Undo => Some(131),
KeyCode::Select => Some(132),
KeyCode::Copy => Some(133),
KeyCode::Open => Some(134),
KeyCode::Paste => Some(135),
KeyCode::Find => Some(136),
KeyCode::Cut => Some(137),
KeyCode::Help => Some(138),
KeyCode::LaunchApp2 => Some(140),
KeyCode::WakeUp => Some(143),
KeyCode::LaunchApp1 => Some(144),
KeyCode::LaunchMail => Some(155),
KeyCode::BrowserFavorites => Some(156),
KeyCode::BrowserBack => Some(158),
KeyCode::BrowserForward => Some(159),
KeyCode::Eject => Some(161),
KeyCode::MediaTrackNext => Some(163), KeyCode::MediaTrackNext => Some(163),
KeyCode::MediaPlayPause => Some(164), KeyCode::MediaPlayPause => Some(164),
KeyCode::MediaTrackPrevious => Some(165), KeyCode::MediaTrackPrevious => Some(165),
KeyCode::MediaStop => Some(166), KeyCode::MediaStop => Some(166),
KeyCode::MediaSelect => Some(171),
KeyCode::BrowserHome => Some(172),
KeyCode::BrowserRefresh => Some(173),
KeyCode::F13 => Some(183), KeyCode::F13 => Some(183),
KeyCode::F14 => Some(184), KeyCode::F14 => Some(184),
KeyCode::F15 => Some(185), KeyCode::F15 => Some(185),
@@ -460,7 +436,6 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
KeyCode::F22 => Some(192), KeyCode::F22 => Some(192),
KeyCode::F23 => Some(193), KeyCode::F23 => Some(193),
KeyCode::F24 => Some(194), KeyCode::F24 => Some(194),
KeyCode::BrowserSearch => Some(217),
_ => None, _ => None,
} }
} }
@@ -664,7 +639,7 @@ pub fn keysym_to_key(keysym: u32) -> Key {
// keysyms::ISO_Release_Margin_Left => NamedKey::IsoReleaseMarginLeft, // keysyms::ISO_Release_Margin_Left => NamedKey::IsoReleaseMarginLeft,
// keysyms::ISO_Release_Margin_Right => NamedKey::IsoReleaseMarginRight, // keysyms::ISO_Release_Margin_Right => NamedKey::IsoReleaseMarginRight,
// keysyms::ISO_Release_Both_Margins => NamedKey::IsoReleaseBothMargins, // keysyms::ISO_Release_Both_Margins => NamedKey::IsoReleaseBothMargins,
// keysyms::ISO_Fast_Cursor_Left => NamedKey::IsoFastPointerLeft, // keysyms::ISO_Fast_Cursor_Left => NamedKey::IsoFastCursorLeft,
// keysyms::ISO_Fast_Cursor_Right => NamedKey::IsoFastCursorRight, // keysyms::ISO_Fast_Cursor_Right => NamedKey::IsoFastCursorRight,
// keysyms::ISO_Fast_Cursor_Up => NamedKey::IsoFastCursorUp, // keysyms::ISO_Fast_Cursor_Up => NamedKey::IsoFastCursorUp,
// keysyms::ISO_Fast_Cursor_Down => NamedKey::IsoFastCursorDown, // keysyms::ISO_Fast_Cursor_Down => NamedKey::IsoFastCursorDown,

View File

@@ -1,11 +1,12 @@
use std::ops::Deref; use std::ops::Deref;
use std::os::raw::c_char; use std::os::raw::c_char;
#[cfg(wayland_platform)]
use std::os::unix::io::OwnedFd;
use std::ptr::{self, NonNull}; use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use crate::utils::Lazy;
use smol_str::SmolStr; use smol_str::SmolStr;
#[cfg(wayland_platform)]
use std::os::unix::io::OwnedFd;
use tracing::warn; use tracing::warn;
use xkbcommon_dl::{ use xkbcommon_dl::{
self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle, self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle,
@@ -17,16 +18,16 @@ use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle
use crate::event::{ElementState, KeyEvent}; use crate::event::{ElementState, KeyEvent};
use crate::keyboard::{Key, KeyLocation}; use crate::keyboard::{Key, KeyLocation};
use crate::platform_impl::KeyEventExtra; use crate::platform_impl::KeyEventExtra;
use crate::utils::Lazy;
mod compose; mod compose;
mod keymap; mod keymap;
mod state; mod state;
use compose::{ComposeStatus, XkbComposeState, XkbComposeTable}; use compose::{ComposeStatus, XkbComposeState, XkbComposeTable};
use keymap::XkbKeymap;
#[cfg(x11_platform)] #[cfg(x11_platform)]
pub use keymap::raw_keycode_to_physicalkey; pub use keymap::raw_keycode_to_physicalkey;
use keymap::XkbKeymap;
pub use keymap::{physicalkey_to_scancode, scancode_to_physicalkey}; pub use keymap::{physicalkey_to_scancode, scancode_to_physicalkey};
pub use state::XkbState; pub use state::XkbState;
@@ -183,7 +184,7 @@ pub struct KeyContext<'a> {
scratch_buffer: &'a mut Vec<u8>, scratch_buffer: &'a mut Vec<u8>,
} }
impl KeyContext<'_> { impl<'a> KeyContext<'a> {
pub fn process_key_event( pub fn process_key_event(
&mut self, &mut self,
keycode: u32, keycode: u32,

View File

@@ -3,33 +3,39 @@
#[cfg(all(not(x11_platform), not(wayland_platform)))] #[cfg(all(not(x11_platform), not(wayland_platform)))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
use std::env; use std::collections::VecDeque;
use std::num::{NonZeroU16, NonZeroU32};
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::{env, fmt};
#[cfg(x11_platform)] #[cfg(x11_platform)]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc, sync::Mutex}; use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
#[cfg(x11_platform)]
use crate::utils::Lazy;
use smol_str::SmolStr; use smol_str::SmolStr;
pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
#[cfg(x11_platform)] #[cfg(x11_platform)]
use self::x11::{XConnection, XError, XNotSupported}; use self::x11::{X11Error, XConnection, XError, XNotSupported};
use crate::application::ApplicationHandler; use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource; use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError};
#[cfg(x11_platform)] use crate::event_loop::{
use crate::dpi::Size; ActiveEventLoop as RootELW, AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
use crate::dpi::{PhysicalPosition, PhysicalSize}; };
use crate::error::{EventLoopError, NotSupportedError}; use crate::icon::Icon;
use crate::event_loop::ActiveEventLoop;
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
use crate::keyboard::Key; use crate::keyboard::Key;
use crate::platform::pump_events::PumpStatus; use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)] #[cfg(x11_platform)]
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook}; use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
#[cfg(x11_platform)] use crate::window::{
use crate::utils::Lazy; ActivationToken, Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose,
use crate::window::ActivationToken; ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel,
};
pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
pub(crate) mod common; pub(crate) mod common;
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
@@ -63,7 +69,7 @@ impl ApplicationName {
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug)]
pub struct PlatformSpecificWindowAttributes { pub struct PlatformSpecificWindowAttributes {
pub name: Option<ApplicationName>, pub name: Option<ApplicationName>,
pub activation_token: Option<ActivationToken>, pub activation_token: Option<ActivationToken>,
@@ -71,7 +77,7 @@ pub struct PlatformSpecificWindowAttributes {
pub x11: X11WindowAttributes, pub x11: X11WindowAttributes,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug)]
#[cfg(x11_platform)] #[cfg(x11_platform)]
pub struct X11WindowAttributes { pub struct X11WindowAttributes {
pub visual_id: Option<x11rb::protocol::xproto::Visualid>, pub visual_id: Option<x11rb::protocol::xproto::Visualid>,
@@ -107,7 +113,73 @@ impl Default for PlatformSpecificWindowAttributes {
pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> = pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))); Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone)]
pub enum OsError {
Misc(&'static str),
#[cfg(x11_platform)]
XError(Arc<X11Error>),
#[cfg(wayland_platform)]
WaylandError(Arc<wayland::WaylandError>),
}
impl fmt::Display for OsError {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match *self {
OsError::Misc(e) => _f.pad(e),
#[cfg(x11_platform)]
OsError::XError(ref e) => fmt::Display::fmt(e, _f),
#[cfg(wayland_platform)]
OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f),
}
}
}
pub(crate) enum Window {
#[cfg(x11_platform)]
X(x11::Window),
#[cfg(wayland_platform)]
Wayland(wayland::Window),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(u64);
impl From<WindowId> for u64 {
fn from(window_id: WindowId) -> Self {
window_id.0
}
}
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self(raw_id)
}
}
impl WindowId {
pub const fn dummy() -> Self {
Self(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeviceId {
#[cfg(x11_platform)]
X(x11::DeviceId),
#[cfg(wayland_platform)]
Wayland(wayland::DeviceId),
}
impl DeviceId {
pub const fn dummy() -> Self {
#[cfg(wayland_platform)]
return DeviceId::Wayland(wayland::DeviceId::dummy());
#[cfg(all(not(wayland_platform), x11_platform))]
return DeviceId::X(x11::DeviceId::dummy());
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum MonitorHandle { pub enum MonitorHandle {
#[cfg(x11_platform)] #[cfg(x11_platform)]
X(x11::MonitorHandle), X(x11::MonitorHandle),
@@ -155,20 +227,25 @@ impl MonitorHandle {
} }
#[inline] #[inline]
pub fn position(&self) -> Option<PhysicalPosition<i32>> { pub fn size(&self) -> PhysicalSize<u32> {
x11_or_wayland!(match self; MonitorHandle(m) => m.size())
}
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
x11_or_wayland!(match self; MonitorHandle(m) => m.position()) x11_or_wayland!(match self; MonitorHandle(m) => m.position())
} }
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
x11_or_wayland!(match self; MonitorHandle(m) => m.refresh_rate_millihertz())
}
#[inline] #[inline]
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as _) x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as _)
} }
#[inline]
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
x11_or_wayland!(match self; MonitorHandle(m) => m.current_video_mode())
}
#[inline] #[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> { pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> {
x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes())) x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
@@ -190,12 +267,12 @@ impl VideoModeHandle {
} }
#[inline] #[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> { pub fn bit_depth(&self) -> u16 {
x11_or_wayland!(match self; VideoModeHandle(m) => m.bit_depth()) x11_or_wayland!(match self; VideoModeHandle(m) => m.bit_depth())
} }
#[inline] #[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> { pub fn refresh_rate_millihertz(&self) -> u32 {
x11_or_wayland!(match self; VideoModeHandle(m) => m.refresh_rate_millihertz()) x11_or_wayland!(match self; VideoModeHandle(m) => m.refresh_rate_millihertz())
} }
@@ -205,6 +282,351 @@ impl VideoModeHandle {
} }
} }
impl Window {
#[inline]
pub(crate) fn new(
window_target: &ActiveEventLoop,
attribs: WindowAttributes,
) -> Result<Self, RootOsError> {
match *window_target {
#[cfg(wayland_platform)]
ActiveEventLoop::Wayland(ref window_target) => {
wayland::Window::new(window_target, attribs).map(Window::Wayland)
},
#[cfg(x11_platform)]
ActiveEventLoop::X(ref window_target) => {
x11::Window::new(window_target, attribs).map(Window::X)
},
}
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
f(self)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
f(self)
}
#[inline]
pub fn id(&self) -> WindowId {
x11_or_wayland!(match self; Window(w) => w.id())
}
#[inline]
pub fn set_title(&self, title: &str) {
x11_or_wayland!(match self; Window(w) => w.set_title(title));
}
#[inline]
pub fn set_transparent(&self, transparent: bool) {
x11_or_wayland!(match self; Window(w) => w.set_transparent(transparent));
}
#[inline]
pub fn set_blur(&self, blur: bool) {
x11_or_wayland!(match self; Window(w) => w.set_blur(blur));
}
#[inline]
pub fn set_visible(&self, visible: bool) {
x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
}
#[inline]
pub fn is_visible(&self) -> Option<bool> {
x11_or_wayland!(match self; Window(w) => w.is_visible())
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
x11_or_wayland!(match self; Window(w) => w.outer_position())
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
x11_or_wayland!(match self; Window(w) => w.inner_position())
}
#[inline]
pub fn set_outer_position(&self, position: Position) {
x11_or_wayland!(match self; Window(w) => w.set_outer_position(position))
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
x11_or_wayland!(match self; Window(w) => w.inner_size())
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
x11_or_wayland!(match self; Window(w) => w.outer_size())
}
#[inline]
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
x11_or_wayland!(match self; Window(w) => w.request_inner_size(size))
}
#[inline]
pub(crate) fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
x11_or_wayland!(match self; Window(w) => w.request_activation_token())
}
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
x11_or_wayland!(match self; Window(w) => w.set_min_inner_size(dimensions))
}
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions))
}
#[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
x11_or_wayland!(match self; Window(w) => w.resize_increments())
}
#[inline]
pub fn set_resize_increments(&self, increments: Option<Size>) {
x11_or_wayland!(match self; Window(w) => w.set_resize_increments(increments))
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable))
}
#[inline]
pub fn is_resizable(&self) -> bool {
x11_or_wayland!(match self; Window(w) => w.is_resizable())
}
#[inline]
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
x11_or_wayland!(match self; Window(w) => w.set_enabled_buttons(buttons))
}
#[inline]
pub fn enabled_buttons(&self) -> WindowButtons {
x11_or_wayland!(match self; Window(w) => w.enabled_buttons())
}
#[inline]
pub fn set_cursor(&self, cursor: Cursor) {
x11_or_wayland!(match self; Window(w) => w.set_cursor(cursor))
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode))
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible))
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.drag_window())
}
#[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction))
}
#[inline]
pub fn show_window_menu(&self, position: Position) {
x11_or_wayland!(match self; Window(w) => w.show_window_menu(position))
}
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
}
#[inline]
pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; Window(w) => w.scale_factor())
}
#[inline]
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.set_cursor_position(position))
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized))
}
#[inline]
pub fn is_maximized(&self) -> bool {
x11_or_wayland!(match self; Window(w) => w.is_maximized())
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized))
}
#[inline]
pub fn is_minimized(&self) -> Option<bool> {
x11_or_wayland!(match self; Window(w) => w.is_minimized())
}
#[inline]
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
x11_or_wayland!(match self; Window(w) => w.fullscreen())
}
#[inline]
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor))
}
#[inline]
pub fn set_decorations(&self, decorations: bool) {
x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations))
}
#[inline]
pub fn is_decorated(&self) -> bool {
x11_or_wayland!(match self; Window(w) => w.is_decorated())
}
#[inline]
pub fn set_window_level(&self, level: WindowLevel) {
x11_or_wayland!(match self; Window(w) => w.set_window_level(level))
}
#[inline]
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
x11_or_wayland!(match self; Window(w) => w.set_window_icon(window_icon.map(|icon| icon.inner)))
}
#[inline]
pub fn set_ime_cursor_area(&self, position: Position, size: Size) {
x11_or_wayland!(match self; Window(w) => w.set_ime_cursor_area(position, size))
}
#[inline]
pub fn reset_dead_keys(&self) {
common::xkb::reset_dead_keys()
}
#[inline]
pub fn set_ime_allowed(&self, allowed: bool) {
x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed))
}
#[inline]
pub fn set_ime_purpose(&self, purpose: ImePurpose) {
x11_or_wayland!(match self; Window(w) => w.set_ime_purpose(purpose))
}
#[inline]
pub fn focus_window(&self) {
x11_or_wayland!(match self; Window(w) => w.focus_window())
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type))
}
#[inline]
pub fn request_redraw(&self) {
x11_or_wayland!(match self; Window(w) => w.request_redraw())
}
#[inline]
pub fn pre_present_notify(&self) {
x11_or_wayland!(match self; Window(w) => w.pre_present_notify())
}
#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
Some(x11_or_wayland!(match self; Window(w) => w.current_monitor()?; as MonitorHandle))
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match self {
#[cfg(x11_platform)]
Window::X(ref window) => {
window.available_monitors().into_iter().map(MonitorHandle::X).collect()
},
#[cfg(wayland_platform)]
Window::Wayland(ref window) => {
window.available_monitors().into_iter().map(MonitorHandle::Wayland).collect()
},
}
}
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(x11_or_wayland!(match self; Window(w) => w.primary_monitor()?; as MonitorHandle))
}
#[cfg(feature = "rwh_04")]
#[inline]
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_04())
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_05())
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_05())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_06())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_06())
}
#[inline]
pub fn set_theme(&self, theme: Option<Theme>) {
x11_or_wayland!(match self; Window(window) => window.set_theme(theme))
}
#[inline]
pub fn theme(&self) -> Option<Theme> {
x11_or_wayland!(match self; Window(window) => window.theme())
}
pub fn set_content_protected(&self, protected: bool) {
x11_or_wayland!(match self; Window(window) => window.set_content_protected(protected))
}
#[inline]
pub fn has_focus(&self) -> bool {
x11_or_wayland!(match self; Window(window) => window.has_focus())
}
pub fn title(&self) -> String {
x11_or_wayland!(match self; Window(window) => window.title())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra { pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>, pub text_with_all_modifiers: Option<SmolStr>,
@@ -221,18 +643,18 @@ pub(crate) enum PlatformCustomCursor {
/// Hooks for X11 errors. /// Hooks for X11 errors.
#[cfg(x11_platform)] #[cfg(x11_platform)]
pub(crate) static XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new()); pub(crate) static mut XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
#[cfg(x11_platform)] #[cfg(x11_platform)]
unsafe extern "C" fn x_error_callback( unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display, display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent, event: *mut x11::ffi::XErrorEvent,
) -> c_int { ) -> c_int {
let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()); let xconn_lock = X11_BACKEND.lock().unwrap();
if let Ok(ref xconn) = *xconn_lock { if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks. // Call all the hooks.
let mut error_handled = false; let mut error_handled = false;
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() { for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() {
error_handled |= hook(display as *mut _, event as *mut _); error_handled |= hook(display as *mut _, event as *mut _);
} }
@@ -270,14 +692,27 @@ unsafe extern "C" fn x_error_callback(
0 0
} }
pub enum EventLoop { pub enum EventLoop<T: 'static> {
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
Wayland(Box<wayland::EventLoop>), Wayland(Box<wayland::EventLoop<T>>),
#[cfg(x11_platform)] #[cfg(x11_platform)]
X(x11::EventLoop), X(x11::EventLoop<T>),
} }
impl EventLoop { pub enum EventLoopProxy<T: 'static> {
#[cfg(x11_platform)]
X(x11::EventLoopProxy<T>),
#[cfg(wayland_platform)]
Wayland(wayland::EventLoopProxy<T>),
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy)
}
}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new( pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes, attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> { ) -> Result<Self, EventLoopError> {
@@ -286,8 +721,8 @@ impl EventLoop {
"Initializing the event loop outside of the main thread is a significant \ "Initializing the event loop outside of the main thread is a significant \
cross-platform compatibility hazard. If you absolutely need to create an \ cross-platform compatibility hazard. If you absolutely need to create an \
EventLoop on a different thread, you can use the \ EventLoop on a different thread, you can use the \
`EventLoopBuilderExtX11::with_any_thread` or \ `EventLoopBuilderExtX11::any_thread` or `EventLoopBuilderExtWayland::any_thread` \
`EventLoopBuilderExtWayland::with_any_thread` functions." functions."
); );
} }
@@ -322,7 +757,7 @@ impl EventLoop {
} else { } else {
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set." "neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
}; };
return Err(NotSupportedError::new(msg).into()); return Err(EventLoopError::Os(os_error!(OsError::Misc(msg))));
}, },
}; };
@@ -336,15 +771,15 @@ impl EventLoop {
} }
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
fn new_wayland_any_thread() -> Result<EventLoop, EventLoopError> { fn new_wayland_any_thread() -> Result<EventLoop<T>, EventLoopError> {
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp))) wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
} }
#[cfg(x11_platform)] #[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop, EventLoopError> { fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> {
let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() { let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
Ok(xconn) => xconn.clone(), Ok(xconn) => xconn.clone(),
Err(err) => return Err(os_error!(err.clone()).into()), Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
}; };
Ok(EventLoop::X(x11::EventLoop::new(xconn))) Ok(EventLoop::X(x11::EventLoop::new(xconn)))
@@ -360,42 +795,217 @@ impl EventLoop {
} }
} }
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> { pub fn create_proxy(&self) -> EventLoopProxy<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app)) x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
} }
pub fn run_app_on_demand<A: ApplicationHandler>( pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
&mut self, where
app: A, F: FnMut(crate::event::Event<T>, &RootELW),
) -> Result<(), EventLoopError> { {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app_on_demand(app)) self.run_on_demand(callback)
} }
pub fn pump_app_events<A: ApplicationHandler>( pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
&mut self, where
timeout: Option<Duration>, F: FnMut(crate::event::Event<T>, &RootELW),
app: A, {
) -> PumpStatus { x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback))
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_app_events(timeout, app))
} }
pub fn window_target(&self) -> &dyn ActiveEventLoop { pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
}
pub fn window_target(&self) -> &crate::event_loop::ActiveEventLoop {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target()) x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target())
} }
} }
impl AsFd for EventLoop { impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> { fn as_fd(&self) -> BorrowedFd<'_> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd()) x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd())
} }
} }
impl AsRawFd for EventLoop { impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd { fn as_raw_fd(&self) -> RawFd {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd()) x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd())
} }
} }
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event))
}
}
pub enum ActiveEventLoop {
#[cfg(wayland_platform)]
Wayland(wayland::ActiveEventLoop),
#[cfg(x11_platform)]
X(x11::ActiveEventLoop),
}
impl ActiveEventLoop {
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
#[cfg(wayland_platform)]
ActiveEventLoop::Wayland(_) => true,
#[cfg(x11_platform)]
_ => false,
}
}
pub fn create_custom_cursor(&self, cursor: CustomCursorSource) -> CustomCursor {
x11_or_wayland!(match self; ActiveEventLoop(evlp) => evlp.create_custom_cursor(cursor))
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match *self {
#[cfg(wayland_platform)]
ActiveEventLoop::Wayland(ref evlp) => {
evlp.available_monitors().map(MonitorHandle::Wayland).collect()
},
#[cfg(x11_platform)]
ActiveEventLoop::X(ref evlp) => {
evlp.available_monitors().map(MonitorHandle::X).collect()
},
}
}
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(
x11_or_wayland!(match self; ActiveEventLoop(evlp) => evlp.primary_monitor()?; as MonitorHandle),
)
}
#[inline]
pub fn listen_device_events(&self, allowed: DeviceEvents) {
x11_or_wayland!(match self; Self(evlp) => evlp.listen_device_events(allowed))
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_06())
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_control_flow(control_flow))
}
pub(crate) fn control_flow(&self) -> ControlFlow {
x11_or_wayland!(match self; Self(evlp) => evlp.control_flow())
}
pub(crate) fn clear_exit(&self) {
x11_or_wayland!(match self; Self(evlp) => evlp.clear_exit())
}
pub(crate) fn exit(&self) {
x11_or_wayland!(match self; Self(evlp) => evlp.exit())
}
pub(crate) fn exiting(&self) -> bool {
x11_or_wayland!(match self; Self(evlp) => evlp.exiting())
}
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
match self {
#[cfg(x11_platform)]
Self::X(conn) => OwnedDisplayHandle::X(conn.x_connection().clone()),
#[cfg(wayland_platform)]
Self::Wayland(conn) => OwnedDisplayHandle::Wayland(conn.connection.clone()),
}
}
#[allow(dead_code)]
fn set_exit_code(&self, code: i32) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code))
}
#[allow(dead_code)]
fn exit_code(&self) -> Option<i32> {
x11_or_wayland!(match self; Self(evlp) => evlp.exit_code())
}
}
#[derive(Clone)]
#[allow(dead_code)]
pub(crate) enum OwnedDisplayHandle {
#[cfg(x11_platform)]
X(Arc<XConnection>),
#[cfg(wayland_platform)]
Wayland(wayland_client::Connection),
}
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
match self {
#[cfg(x11_platform)]
Self::X(xconn) => {
let mut xlib_handle = rwh_05::XlibDisplayHandle::empty();
xlib_handle.display = xconn.display.cast();
xlib_handle.screen = xconn.default_screen_index() as _;
xlib_handle.into()
},
#[cfg(wayland_platform)]
Self::Wayland(conn) => {
use sctk::reexports::client::Proxy;
let mut wayland_handle = rwh_05::WaylandDisplayHandle::empty();
wayland_handle.display = conn.display().id().as_ptr() as *mut _;
wayland_handle.into()
},
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
use std::ptr::NonNull;
match self {
#[cfg(x11_platform)]
Self::X(xconn) => Ok(rwh_06::XlibDisplayHandle::new(
NonNull::new(xconn.display.cast()),
xconn.default_screen_index() as _,
)
.into()),
#[cfg(wayland_platform)]
Self::Wayland(conn) => {
use sctk::reexports::client::Proxy;
Ok(rwh_06::WaylandDisplayHandle::new(
NonNull::new(conn.display().id().as_ptr().cast()).unwrap(),
)
.into())
},
}
}
}
/// Returns the minimum `Option<Duration>`, taking into account that `None` /// Returns the minimum `Option<Duration>`, 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`)

View File

@@ -2,44 +2,44 @@
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::io::Result as IOResult; use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem; use std::mem;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::rc::Rc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource; use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::{globals, Connection, QueueHandle}; use sctk::reexports::client::{globals, Connection, QueueHandle};
use crate::application::ApplicationHandler;
use crate::cursor::OnlyCursorImage; use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize; use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, OsError, RequestError}; use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent}; use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{ use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents};
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::platform::pump_events::PumpStatus; use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::min_timeout; use crate::platform_impl::platform::min_timeout;
use crate::platform_impl::PlatformCustomCursor; use crate::platform_impl::{
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme}; ActiveEventLoop as PlatformActiveEventLoop, OsError, PlatformCustomCursor,
};
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource};
mod proxy; mod proxy;
pub mod sink; pub mod sink;
use proxy::EventLoopProxy; pub use proxy::EventLoopProxy;
use sink::EventSink; use sink::EventSink;
use super::state::{WindowCompositorUpdate, WinitState}; use super::state::{WindowCompositorUpdate, WinitState};
use super::window::state::FrameCallbackState; use super::window::state::FrameCallbackState;
use super::{logical_to_physical_rounded, WindowId}; use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
pub use crate::event_loop::EventLoopProxy as CoreEventLoopProxy;
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>; type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
/// The Wayland event loop. /// The Wayland event loop.
pub struct EventLoop { pub struct EventLoop<T: 'static> {
/// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop /// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop
loop_running: bool, loop_running: bool,
@@ -47,37 +47,52 @@ pub struct EventLoop {
compositor_updates: Vec<WindowCompositorUpdate>, compositor_updates: Vec<WindowCompositorUpdate>,
window_ids: Vec<WindowId>, window_ids: Vec<WindowId>,
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
// XXX can't remove RefCell out of here, unless we can plumb generics into the `Window`, which
// we don't really want, since it'll break public API by a lot.
/// Pending events from the user.
pending_user_events: Rc<RefCell<Vec<T>>>,
/// The Wayland dispatcher to has raw access to the queue when needed, such as /// The Wayland dispatcher to has raw access to the queue when needed, such as
/// when creating a new window. /// when creating a new window.
wayland_dispatcher: WaylandDispatcher, wayland_dispatcher: WaylandDispatcher,
/// Connection to the wayland server. /// Connection to the wayland server.
handle: Arc<OwnedDisplayHandle>, connection: Connection,
/// Event loop window target. /// Event loop window target.
active_event_loop: ActiveEventLoop, window_target: RootActiveEventLoop,
// XXX drop after everything else, just to be safe. // XXX drop after everything else, just to be safe.
/// Calloop's event loop. /// Calloop's event loop.
event_loop: calloop::EventLoop<'static, WinitState>, event_loop: calloop::EventLoop<'static, WinitState>,
} }
impl EventLoop { impl<T: 'static> EventLoop<T> {
pub fn new() -> Result<EventLoop, EventLoopError> { pub fn new() -> Result<EventLoop<T>, EventLoopError> {
let connection = Connection::connect_to_env().map_err(|err| os_error!(err))?; macro_rules! map_err {
($e:expr, $err:expr) => {
$e.map_err(|error| os_error!($err(error).into()))
};
}
let connection = map_err!(Connection::connect_to_env(), WaylandError::Connection)?;
let (globals, mut event_queue) = let (globals, mut event_queue) =
globals::registry_queue_init(&connection).map_err(|err| os_error!(err))?; map_err!(globals::registry_queue_init(&connection), WaylandError::Global)?;
let queue_handle = event_queue.handle(); let queue_handle = event_queue.handle();
let event_loop = let event_loop =
calloop::EventLoop::<WinitState>::try_new().map_err(|err| os_error!(err))?; map_err!(calloop::EventLoop::<WinitState>::try_new(), WaylandError::Calloop)?;
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?; let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())
.map_err(|error| os_error!(error))?;
// NOTE: do a roundtrip after binding the globals to prevent potential // NOTE: do a roundtrip after binding the globals to prevent potential
// races with the server. // races with the server.
event_queue.roundtrip(&mut winit_state).map_err(|err| os_error!(err))?; map_err!(event_queue.roundtrip(&mut winit_state), WaylandError::Dispatch)?;
// Register Wayland source. // Register Wayland source.
let wayland_source = WaylandSource::new(connection.clone(), event_queue); let wayland_source = WaylandSource::new(connection.clone(), event_queue);
@@ -93,39 +108,46 @@ impl EventLoop {
result result
}); });
event_loop map_err!(
.handle() event_loop.handle().register_dispatcher(wayland_dispatcher.clone()),
.register_dispatcher(wayland_dispatcher.clone()) WaylandError::Calloop
.map_err(|err| os_error!(err))?; )?;
// Setup the user proxy. // Setup the user proxy.
let (ping, ping_source) = calloop::ping::make_ping().unwrap(); let pending_user_events = Rc::new(RefCell::new(Vec::new()));
event_loop let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel();
let result = event_loop
.handle() .handle()
.insert_source(ping_source, move |_, _, winit_state: &mut WinitState| { .insert_source(user_events_channel, move |event, _, winit_state: &mut WinitState| {
winit_state.dispatched_events = true; if let calloop::channel::Event::Msg(msg) = event {
winit_state.proxy_wake_up = true; winit_state.dispatched_events = true;
pending_user_events_clone.borrow_mut().push(msg);
}
}) })
.map_err(|err| os_error!(err))?; .map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
// An event's loop awakener to wake up for window events from winit's windows. // An event's loop awakener to wake up for window events from winit's windows.
let (event_loop_awakener, event_loop_awakener_source) = let (event_loop_awakener, event_loop_awakener_source) = map_err!(
calloop::ping::make_ping().map_err(|err| os_error!(err))?; calloop::ping::make_ping()
.map_err(|error| CalloopError::OtherError(Box::new(error).into())),
WaylandError::Calloop
)?;
event_loop let result = event_loop
.handle() .handle()
.insert_source(event_loop_awakener_source, move |_, _, winit_state: &mut WinitState| { .insert_source(event_loop_awakener_source, move |_, _, winit_state: &mut WinitState| {
// Mark that we have something to dispatch. // Mark that we have something to dispatch.
winit_state.dispatched_events = true; winit_state.dispatched_events = true;
}) })
.map_err(|err| os_error!(err))?; .map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
let handle = Arc::new(OwnedDisplayHandle::new(connection)); let window_target = ActiveEventLoop {
let active_event_loop = ActiveEventLoop { connection: connection.clone(),
handle: handle.clone(),
wayland_dispatcher: wayland_dispatcher.clone(), wayland_dispatcher: wayland_dispatcher.clone(),
event_loop_awakener, event_loop_awakener,
event_loop_proxy: EventLoopProxy::new(ping).into(),
queue_handle, queue_handle,
control_flow: Cell::new(ControlFlow::default()), control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(None), exit: Cell::new(None),
@@ -137,26 +159,26 @@ impl EventLoop {
compositor_updates: Vec::new(), compositor_updates: Vec::new(),
buffer_sink: EventSink::default(), buffer_sink: EventSink::default(),
window_ids: Vec::new(), window_ids: Vec::new(),
handle, connection,
wayland_dispatcher, wayland_dispatcher,
user_events_sender,
pending_user_events,
event_loop, event_loop,
active_event_loop, window_target: RootActiveEventLoop {
p: PlatformActiveEventLoop::Wayland(window_target),
_marker: PhantomData,
},
}; };
Ok(event_loop) Ok(event_loop)
} }
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> { pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
self.run_app_on_demand(app) where
} F: FnMut(Event<T>, &RootActiveEventLoop),
{
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
mut app: A,
) -> Result<(), EventLoopError> {
self.active_event_loop.clear_exit();
let exit = loop { let exit = loop {
match self.pump_app_events(None, &mut app) { match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => { PumpStatus::Exit(0) => {
break Ok(()); break Ok(());
}, },
@@ -178,27 +200,26 @@ impl EventLoop {
exit exit
} }
pub fn pump_app_events<A: ApplicationHandler>( pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
&mut self, where
timeout: Option<Duration>, F: FnMut(Event<T>, &RootActiveEventLoop),
mut app: A, {
) -> PumpStatus {
if !self.loop_running { if !self.loop_running {
self.loop_running = true; self.loop_running = true;
// Run the initial loop iteration. // Run the initial loop iteration.
self.single_iteration(&mut app, StartCause::Init); self.single_iteration(&mut callback, StartCause::Init);
} }
// 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 app); self.poll_events_with_timeout(timeout, &mut callback);
} }
if let Some(code) = self.exit_code() { if let Some(code) = self.exit_code() {
self.loop_running = false; self.loop_running = false;
app.exiting(&self.active_event_loop); callback(Event::LoopExiting, self.window_target());
PumpStatus::Exit(code) PumpStatus::Exit(code)
} else { } else {
@@ -206,11 +227,10 @@ impl EventLoop {
} }
} }
fn poll_events_with_timeout<A: ApplicationHandler>( pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
&mut self, where
mut timeout: Option<Duration>, F: FnMut(Event<T>, &RootActiveEventLoop),
app: &mut A, {
) {
let cause = loop { let cause = loop {
let start = Instant::now(); let start = Instant::now();
@@ -231,7 +251,7 @@ impl EventLoop {
// //
// Checking for flush error is essential to perform an exit with error, since // Checking for flush error is essential to perform an exit with error, since
// once we have a protocol error, we could get stuck retrying... // once we have a protocol error, we could get stuck retrying...
if self.handle.connection.flush().is_err() { if self.connection.flush().is_err() {
self.set_exit_code(1); self.set_exit_code(1);
return; return;
} }
@@ -272,10 +292,13 @@ impl EventLoop {
break cause; break cause;
}; };
self.single_iteration(app, cause); self.single_iteration(&mut callback, cause);
} }
fn single_iteration<A: ApplicationHandler>(&mut self, app: &mut A, cause: StartCause) { fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
// NOTE currently just indented to simplify the diff // NOTE currently just indented to simplify the diff
// We retain these grow-only scratch buffers as part of the EventLoop // We retain these grow-only scratch buffers as part of the EventLoop
@@ -286,17 +309,18 @@ impl EventLoop {
let mut buffer_sink = std::mem::take(&mut self.buffer_sink); let mut buffer_sink = std::mem::take(&mut self.buffer_sink);
let mut window_ids = std::mem::take(&mut self.window_ids); let mut window_ids = std::mem::take(&mut self.window_ids);
app.new_events(&self.active_event_loop, cause); callback(Event::NewEvents(cause), &self.window_target);
// NB: For consistency all platforms must call `can_create_surfaces` even though Wayland // NB: For consistency all platforms must emit a 'resumed' event even though Wayland
// applications don't themselves have a formal surface destroy/create lifecycle. // applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init { if cause == StartCause::Init {
app.can_create_surfaces(&self.active_event_loop); callback(Event::Resumed, &self.window_target);
} }
// Indicate user wake up. // Handle pending user events. We don't need back buffer, since we can't dispatch
if self.with_state(|state| mem::take(&mut state.proxy_wake_up)) { // user events indirectly via callback to the user.
app.proxy_wake_up(&self.active_event_loop); for user_event in self.pending_user_events.borrow_mut().drain(..) {
callback(Event::UserEvent(user_event), &self.window_target);
} }
// Drain the pending compositor updates. // Drain the pending compositor updates.
@@ -309,23 +333,29 @@ impl EventLoop {
let windows = state.windows.get_mut(); let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap(); let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor(); let scale_factor = window.scale_factor();
let size = logical_to_physical_rounded(window.surface_size(), scale_factor); let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
(size, scale_factor) (size, scale_factor)
}); });
// Stash the old window size. // Stash the old window size.
let old_physical_size = physical_size; let old_physical_size = physical_size;
let new_surface_size = Arc::new(Mutex::new(physical_size)); let new_inner_size = Arc::new(Mutex::new(physical_size));
let event = WindowEvent::ScaleFactorChanged { callback(
scale_factor, Event::WindowEvent {
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)), window_id: crate::window::WindowId(window_id),
}; event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
},
},
&self.window_target,
);
app.window_event(&self.active_event_loop, window_id, event); let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let physical_size = *new_surface_size.lock().unwrap();
drop(new_surface_size);
// Resize the window when user altered the size. // Resize the window when user altered the size.
if old_physical_size != physical_size { if old_physical_size != physical_size {
@@ -335,7 +365,7 @@ impl EventLoop {
let new_logical_size: LogicalSize<f64> = let new_logical_size: LogicalSize<f64> =
physical_size.to_logical(scale_factor); physical_size.to_logical(scale_factor);
window.request_surface_size(new_logical_size.into()); window.request_inner_size(new_logical_size.into());
}); });
// Make it queue resize. // Make it queue resize.
@@ -351,7 +381,7 @@ impl EventLoop {
let window = windows.get(&window_id).unwrap().lock().unwrap(); let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor(); let scale_factor = window.scale_factor();
let size = logical_to_physical_rounded(window.surface_size(), scale_factor); let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
// Mark the window as needed a redraw. // Mark the window as needed a redraw.
state state
@@ -365,12 +395,23 @@ impl EventLoop {
size size
}); });
let event = WindowEvent::SurfaceResized(physical_size); callback(
app.window_event(&self.active_event_loop, window_id, event); Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
);
} }
if compositor_update.close_window { if compositor_update.close_window {
app.window_event(&self.active_event_loop, window_id, WindowEvent::CloseRequested); callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
);
} }
} }
@@ -379,15 +420,8 @@ impl EventLoop {
buffer_sink.append(&mut state.window_events_sink.lock().unwrap()); buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
}); });
for event in buffer_sink.drain() { for event in buffer_sink.drain() {
match event { let event = event.map_nonuser_event().unwrap();
Event::WindowEvent { window_id, event } => { callback(event, &self.window_target);
app.window_event(&self.active_event_loop, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.active_event_loop, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
} }
// Handle non-synthetic events. // Handle non-synthetic events.
@@ -395,15 +429,8 @@ impl EventLoop {
buffer_sink.append(&mut state.events_sink); buffer_sink.append(&mut state.events_sink);
}); });
for event in buffer_sink.drain() { for event in buffer_sink.drain() {
match event { let event = event.map_nonuser_event().unwrap();
Event::WindowEvent { window_id, event } => { callback(event, &self.window_target);
app.window_event(&self.active_event_loop, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.active_event_loop, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
} }
// Collect the window ids // Collect the window ids
@@ -439,7 +466,10 @@ impl EventLoop {
}); });
if let Some(event) = event { if let Some(event) = event {
app.window_event(&self.active_event_loop, *window_id, event); callback(
Event::WindowEvent { window_id: crate::window::WindowId(*window_id), event },
&self.window_target,
);
} }
} }
@@ -449,7 +479,7 @@ impl EventLoop {
}); });
// This is always the last event we dispatch before poll again // This is always the last event we dispatch before poll again
app.about_to_wait(&self.active_event_loop); callback(Event::AboutToWait, &self.window_target);
// Update the window frames and schedule redraws. // Update the window frames and schedule redraws.
let mut wake_up = false; let mut wake_up = false;
@@ -478,7 +508,13 @@ impl EventLoop {
// If the user draws from the `AboutToWait` this is likely not required, however // If the user draws from the `AboutToWait` this is likely not required, however
// we can't do much about it. // we can't do much about it.
if wake_up { if wake_up {
self.active_event_loop.event_loop_awakener.ping(); match &self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => {
window_target.event_loop_awakener.ping();
},
#[cfg(x11_platform)]
PlatformActiveEventLoop::X(_) => unreachable!(),
}
} }
std::mem::swap(&mut self.compositor_updates, &mut compositor_updates); std::mem::swap(&mut self.compositor_updates, &mut compositor_updates);
@@ -487,17 +523,31 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn window_target(&self) -> &dyn RootActiveEventLoop { pub fn create_proxy(&self) -> EventLoopProxy<T> {
&self.active_event_loop EventLoopProxy::new(self.user_events_sender.clone())
}
#[inline]
pub fn window_target(&self) -> &RootActiveEventLoop {
&self.window_target
} }
fn with_state<'a, U: 'a, F: FnOnce(&'a mut WinitState) -> U>(&'a mut self, callback: F) -> U { fn with_state<'a, U: 'a, F: FnOnce(&'a mut WinitState) -> U>(&'a mut self, callback: F) -> U {
let state = self.active_event_loop.state.get_mut(); let state = match &mut self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(x11_platform)]
_ => unreachable!(),
};
callback(state) callback(state)
} }
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> { fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
let state = &mut self.active_event_loop.state.get_mut(); let state = match &mut self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
self.event_loop.dispatch(timeout, state).map_err(|error| { self.event_loop.dispatch(timeout, state).map_err(|error| {
tracing::error!("Error dispatching event loop: {}", error); tracing::error!("Error dispatching event loop: {}", error);
@@ -505,47 +555,50 @@ impl EventLoop {
}) })
} }
fn roundtrip(&mut self) -> Result<usize, OsError> { fn roundtrip(&mut self) -> Result<usize, RootOsError> {
let state = &mut self.active_event_loop.state.get_mut(); let state = match &mut self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
let mut wayland_source = self.wayland_dispatcher.as_source_mut(); let mut wayland_source = self.wayland_dispatcher.as_source_mut();
let event_queue = wayland_source.queue(); let event_queue = wayland_source.queue();
event_queue.roundtrip(state).map_err(|err| os_error!(err)) event_queue.roundtrip(state).map_err(|error| {
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
})
} }
fn control_flow(&self) -> ControlFlow { fn control_flow(&self) -> ControlFlow {
self.active_event_loop.control_flow() self.window_target.p.control_flow()
} }
fn exiting(&self) -> bool { fn exiting(&self) -> bool {
self.active_event_loop.exiting() self.window_target.p.exiting()
} }
fn set_exit_code(&self, code: i32) { fn set_exit_code(&self, code: i32) {
self.active_event_loop.set_exit_code(code) self.window_target.p.set_exit_code(code)
} }
fn exit_code(&self) -> Option<i32> { fn exit_code(&self) -> Option<i32> {
self.active_event_loop.exit_code() self.window_target.p.exit_code()
} }
} }
impl AsFd for EventLoop { impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> { fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd() self.event_loop.as_fd()
} }
} }
impl AsRawFd for EventLoop { impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd { fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd() self.event_loop.as_raw_fd()
} }
} }
pub struct ActiveEventLoop { pub struct ActiveEventLoop {
/// Event loop proxy
event_loop_proxy: CoreEventLoopProxy,
/// The event loop wakeup source. /// The event loop wakeup source.
pub event_loop_awakener: calloop::ping::Ping, pub event_loop_awakener: calloop::ping::Ping,
@@ -565,121 +618,69 @@ pub struct ActiveEventLoop {
/// Dispatcher of Wayland events. /// Dispatcher of Wayland events.
pub wayland_dispatcher: WaylandDispatcher, pub wayland_dispatcher: WaylandDispatcher,
/// Handle for the underlying event loop. /// Connection to the wayland server.
pub handle: Arc<OwnedDisplayHandle>, pub connection: Connection,
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> CoreEventLoopProxy {
self.event_loop_proxy.clone()
}
fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
fn exit(&self) {
self.exit.set(Some(0))
}
fn exiting(&self) -> bool {
self.exit.get().is_some()
}
#[inline]
fn listen_device_events(&self, _allowed: DeviceEvents) {}
fn create_custom_cursor(
&self,
cursor: CustomCursorSource,
) -> Result<RootCustomCursor, RequestError> {
Ok(RootCustomCursor {
inner: PlatformCustomCursor::Wayland(OnlyCursorImage(Arc::from(cursor.inner.0))),
})
}
#[inline]
fn system_theme(&self) -> Option<Theme> {
None
}
fn create_window(
&self,
window_attributes: crate::window::WindowAttributes,
) -> Result<Box<dyn crate::window::Window>, RequestError> {
let window = crate::platform_impl::wayland::Window::new(self, window_attributes)?;
Ok(Box::new(window))
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = crate::monitor::MonitorHandle>> {
Box::new(
self.state
.borrow()
.output_state
.outputs()
.map(crate::platform_impl::wayland::output::MonitorHandle::new)
.map(crate::platform_impl::MonitorHandle::Wayland)
.map(|inner| crate::monitor::MonitorHandle { inner }),
)
}
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
// There's no primary monitor on Wayland.
None
}
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(self.handle.clone())
}
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
} }
impl ActiveEventLoop { impl ActiveEventLoop {
fn clear_exit(&self) { pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub(crate) fn exit(&self) {
self.exit.set(Some(0))
}
pub(crate) fn clear_exit(&self) {
self.exit.set(None) self.exit.set(None)
} }
fn set_exit_code(&self, code: i32) { pub(crate) fn exiting(&self) -> bool {
self.exit.get().is_some()
}
pub(crate) fn set_exit_code(&self, code: i32) {
self.exit.set(Some(code)) self.exit.set(Some(code))
} }
fn exit_code(&self) -> Option<i32> { pub(crate) fn exit_code(&self) -> Option<i32> {
self.exit.get() self.exit.get()
} }
}
impl rwh_06::HasDisplayHandle for ActiveEventLoop { #[inline]
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> { pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
self.handle.display_handle()
pub(crate) fn create_custom_cursor(&self, cursor: CustomCursorSource) -> RootCustomCursor {
RootCustomCursor {
inner: PlatformCustomCursor::Wayland(OnlyCursorImage(Arc::from(cursor.inner.0))),
}
} }
}
pub struct OwnedDisplayHandle { #[cfg(feature = "rwh_05")]
pub(crate) connection: Connection, #[inline]
} pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
impl OwnedDisplayHandle {
fn new(connection: Connection) -> Self {
Self { connection }
}
}
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
use sctk::reexports::client::Proxy; use sctk::reexports::client::Proxy;
let raw = rwh_06::WaylandDisplayHandle::new({ let mut display_handle = rwh_05::WaylandDisplayHandle::empty();
display_handle.display = self.connection.display().id().as_ptr() as *mut _;
rwh_05::RawDisplayHandle::Wayland(display_handle)
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
use sctk::reexports::client::Proxy;
Ok(rwh_06::WaylandDisplayHandle::new({
let ptr = self.connection.display().id().as_ptr(); let ptr = self.connection.display().id().as_ptr();
std::ptr::NonNull::new(ptr as *mut _).expect("wl_display should never be null") std::ptr::NonNull::new(ptr as *mut _).expect("wl_display should never be null")
}); })
.into())
Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw.into()) })
} }
} }

View File

@@ -1,30 +1,28 @@
//! An event loop proxy. //! An event loop proxy.
use std::sync::Arc; use std::sync::mpsc::SendError;
use sctk::reexports::calloop::ping::Ping; use sctk::reexports::calloop::channel::Sender;
use crate::event_loop::{EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider}; use crate::event_loop::EventLoopClosed;
/// A handle that can be sent across the threads and used to wake up the `EventLoop`. /// A handle that can be sent across the threads and used to wake up the `EventLoop`.
pub struct EventLoopProxy { pub struct EventLoopProxy<T: 'static> {
ping: Ping, user_events_sender: Sender<T>,
} }
impl EventLoopProxyProvider for EventLoopProxy { impl<T: 'static> Clone for EventLoopProxy<T> {
fn wake_up(&self) { fn clone(&self) -> Self {
self.ping.ping(); EventLoopProxy { user_events_sender: self.user_events_sender.clone() }
} }
} }
impl EventLoopProxy { impl<T: 'static> EventLoopProxy<T> {
pub fn new(ping: Ping) -> Self { pub fn new(user_events_sender: Sender<T>) -> Self {
Self { ping } Self { user_events_sender }
} }
}
impl From<EventLoopProxy> for CoreEventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
fn from(value: EventLoopProxy) -> Self { self.user_events_sender.send(event).map_err(|SendError(error)| EventLoopClosed(error))
CoreEventLoopProxy::new(Arc::new(value))
} }
} }

View File

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

View File

@@ -1,14 +1,18 @@
//! Winit's Wayland backend. //! Winit's Wayland backend.
pub use event_loop::{ActiveEventLoop, EventLoop}; use std::fmt::Display;
pub use output::{MonitorHandle, VideoModeHandle}; use std::sync::Arc;
use sctk::reexports::client::globals::{BindError, GlobalError};
use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy; use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy};
pub use window::Window;
pub(super) use crate::cursor::OnlyCursorImage as CustomCursor; pub(super) use crate::cursor::OnlyCursorImage as CustomCursor;
use crate::dpi::{LogicalSize, PhysicalSize}; use crate::dpi::{LogicalSize, PhysicalSize};
use crate::window::WindowId; pub use crate::platform_impl::platform::{OsError, WindowId};
pub use event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
pub use output::{MonitorHandle, VideoModeHandle};
pub use window::Window;
mod event_loop; mod event_loop;
mod output; mod output;
@@ -17,10 +21,60 @@ mod state;
mod types; mod types;
mod window; mod window;
#[derive(Debug)]
pub enum WaylandError {
/// Error connecting to the socket.
Connection(ConnectError),
/// Error binding the global.
Global(GlobalError),
// Bind error.
Bind(BindError),
/// Error during the dispatching the event queue.
Dispatch(DispatchError),
/// Calloop error.
Calloop(calloop::Error),
/// Wayland
Wire(client::backend::WaylandError),
}
impl Display for WaylandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WaylandError::Connection(error) => error.fmt(f),
WaylandError::Global(error) => error.fmt(f),
WaylandError::Bind(error) => error.fmt(f),
WaylandError::Dispatch(error) => error.fmt(f),
WaylandError::Calloop(error) => error.fmt(f),
WaylandError::Wire(error) => error.fmt(f),
}
}
}
impl From<WaylandError> for OsError {
fn from(value: WaylandError) -> Self {
Self::WaylandError(Arc::new(value))
}
}
/// Dummy device id, since Wayland doesn't have device events.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub const fn dummy() -> Self {
DeviceId
}
}
/// Get the WindowId out of the surface. /// Get the WindowId out of the surface.
#[inline] #[inline]
fn make_wid(surface: &WlSurface) -> WindowId { fn make_wid(surface: &WlSurface) -> WindowId {
WindowId::from_raw(surface.id().as_ptr() as usize) WindowId(surface.id().as_ptr() as u64)
} }
/// The default routine does floor, but we need round on Wayland. /// The default routine does floor, but we need round on Wayland.

View File

@@ -1,12 +1,26 @@
use std::num::{NonZeroU16, NonZeroU32};
use sctk::output::{Mode, OutputData};
use sctk::reexports::client::protocol::wl_output::WlOutput; use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Proxy; use sctk::reexports::client::Proxy;
use sctk::output::OutputData;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::platform_impl::platform::VideoModeHandle as PlatformVideoModeHandle; use crate::platform_impl::platform::VideoModeHandle as PlatformVideoModeHandle;
use super::event_loop::ActiveEventLoop;
impl ActiveEventLoop {
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.state.borrow().output_state.outputs().map(MonitorHandle::new)
}
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
// There's no primary monitor on Wayland.
None
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MonitorHandle { pub struct MonitorHandle {
pub(crate) proxy: WlOutput, pub(crate) proxy: WlOutput,
@@ -31,9 +45,23 @@ impl MonitorHandle {
} }
#[inline] #[inline]
pub fn position(&self) -> Option<PhysicalPosition<i32>> { pub fn size(&self) -> PhysicalSize<u32> {
let output_data = self.proxy.data::<OutputData>().unwrap(); let output_data = self.proxy.data::<OutputData>().unwrap();
Some(output_data.with_output_info(|info| { let dimensions = output_data.with_output_info(|info| {
info.modes.iter().find_map(|mode| mode.current.then_some(mode.dimensions))
});
match dimensions {
Some((width, height)) => (width as u32, height as u32),
_ => (0, 0),
}
.into()
}
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| {
info.logical_position.map_or_else( info.logical_position.map_or_else(
|| { || {
LogicalPosition::<i32>::from(info.location) LogicalPosition::<i32>::from(info.location)
@@ -44,7 +72,15 @@ impl MonitorHandle {
.to_physical(info.scale_factor as f64) .to_physical(info.scale_factor as f64)
}, },
) )
})) })
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| {
info.modes.iter().find_map(|mode| mode.current.then_some(mode.refresh_rate as u32))
})
} }
#[inline] #[inline]
@@ -53,18 +89,6 @@ impl MonitorHandle {
output_data.scale_factor() output_data.scale_factor()
} }
#[inline]
pub fn current_video_mode(&self) -> Option<PlatformVideoModeHandle> {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| {
let mode = info.modes.iter().find(|mode| mode.current).cloned();
mode.map(|mode| {
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(self.clone(), mode))
})
})
}
#[inline] #[inline]
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> { pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> {
let output_data = self.proxy.data::<OutputData>().unwrap(); let output_data = self.proxy.data::<OutputData>().unwrap();
@@ -73,7 +97,12 @@ impl MonitorHandle {
let monitor = self.clone(); let monitor = self.clone();
modes.into_iter().map(move |mode| { modes.into_iter().map(move |mode| {
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(monitor.clone(), mode)) PlatformVideoModeHandle::Wayland(VideoModeHandle {
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
refresh_rate_millihertz: mode.refresh_rate as u32,
bit_depth: 32,
monitor: monitor.clone(),
})
}) })
} }
} }
@@ -107,31 +136,24 @@ impl std::hash::Hash for MonitorHandle {
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle { pub struct VideoModeHandle {
pub(crate) size: PhysicalSize<u32>, pub(crate) size: PhysicalSize<u32>,
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>, pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) monitor: MonitorHandle, pub(crate) monitor: MonitorHandle,
} }
impl VideoModeHandle { impl VideoModeHandle {
fn new(monitor: MonitorHandle, mode: Mode) -> Self {
VideoModeHandle {
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
refresh_rate_millihertz: NonZeroU32::new(mode.refresh_rate as u32),
monitor: monitor.clone(),
}
}
#[inline] #[inline]
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
self.size self.size
} }
#[inline] #[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> { pub fn bit_depth(&self) -> u16 {
None self.bit_depth
} }
#[inline] #[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> { pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz self.refresh_rate_millihertz
} }

View File

@@ -5,19 +5,21 @@ use std::time::Duration;
use calloop::timer::{TimeoutAction, Timer}; use calloop::timer::{TimeoutAction, Timer};
use calloop::{LoopHandle, RegistrationToken}; use calloop::{LoopHandle, RegistrationToken};
use tracing::warn;
use sctk::reexports::client::protocol::wl_keyboard::{ use sctk::reexports::client::protocol::wl_keyboard::{
Event as WlKeyboardEvent, KeyState as WlKeyState, KeymapFormat as WlKeymapFormat, WlKeyboard, Event as WlKeyboardEvent, KeyState as WlKeyState, KeymapFormat as WlKeymapFormat, WlKeyboard,
}; };
use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum}; use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum};
use tracing::warn;
use crate::event::{ElementState, WindowEvent}; use crate::event::{ElementState, WindowEvent};
use crate::keyboard::ModifiersState; use crate::keyboard::ModifiersState;
use crate::platform_impl::common::xkb::Context; use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::wayland::event_loop::sink::EventSink; use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, WindowId}; use crate::platform_impl::wayland::{self, DeviceId, WindowId};
impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState { impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
fn event( fn event(
@@ -369,9 +371,10 @@ fn key_input(
None => return, None => return,
}; };
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
if let Some(mut key_context) = keyboard_state.xkb_context.key_context() { if let Some(mut key_context) = keyboard_state.xkb_context.key_context() {
let event = key_context.process_key_event(keycode, state, repeat); let event = key_context.process_key_event(keycode, state, repeat);
let event = WindowEvent::KeyboardInput { device_id: None, event, is_synthetic: false }; let event = WindowEvent::KeyboardInput { device_id, event, is_synthetic: false };
event_sink.push_window_event(event, window_id); event_sink.push_window_event(event, window_id);
} }
} }

View File

@@ -3,15 +3,17 @@
use std::sync::Arc; use std::sync::Arc;
use ahash::AHashMap; use ahash::AHashMap;
use tracing::warn;
use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_touch::WlTouch; use sctk::reexports::client::protocol::wl_touch::WlTouch;
use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1; use sctk::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;
use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
use tracing::warn;
use crate::event::WindowEvent; use crate::event::WindowEvent;
use crate::keyboard::ModifiersState; use crate::keyboard::ModifiersState;
@@ -22,11 +24,12 @@ mod pointer;
mod text_input; mod text_input;
mod touch; mod touch;
use keyboard::{KeyboardData, KeyboardState};
pub use pointer::relative_pointer::RelativePointerState; pub use pointer::relative_pointer::RelativePointerState;
pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt}; pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt};
use text_input::TextInputData;
pub use text_input::{TextInputState, ZwpTextInputV3Ext}; pub use text_input::{TextInputState, ZwpTextInputV3Ext};
use keyboard::{KeyboardData, KeyboardState};
use text_input::TextInputData;
use touch::TouchPoint; use touch::TouchPoint;
#[derive(Debug, Default)] #[derive(Debug, Default)]
@@ -40,9 +43,6 @@ pub struct WinitSeatState {
/// The mapping from touched points to the surfaces they're present. /// The mapping from touched points to the surfaces they're present.
touch_map: AHashMap<i32, TouchPoint>, touch_map: AHashMap<i32, TouchPoint>,
/// Id of the first touch event.
first_touch_id: Option<i32>,
/// The text input bound on the seat. /// The text input bound on the seat.
text_input: Option<Arc<ZwpTextInputV3>>, text_input: Option<Arc<ZwpTextInputV3>>,

View File

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

View File

@@ -68,7 +68,14 @@ impl Dispatch<ZwpRelativePointerV1, GlobalData, WinitState> for RelativePointerS
}; };
state state
.events_sink .events_sink
.push_device_event(DeviceEvent::PointerMotion { delta: (dx_unaccel, dy_unaccel) }); .push_device_event(DeviceEvent::Motion { axis: 0, value: dx_unaccel }, super::DeviceId);
state
.events_sink
.push_device_event(DeviceEvent::Motion { axis: 1, value: dy_unaccel }, super::DeviceId);
state.events_sink.push_device_event(
DeviceEvent::MouseMotion { delta: (dx_unaccel, dy_unaccel) },
super::DeviceId,
);
} }
} }

View File

@@ -1,9 +1,11 @@
use std::ops::Deref; use std::ops::Deref;
use sctk::globals::GlobalData; use sctk::globals::GlobalData;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle}; use sctk::reexports::client::{delegate_dispatch, Dispatch};
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::{ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
ContentHint, ContentPurpose, Event as TextInputEvent, ZwpTextInputV3, ContentHint, ContentPurpose, Event as TextInputEvent, ZwpTextInputV3,

View File

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

View File

@@ -3,14 +3,16 @@ use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use ahash::AHashMap; use ahash::AHashMap;
use sctk::compositor::{CompositorHandler, CompositorState};
use sctk::output::{OutputHandler, OutputState};
use sctk::reexports::calloop::LoopHandle; use sctk::reexports::calloop::LoopHandle;
use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::globals::GlobalList; use sctk::reexports::client::globals::GlobalList;
use sctk::reexports::client::protocol::wl_output::WlOutput; use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::compositor::{CompositorHandler, CompositorState};
use sctk::output::{OutputHandler, OutputState};
use sctk::registry::{ProvidesRegistryState, RegistryState}; use sctk::registry::{ProvidesRegistryState, RegistryState};
use sctk::seat::pointer::ThemedPointer; use sctk::seat::pointer::ThemedPointer;
use sctk::seat::SeatState; use sctk::seat::SeatState;
@@ -21,7 +23,6 @@ use sctk::shm::slot::SlotPool;
use sctk::shm::{Shm, ShmHandler}; use sctk::shm::{Shm, ShmHandler};
use sctk::subcompositor::SubcompositorState; use sctk::subcompositor::SubcompositorState;
use crate::error::OsError;
use crate::platform_impl::wayland::event_loop::sink::EventSink; use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::output::MonitorHandle; use crate::platform_impl::wayland::output::MonitorHandle;
use crate::platform_impl::wayland::seat::{ use crate::platform_impl::wayland::seat::{
@@ -33,7 +34,8 @@ use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScali
use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState; use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState;
use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState; use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState;
use crate::platform_impl::wayland::window::{WindowRequests, WindowState}; use crate::platform_impl::wayland::window::{WindowRequests, WindowState};
use crate::platform_impl::wayland::WindowId; use crate::platform_impl::wayland::{WaylandError, WindowId};
use crate::platform_impl::OsError;
/// Winit's Wayland state. /// Winit's Wayland state.
pub struct WinitState { pub struct WinitState {
@@ -113,9 +115,6 @@ pub struct WinitState {
/// Whether we have dispatched events to the user thus we want to /// Whether we have dispatched events to the user thus we want to
/// send `AboutToWait` and normally wakeup the user. /// send `AboutToWait` and normally wakeup the user.
pub dispatched_events: bool, pub dispatched_events: bool,
/// Whether the user initiated a wake up.
pub proxy_wake_up: bool,
} }
impl WinitState { impl WinitState {
@@ -126,7 +125,7 @@ impl WinitState {
) -> Result<Self, OsError> { ) -> Result<Self, OsError> {
let registry_state = RegistryState::new(globals); let registry_state = RegistryState::new(globals);
let compositor_state = let compositor_state =
CompositorState::bind(globals, queue_handle).map_err(|err| os_error!(err))?; CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
let subcompositor_state = match SubcompositorState::bind( let subcompositor_state = match SubcompositorState::bind(
compositor_state.wl_compositor().clone(), compositor_state.wl_compositor().clone(),
globals, globals,
@@ -156,7 +155,7 @@ impl WinitState {
(None, None) (None, None)
}; };
let shm = Shm::bind(globals, queue_handle).map_err(|err| os_error!(err))?; let shm = Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
let custom_cursor_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap())); let custom_cursor_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap()));
Ok(Self { Ok(Self {
@@ -168,7 +167,7 @@ impl WinitState {
shm, shm,
custom_cursor_pool, custom_cursor_pool,
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(|err| os_error!(err))?, xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(), xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
windows: Default::default(), windows: Default::default(),
@@ -193,7 +192,6 @@ impl WinitState {
loop_handle, loop_handle,
// Make it true by default. // Make it true by default.
dispatched_events: true, dispatched_events: true,
proxy_wake_up: false,
}) })
} }

View File

@@ -1,4 +1,5 @@
use cursor_icon::CursorIcon; use cursor_icon::CursorIcon;
use sctk::reexports::client::protocol::wl_shm::Format; use sctk::reexports::client::protocol::wl_shm::Format;
use sctk::shm::slot::{Buffer, SlotPool}; use sctk::shm::slot::{Buffer, SlotPool};

View File

@@ -1,12 +1,13 @@
//! Handling of KDE-compatible blur. //! Handling of KDE-compatible blur.
use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle}; use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur_manager::OrgKdeKwinBlurManager; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur_manager::OrgKdeKwinBlurManager;
use sctk::globals::GlobalData;
use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::state::WinitState;
/// KWin blur manager. /// KWin blur manager.

View File

@@ -1,6 +1,5 @@
//! Handling of the fractional scaling. //! Handling of the fractional scaling.
use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle}; use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
@@ -9,6 +8,8 @@ use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_
Event as FractionalScalingEvent, WpFractionalScaleV1, Event as FractionalScalingEvent, WpFractionalScaleV1,
}; };
use sctk::globals::GlobalData;
use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::state::WinitState;
/// The scaling factor denominator. /// The scaling factor denominator.

View File

@@ -1,12 +1,13 @@
//! Handling of the wp-viewporter. //! Handling of the wp-viewporter.
use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle}; use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter; use sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
use sctk::globals::GlobalData;
use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::state::WinitState;
/// Viewporter. /// Viewporter.

View File

@@ -3,7 +3,6 @@
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::Weak; use std::sync::Weak;
use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle}; use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
@@ -12,9 +11,12 @@ use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_toke
}; };
use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1; use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::globals::GlobalData;
use crate::event_loop::AsyncRequestSerial; use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::state::WinitState;
use crate::window::{ActivationToken, WindowId}; use crate::platform_impl::WindowId;
use crate::window::ActivationToken;
pub struct XdgActivationState { pub struct XdgActivationState {
xdg_activation: XdgActivationV1, xdg_activation: XdgActivationV1,

View File

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

View File

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

View File

@@ -5,14 +5,14 @@
//! X11 has a "startup notification" specification similar to Wayland's, see this URL: //! X11 has a "startup notification" specification similar to Wayland's, see this URL:
//! <https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt> //! <https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt>
use super::atoms::*;
use super::{VoidCookie, X11Error, XConnection};
use std::ffi::CString; use std::ffi::CString;
use std::fmt::Write; use std::fmt::Write;
use x11rb::protocol::xproto::{self, ConnectionExt as _}; use x11rb::protocol::xproto::{self, ConnectionExt as _};
use super::atoms::*;
use super::{VoidCookie, X11Error, XConnection};
impl XConnection { impl XConnection {
/// "Request" a new activation token from the server. /// "Request" a new activation token from the server.
pub(crate) fn request_activation_token(&self, window_title: &str) -> Result<String, X11Error> { pub(crate) fn request_activation_token(&self, window_title: &str) -> Result<String, X11Error> {
@@ -165,14 +165,14 @@ fn push_display(buffer: &mut Vec<u8>, display: &impl std::fmt::Display) {
buffer: &'a mut Vec<u8>, buffer: &'a mut Vec<u8>,
} }
impl std::fmt::Write for Writer<'_> { impl<'a> std::fmt::Write for Writer<'a> {
fn write_str(&mut self, s: &str) -> std::fmt::Result { fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buffer.extend_from_slice(s.as_bytes()); self.buffer.extend_from_slice(s.as_bytes());
Ok(()) Ok(())
} }
} }
write!(Writer { buffer }, "{display}").unwrap(); write!(Writer { buffer }, "{}", display).unwrap();
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -48,8 +48,6 @@ atom_manager! {
_NET_WM_NAME, _NET_WM_NAME,
_NET_WM_PID, _NET_WM_PID,
_NET_WM_PING, _NET_WM_PING,
_NET_WM_SYNC_REQUEST,
_NET_WM_SYNC_REQUEST_COUNTER,
_NET_WM_STATE, _NET_WM_STATE,
_NET_WM_STATE_ABOVE, _NET_WM_STATE_ABOVE,
_NET_WM_STATE_BELOW, _NET_WM_STATE_BELOW,
@@ -114,7 +112,6 @@ impl Index<AtomName> for Atoms {
} }
} }
pub(crate) use AtomName::*;
// Make sure `None` is still defined. // Make sure `None` is still defined.
pub(crate) use core::option::Option::None; pub(crate) use core::option::Option::None;
pub(crate) use AtomName::*;

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -3,10 +3,11 @@ use std::os::raw::c_char;
use std::ptr; use std::ptr;
use std::sync::Arc; use std::sync::Arc;
use super::{ffi, XConnection, XError};
use super::context::{ImeContext, ImeContextCreationError}; use super::context::{ImeContext, ImeContextCreationError};
use super::inner::{close_im, ImeInner}; use super::inner::{close_im, ImeInner};
use super::input_method::PotentialInputMethods; use super::input_method::PotentialInputMethods;
use super::{ffi, XConnection, XError};
pub(crate) unsafe fn xim_set_callback( pub(crate) unsafe fn xim_set_callback(
xconn: &Arc<XConnection>, xconn: &Arc<XConnection>,

View File

@@ -1,15 +1,15 @@
use std::error::Error;
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::c_short; use std::os::raw::c_short;
use std::sync::Arc; use std::sync::Arc;
use std::{fmt, mem, ptr}; use std::{mem, ptr};
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct}; use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
use super::{ffi, util, XConnection, XError};
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle}; use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender}; use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
use super::{ffi, util, XConnection, XError};
/// IME creation error. /// IME creation error.
#[derive(Debug)] #[derive(Debug)]
pub enum ImeContextCreationError { pub enum ImeContextCreationError {
@@ -20,19 +20,6 @@ pub enum ImeContextCreationError {
Null, Null,
} }
impl fmt::Display for ImeContextCreationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ImeContextCreationError::XError(err) => err.fmt(f),
ImeContextCreationError::Null => {
write!(f, "got null pointer from Xlib without exact reason")
},
}
}
}
impl Error for ImeContextCreationError {}
/// The callback used by XIM preedit functions. /// The callback used by XIM preedit functions.
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer); type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);

View File

@@ -2,9 +2,10 @@ use std::collections::HashMap;
use std::mem; use std::mem;
use std::sync::Arc; use std::sync::Arc;
use super::{ffi, XConnection, XError};
use super::context::ImeContext; use super::context::ImeContext;
use super::input_method::{InputMethod, PotentialInputMethods}; use super::input_method::{InputMethod, PotentialInputMethods};
use super::{ffi, XConnection, XError};
use crate::platform_impl::platform::x11::ime::ImeEventSender; use crate::platform_impl::platform::x11::ime::ImeEventSender;
pub(crate) unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> { pub(crate) unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {

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