mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
194 Commits
v0.29.0-be
...
v0.29.14
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaaf08972d | ||
|
|
ba4660dade | ||
|
|
090800e6a6 | ||
|
|
70fc8f66e9 | ||
|
|
7601a1506d | ||
|
|
b1dad450ee | ||
|
|
8d66df7f6f | ||
|
|
1c5fcf3309 | ||
|
|
58c89c1ffc | ||
|
|
2dae807c4f | ||
|
|
8b6c8ef323 | ||
|
|
2e63493776 | ||
|
|
d2acea95cc | ||
|
|
454dc56a6e | ||
|
|
016fd47d0d | ||
|
|
8ce5f6ea41 | ||
|
|
64084c5cf0 | ||
|
|
96d29ab26c | ||
|
|
3e42fa364c | ||
|
|
cb855b87cc | ||
|
|
9c033ce101 | ||
|
|
54ad02e4b9 | ||
|
|
00fe65630e | ||
|
|
961b675d34 | ||
|
|
bcd2fba4a0 | ||
|
|
0135fe51ae | ||
|
|
ed600e415a | ||
|
|
ad892c6949 | ||
|
|
a6b25643ad | ||
|
|
bc5b6ff8ce | ||
|
|
ba6254bc25 | ||
|
|
bd2d2760f0 | ||
|
|
3de08204d3 | ||
|
|
c9030f06c0 | ||
|
|
3035546b17 | ||
|
|
57cb3126d6 | ||
|
|
221b2e71cd | ||
|
|
0dc376a9ef | ||
|
|
85052c09bb | ||
|
|
be63581654 | ||
|
|
45b5f3b031 | ||
|
|
0270516067 | ||
|
|
a90cd1c9ad | ||
|
|
dbeeaeffd9 | ||
|
|
c4bfbbe417 | ||
|
|
978ec7dfec | ||
|
|
87f44ecffb | ||
|
|
da82971f52 | ||
|
|
324dd5fa86 | ||
|
|
fdedda38d2 | ||
|
|
cf0a533461 | ||
|
|
017ff26e7d | ||
|
|
6eb79f04c8 | ||
|
|
2bf12c74dc | ||
|
|
2998bbf7db | ||
|
|
3f82a6a90d | ||
|
|
2e610111b0 | ||
|
|
63d52aae32 | ||
|
|
5b4f97edac | ||
|
|
9135eb4024 | ||
|
|
23b3c127fd | ||
|
|
b343f45500 | ||
|
|
572d61f9ba | ||
|
|
87fc19826b | ||
|
|
11d1b7a980 | ||
|
|
5ca810ba8f | ||
|
|
2d1607b3f7 | ||
|
|
a32e232020 | ||
|
|
9b03bb7276 | ||
|
|
e39596151c | ||
|
|
5289b4f206 | ||
|
|
380dc4c451 | ||
|
|
6fbdbce6dd | ||
|
|
cafcaa2cdc | ||
|
|
e00204e626 | ||
|
|
a5b89bfe5a | ||
|
|
44052a093e | ||
|
|
40cee238e2 | ||
|
|
3dc5c42387 | ||
|
|
8c4a6ddcb4 | ||
|
|
5011a67f6d | ||
|
|
d621ab5018 | ||
|
|
966c033a6c | ||
|
|
1681410ca8 | ||
|
|
a82327c73f | ||
|
|
e71f765dea | ||
|
|
0738528931 | ||
|
|
8119c72d64 | ||
|
|
43f29f0481 | ||
|
|
266219f27f | ||
|
|
7449534ba2 | ||
|
|
7aa202b872 | ||
|
|
06cec065d4 | ||
|
|
f968e64ac8 | ||
|
|
a97309690e | ||
|
|
dec45ce0ff | ||
|
|
f709ac667f | ||
|
|
ecbe04caa7 | ||
|
|
7103514ae8 | ||
|
|
8b5aa33a88 | ||
|
|
7a3b486965 | ||
|
|
fc9c78cb56 | ||
|
|
525219716c | ||
|
|
7f851fe433 | ||
|
|
5dea2a4734 | ||
|
|
821fc63a9c | ||
|
|
8e9a3d2dd3 | ||
|
|
70a77b8534 | ||
|
|
a5bb6d67f7 | ||
|
|
33a2e4cebd | ||
|
|
0ee26986d8 | ||
|
|
ec41dddd0d | ||
|
|
7a872903a4 | ||
|
|
d82886bddc | ||
|
|
08edda1b0b | ||
|
|
7de33bca40 | ||
|
|
40ba9a7ce7 | ||
|
|
0656c54c3b | ||
|
|
74fcf7f9c0 | ||
|
|
0bc8f5e33a | ||
|
|
f6cc6c1472 | ||
|
|
20384d2f02 | ||
|
|
cdee616812 | ||
|
|
f58fb69446 | ||
|
|
6b445219c1 | ||
|
|
18b8569161 | ||
|
|
08b0464ac3 | ||
|
|
df2f5adfba | ||
|
|
99f86d729f | ||
|
|
d06deeecf6 | ||
|
|
e6d2fd7287 | ||
|
|
f2edd23542 | ||
|
|
70e6ddd210 | ||
|
|
f3fb27c17b | ||
|
|
75b463a368 | ||
|
|
ea8604e175 | ||
|
|
b1bd0f77fb | ||
|
|
1fded249d0 | ||
|
|
349a3e7b8c | ||
|
|
f2d277e599 | ||
|
|
8d5d612456 | ||
|
|
5788319632 | ||
|
|
976023bfc0 | ||
|
|
0f9b95814e | ||
|
|
112dcc808a | ||
|
|
4a381fb1db | ||
|
|
8339ddf368 | ||
|
|
b41f01c990 | ||
|
|
570f3101e5 | ||
|
|
3923c59fd8 | ||
|
|
75ae402a24 | ||
|
|
4385c17cbb | ||
|
|
3af256260e | ||
|
|
d9363219e1 | ||
|
|
a52a6d47ca | ||
|
|
ec83de3938 | ||
|
|
43d6eac871 | ||
|
|
1f101b2654 | ||
|
|
709929fcab | ||
|
|
220a2d32d5 | ||
|
|
c5cef46060 | ||
|
|
367a2ae057 | ||
|
|
e038597e81 | ||
|
|
27cd20739d | ||
|
|
8d9fd3d3d6 | ||
|
|
56427e47a7 | ||
|
|
c744b9aea5 | ||
|
|
ef9ed71f1b | ||
|
|
dda8053bd3 | ||
|
|
779212da33 | ||
|
|
28552c9cc1 | ||
|
|
48647b506f | ||
|
|
42243ce288 | ||
|
|
84d9bfd59e | ||
|
|
cd5c1fb724 | ||
|
|
8455f3415e | ||
|
|
c59d6bc809 | ||
|
|
4681133eca | ||
|
|
b278aa859f | ||
|
|
ee4ec43cf3 | ||
|
|
25b629f117 | ||
|
|
4b30f9ce22 | ||
|
|
2428224c09 | ||
|
|
2d9b852a95 | ||
|
|
246d53d5a1 | ||
|
|
865afd22be | ||
|
|
05130cb329 | ||
|
|
a1a6f7baf9 | ||
|
|
f160a6003c | ||
|
|
a24d092fa1 | ||
|
|
a8a0462c0d | ||
|
|
647c320ca7 | ||
|
|
5144337253 | ||
|
|
7f1aaa652d |
282
.github/workflows/ci.yml
vendored
282
.github/workflows/ci.yml
vendored
@@ -6,146 +6,182 @@ on:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
Check_Formatting:
|
||||
fmt:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
rust-version: stable
|
||||
components: rustfmt
|
||||
- name: Check Formatting
|
||||
run: cargo +stable fmt --all -- --check
|
||||
|
||||
run: cargo fmt -- --check
|
||||
|
||||
tests:
|
||||
name: Test ${{ matrix.toolchain }} ${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain: [stable, nightly, '1.65.0']
|
||||
platform:
|
||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
||||
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||
- { name: 'Windows 32bit MSVC', target: i686-pc-windows-msvc, os: windows-latest, }
|
||||
- { name: 'Windows 64bit GNU', target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
|
||||
- { name: 'Windows 32bit GNU', target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
|
||||
- { name: 'Linux 32bit', target: i686-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
|
||||
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
|
||||
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
|
||||
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
|
||||
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
|
||||
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
|
||||
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
||||
exclude:
|
||||
# Android is tested on stable-3
|
||||
- toolchain: '1.65.0'
|
||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
include:
|
||||
- toolchain: '1.69.0'
|
||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
|
||||
env:
|
||||
# Set more verbose terminal output
|
||||
CARGO_TERM_VERBOSE: true
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
# Faster compilation and error on warnings
|
||||
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings'
|
||||
RUSTDOCFLAGS: '--deny=warnings'
|
||||
|
||||
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
|
||||
CMD: ${{ matrix.platform.cmd }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Restore cache of cargo folder
|
||||
# We use `restore` and later `save`, so that we can create the key after
|
||||
# the cache has been downloaded.
|
||||
#
|
||||
# This could be avoided if we added Cargo.lock to the repository.
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
|
||||
path: |
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-never-intended-to-be-found
|
||||
restore-keys: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}
|
||||
|
||||
- name: Generate lockfile
|
||||
# Also updates the crates.io index
|
||||
run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0
|
||||
|
||||
- name: Install GCC Multilib
|
||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||
run: sudo apt-get update && sudo apt-get install gcc-multilib
|
||||
|
||||
- name: Cache cargo-apk
|
||||
if: contains(matrix.platform.target, 'android')
|
||||
id: cargo-apk-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cargo/bin/cargo-apk
|
||||
# Change this key if we update the required cargo-apk version
|
||||
key: cargo-apk-v0-9-7
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install cargo-apk
|
||||
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
|
||||
run: cargo install cargo-apk --version=^0.9.7 --locked
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
|
||||
targets: ${{ matrix.platform.target }}
|
||||
components: clippy
|
||||
|
||||
- name: Check documentation
|
||||
run: cargo doc --no-deps $OPTIONS --document-private-items
|
||||
|
||||
- name: Build crate
|
||||
run: cargo $CMD build $OPTIONS
|
||||
|
||||
- name: Build tests
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
run: cargo $CMD test --no-run $OPTIONS
|
||||
|
||||
- name: Run tests
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
run: cargo $CMD test $OPTIONS
|
||||
|
||||
- name: Lint with clippy
|
||||
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
|
||||
run: cargo clippy --all-targets $OPTIONS -- -Dwarnings
|
||||
|
||||
- name: Build tests with serde enabled
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
run: cargo $CMD test --no-run $OPTIONS --features serde
|
||||
|
||||
- name: Run tests with serde enabled
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
run: cargo $CMD test $OPTIONS --features serde
|
||||
|
||||
# See restore step above
|
||||
- name: Save cache of cargo folder
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
|
||||
|
||||
cargo-deny:
|
||||
name: cargo-deny
|
||||
name: Run cargo-deny on ${{ matrix.platform.name }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- aarch64-apple-ios
|
||||
- aarch64-linux-android
|
||||
- i686-pc-windows-gnu
|
||||
- i686-pc-windows-msvc
|
||||
- i686-unknown-linux-gnu
|
||||
- wasm32-unknown-unknown
|
||||
- x86_64-apple-darwin
|
||||
- x86_64-apple-ios
|
||||
- x86_64-pc-windows-gnu
|
||||
- x86_64-pc-windows-msvc
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-redox
|
||||
- { name: 'Android', target: aarch64-linux-android }
|
||||
- { name: 'iOS', target: aarch64-apple-ios }
|
||||
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
|
||||
- { name: 'macOS', target: x86_64-apple-darwin }
|
||||
- { name: 'Redox OS', target: x86_64-unknown-redox }
|
||||
- { name: 'web', target: wasm32-unknown-unknown }
|
||||
- { name: 'Windows', target: x86_64-pc-windows-gnu }
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
with:
|
||||
command: check
|
||||
log-level: error
|
||||
arguments: --all-features --target ${{ matrix.platform }}
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust_version: ['1.65.0', stable, nightly]
|
||||
platform:
|
||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||
- { target: i686-pc-windows-msvc, os: windows-latest, }
|
||||
- { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
|
||||
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
|
||||
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
|
||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
|
||||
- { target: aarch64-linux-android, os: ubuntu-latest, options: -p winit, cmd: 'apk --', features: "android-native-activity" }
|
||||
- { target: x86_64-unknown-redox, os: ubuntu-latest, }
|
||||
- { target: x86_64-apple-darwin, os: macos-latest, }
|
||||
- { target: x86_64-apple-ios, os: macos-latest, }
|
||||
- { target: aarch64-apple-ios, os: macos-latest, }
|
||||
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
|
||||
# doesn't currently work on Linux.
|
||||
- { target: wasm32-unknown-unknown, os: windows-latest, }
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
|
||||
OPTIONS: ${{ matrix.platform.options }}
|
||||
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
|
||||
CMD: ${{ matrix.platform.cmd }}
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# Used to cache cargo-web
|
||||
- name: Cache cargo folder
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cargo
|
||||
key: ${{ matrix.platform.target }}-cargo-${{ matrix.rust_version }}
|
||||
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
|
||||
targets: ${{ matrix.platform.target }}
|
||||
components: clippy
|
||||
|
||||
- name: Install GCC Multilib
|
||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||
run: sudo apt-get update && sudo apt-get install gcc-multilib
|
||||
- name: Install cargo-apk
|
||||
if: contains(matrix.platform.target, 'android')
|
||||
run: cargo install cargo-apk
|
||||
|
||||
- name: Check documentation
|
||||
shell: bash
|
||||
run: cargo doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
|
||||
|
||||
- name: Build crate
|
||||
shell: bash
|
||||
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
||||
|
||||
- name: Build tests
|
||||
shell: bash
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.65.0'
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.65.0'
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
||||
|
||||
- name: Lint with clippy
|
||||
shell: bash
|
||||
if: (matrix.rust_version == 'stable') && !contains(matrix.platform.options, '--no-default-features')
|
||||
run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings
|
||||
|
||||
- name: Build tests with serde enabled
|
||||
shell: bash
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.65.0'
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
|
||||
- name: Run tests with serde enabled
|
||||
shell: bash
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.65.0'
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
|
||||
arguments: --all-features --target ${{ matrix.platform.target }}
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "deps/apk-builder"]
|
||||
path = deps/apk-builder
|
||||
url = https://github.com/rust-windowing/android-rs-glue
|
||||
394
CHANGELOG.md
394
CHANGELOG.md
@@ -2,22 +2,200 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
Please keep one empty line before and after all headers. (This is required for `git` to produce a conflict when a release is made while a PR is open and the PR's changelog entry would go into the wrong section).
|
||||
Please keep one empty line before and after all headers. (This is required for
|
||||
`git` to produce a conflict when a release is made while a PR is open and the
|
||||
PR's changelog entry would go into the wrong section).
|
||||
|
||||
And please only add new entries to the top of this list, right below the `# Unreleased` header.
|
||||
And please only add new entries to the top of this list, right below the `#
|
||||
Unreleased` header.
|
||||
|
||||
# Unreleased
|
||||
|
||||
# 0.29.0-beta.1
|
||||
# 0.29.14
|
||||
|
||||
- **Breaking:** Bump `ndk` version to `0.8.0-beta.0`, ndk-sys to `v0.5.0-beta.0`, `android-activity` to `0.5.0-beta.1`.
|
||||
- **Breaking:** Bump MSRV from `1.64` to `1.65`.
|
||||
- Make iOS windows usable from other threads.
|
||||
- Reexport `raw-window-handle` in `window` module.
|
||||
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
|
||||
- On X11/Wayland, fix `text` and `text_with_all_modifiers` not being `None` during compose.
|
||||
- On Wayland, don't reapply cursor grab when unchanged.
|
||||
- On X11, fix a bug where some mouse events would be unexpectedly filtered out.
|
||||
|
||||
# 0.29.13
|
||||
|
||||
- On Web, fix possible crash with `ControlFlow::Wait` and `ControlFlow::WaitUntil`.
|
||||
|
||||
# 0.29.12
|
||||
|
||||
- On X11, fix use after free during xinput2 handling.
|
||||
- On X11, filter close to zero values in mouse device events
|
||||
|
||||
# 0.29.11
|
||||
|
||||
- On Wayland, fix DeviceEvent::Motion not being sent
|
||||
- On X11, don't require XIM to run.
|
||||
- On X11, fix xkb state not being updated correctly sometimes leading to wrong input.
|
||||
- Fix compatibility with 32-bit platforms without 64-bit atomics.
|
||||
- On macOS, fix incorrect IME cursor rect origin.
|
||||
- On X11, fix swapped instance and general class names.
|
||||
- On Windows, fixed a race condition when sending an event through the loop proxy.
|
||||
- On Wayland, disable `Occluded` event handling.
|
||||
- On X11, reload dpi on `_XSETTINGS_SETTINGS` update.
|
||||
- On X11, fix deadlock when adjusting DPI and resizing at the same time.
|
||||
- On Wayland, fix `Focused(false)` being send when other seats still have window focused.
|
||||
- On Wayland, fix `Window::set_{min,max}_inner_size` not always applied.
|
||||
- On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop.
|
||||
- On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
|
||||
- On Orbital, fix `logical_key` and `text` not reported in `KeyEvent`.
|
||||
- On Orbital, implement `KeyEventExtModifierSupplement`.
|
||||
- On Orbital, map keys to `NamedKey` when possible.
|
||||
- On Orbital, implement `set_cursor_grab`.
|
||||
- On Orbital, implement `set_cursor_visible`.
|
||||
- On Orbital, implement `drag_window`.
|
||||
- On Orbital, implement `drag_resize_window`.
|
||||
- On Orbital, implement `set_transparent`.
|
||||
- On Orbital, implement `set_visible`.
|
||||
- On Orbital, implement `is_visible`.
|
||||
- On Orbital, implement `set_resizable`.
|
||||
- On Orbital, implement `is_resizable`.
|
||||
- On Orbital, implement `set_maximized`.
|
||||
- On Orbital, implement `is_maximized`.
|
||||
- On Orbital, implement `set_decorations`.
|
||||
- On Orbital, implement `is_decorated`.
|
||||
- On Orbital, implement `set_window_level`.
|
||||
- On Orbital, emit `DeviceEvent::MouseMotion`.
|
||||
- On Wayland, fix title in CSD not updated from `AboutToWait`.
|
||||
|
||||
# 0.29.10
|
||||
|
||||
- On Web, account for canvas being focused already before event loop starts.
|
||||
- On Web, increase cursor position accuracy.
|
||||
|
||||
# 0.29.9
|
||||
|
||||
- On X11, fix `NotSupported` error not propagated when creating event loop.
|
||||
- On Wayland, fix resize not issued when scale changes
|
||||
- On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`.
|
||||
- On macOS, report correct logical key when Ctrl or Cmd is pressed.
|
||||
|
||||
# 0.29.8
|
||||
|
||||
- On X11, fix IME input lagging behind.
|
||||
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
|
||||
- On X11, fix keymap not updated from xmodmap.
|
||||
- On X11, reduce the amount of time spent fetching screen resources.
|
||||
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
|
||||
- On Wayland, fix `Window::inner_size` not using the correct rounding.
|
||||
|
||||
# 0.29.7
|
||||
|
||||
- On X11, fix `Xft.dpi` reload during runtime.
|
||||
- On X11, fix window minimize.
|
||||
|
||||
# 0.29.6
|
||||
|
||||
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
|
||||
- On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window.
|
||||
- Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation
|
||||
|
||||
# 0.29.5
|
||||
|
||||
- On macOS, remove spurious error logging when handling `Fn`.
|
||||
- On X11, fix an issue where floating point data from the server is
|
||||
misinterpreted during a drag and drop operation.
|
||||
- On X11, fix a bug where focusing the window would panic.
|
||||
- On macOS, fix `refresh_rate_millihertz`.
|
||||
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
|
||||
- On X11, fix `Xft.dpi` detection from Xresources.
|
||||
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
|
||||
- On Wayland, fix resize being sent on focus change.
|
||||
- On Windows, fix `set_ime_cursor_area`.
|
||||
|
||||
# 0.29.4
|
||||
|
||||
- Fix crash when running iOS app on macOS.
|
||||
- On X11, check common alternative cursor names when loading cursor.
|
||||
- On X11, reload the DPI after a property change event.
|
||||
- On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread.
|
||||
- On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account.
|
||||
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
|
||||
- On Wayland, fix `wl_surface` being destroyed before associated objects.
|
||||
- On macOS, fix assertion when pressing `Fn` key.
|
||||
|
||||
# 0.29.3
|
||||
|
||||
- On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible.
|
||||
- On Wayland, fix `RedrawRequsted` being always sent without decorations and `sctk-adwaita` feature.
|
||||
- On Wayland, ignore resize requests when the window is fully tiled.
|
||||
- On Wayland, use `configure_bounds` to constrain `with_inner_size` when compositor wants users to pick size.
|
||||
- On Windows, fix deadlock when accessing the state during `Cursor{Enter,Leave}`.
|
||||
- On Windows, add support for `Window::set_transparent`.
|
||||
- On macOS, fix deadlock when entering a nested event loop from an event handler.
|
||||
- On macOS, add support for `Window::set_blur`.
|
||||
|
||||
# 0.29.2
|
||||
|
||||
- **Breaking:** Bump MSRV from `1.60` to `1.65`.
|
||||
- **Breaking:** Add `Event::MemoryWarning`; implemented on iOS/Android.
|
||||
- **Breaking:** Bump `ndk` version to `0.8.0`, ndk-sys to `0.5.0`, `android-activity` to `0.5.0`.
|
||||
- **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`.
|
||||
- **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`.
|
||||
- **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely.
|
||||
- **Breaking:** Moved `ControlFlow` to `EventLoopWindowTarget::set_control_flow()` and `EventLoopWindowTarget::control_flow()`.
|
||||
- **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>`
|
||||
- On X11, set `visual_id` in returned `raw-window-handle`.
|
||||
- **Breaking:** on Wayland, dispatching user created wayland queue won't wake up the loop unless winit has event to send back.
|
||||
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
|
||||
- **Breaking:** on Wayland, dispatching user created Wayland queue won't wake up the loop unless winit has event to send back.
|
||||
- **Breaking:** remove `DeviceEvent::Text`.
|
||||
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
|
||||
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
|
||||
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
|
||||
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
|
||||
- **Breaking** `run() -> !` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
|
||||
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_on_demand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
|
||||
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
|
||||
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
|
||||
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- **Breaking:** Remove all deprecated `modifiers` fields.
|
||||
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
|
||||
- **Breaking** Add `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
|
||||
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
|
||||
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
|
||||
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events on Wayland, X11, Windows, macOS and Web.
|
||||
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
|
||||
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
|
||||
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
|
||||
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to the canvas size will be reported through `WindowEvent::Resized`.
|
||||
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
|
||||
- **Breaking:** `CursorIcon::Arrow` was removed.
|
||||
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
|
||||
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
|
||||
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
|
||||
- **Breaking:** Overhaul keyboard input handling.
|
||||
- Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`.
|
||||
- Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`.
|
||||
- Change `Event::Key` to contain a `RawKeyEvent`.
|
||||
- Remove `Event::ReceivedCharacter`. In its place, you should use
|
||||
`KeyEvent.text` in combination with `WindowEvent::Ime`.
|
||||
- Replace `VirtualKeyCode` with the `Key` enum.
|
||||
- Replace `ScanCode` with the `KeyCode` enum.
|
||||
- Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`.
|
||||
- Add `PhysicalKey` wrapping `KeyCode` and `NativeKeyCode`.
|
||||
- Add `KeyCode` to refer to keys (roughly) by their physical location.
|
||||
- Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't
|
||||
understand.
|
||||
- Add `Key` to represent the keys after they've been interpreted by the
|
||||
active (software) keyboard layout.
|
||||
- Add `NamedKey` to represent the categorized keys.
|
||||
- Add `NativeKey` to represent raw `Key`s which Winit doesn't understand.
|
||||
- Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing,
|
||||
but can appear simultaneously in different spots on the same keyboard
|
||||
layout.
|
||||
- Add `Window::reset_dead_keys` to enable application-controlled cancellation
|
||||
of dead key sequences.
|
||||
- Add `KeyEventExtModifierSupplement` to expose additional (and less
|
||||
portable) interpretations of a given key-press.
|
||||
- Add `PhysicalKeyExtScancode`, which lets you convert between scancodes and
|
||||
`PhysicalKey`.
|
||||
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
|
||||
- Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead:
|
||||
- `platform::windows::HINSTANCE`.
|
||||
- `WindowExtWindows::hinstance`.
|
||||
@@ -34,125 +212,93 @@ And please only add new entries to the top of this list, right below the `# Unre
|
||||
- `WindowExtX11::xlib_display`.
|
||||
- `WindowExtX11::xlib_screen_id`.
|
||||
- `WindowExtX11::xcb_connection`.
|
||||
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events.
|
||||
- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor.
|
||||
- Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
|
||||
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
|
||||
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
|
||||
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
|
||||
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
|
||||
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
|
||||
- On Wayland, make double clicking and moving the CSD frame more reliable.
|
||||
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
|
||||
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
|
||||
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
|
||||
- Implement `PartialOrd` and `Ord` for `Key`, `KeyCode`, `NativeKey`, and `NativeKeyCode`.
|
||||
- Reexport `raw-window-handle` in `window` module.
|
||||
- Add `ElementState::is_pressed`.
|
||||
- On Web, implement `WindowEvent::Occluded`.
|
||||
- On Web, fix touch location to be as accurate as mouse position.
|
||||
- On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position.
|
||||
- On Web, add Fullscreen API compatibility for Safari.
|
||||
- On Web, implement `Window::set_(min|max)_inner_size()`.
|
||||
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
|
||||
- On Web, fix some `WindowBuilder` methods doing nothing.
|
||||
- On Web, implement `Window::focus_window()`.
|
||||
- On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`.
|
||||
- On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
|
||||
- On Windows, add `drag_resize_window` method support.
|
||||
- **Breaking** `run() ->!` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
|
||||
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_ondemand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
|
||||
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
|
||||
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
|
||||
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- Added `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
|
||||
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
|
||||
- On iOS, add force data to touch events when using the Apple Pencil.
|
||||
|
||||
# 0.29.0-beta.0
|
||||
|
||||
- On Web, allow event loops to be recreated with `spawn`.
|
||||
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
|
||||
- On Android, changed default behavior of Android to ignore volume keys letting the operating system handle them.
|
||||
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
|
||||
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
|
||||
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
|
||||
- On X11, fix `EventLoopWindowTarget::listen_device_events` effect being reversed.
|
||||
- **Breaking:** Remove all deprecated `modifiers` fields.
|
||||
- **Breaking:** Overhaul keyboard input handling.
|
||||
- Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`.
|
||||
- Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`.
|
||||
- Change `Event::Key` to contain a `RawKeyEvent`.
|
||||
- Remove `Event::ReceivedCharacter`. In its place, you should use
|
||||
`KeyEvent.text` in combination with `WindowEvent::Ime`.
|
||||
- Replace `VirtualKeyCode` with the `Key` enum.
|
||||
- Replace `ScanCode` with the `KeyCode` enum.
|
||||
- Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`.
|
||||
- Add `KeyCode` to refer to keys (roughly) by their physical location.
|
||||
- Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't
|
||||
understand.
|
||||
- Add `Key` to represent the keys after they've been interpreted by the
|
||||
active (software) keyboard layout.
|
||||
- Add `NativeKey` to represent raw `Key`s which Winit doesn't understand.
|
||||
- Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing,
|
||||
but can appear simultaneously in different spots on the same keyboard
|
||||
layout.
|
||||
- Add `Window::reset_dead_keys` to enable application-controlled cancellation
|
||||
of dead key sequences.
|
||||
- Add `KeyEventExtModifierSupplement` to expose additional (and less
|
||||
portable) interpretations of a given key-press.
|
||||
- Add `KeyCodeExtScancode`, which lets you convert between raw keycodes and
|
||||
`KeyCode`.
|
||||
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
|
||||
- On Orbital, fix `ModifiersChanged` not being sent.
|
||||
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
|
||||
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
|
||||
- **Breaking:** `CursorIcon::Arrow` was removed.
|
||||
- On Wayland, fix maximized startup not taking full size on GNOME.
|
||||
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
|
||||
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
|
||||
- On Wayland, fix window not checking that it actually got initial configure event.
|
||||
- On Wayland, fix maximized window creation and window geometry handling.
|
||||
- On Wayland, fix forward compatibility issues.
|
||||
- On Wayland, add `Window::drag_resize_window` method.
|
||||
- On Wayland, drop `WINIT_WAYLAND_CSD_THEME` variable.
|
||||
- Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
|
||||
- Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now.
|
||||
- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now.
|
||||
- Implement `AsFd`/`AsRawFd` for `EventLoop<T>` on X11 and Wayland.
|
||||
- Implement `PartialOrd` and `Ord` for `MouseButton`.
|
||||
- Implement `PartialOrd` and `Ord` on types in the `dpi` module.
|
||||
- **Breaking:** Bump MSRV from `1.60` to `1.64`.
|
||||
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
|
||||
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
|
||||
- On Web: fix position of touch events to be relative to the canvas.
|
||||
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during
|
||||
a transient activation.
|
||||
- On Web, fix pointer button events not being processed when a buttons is already pressed.
|
||||
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
|
||||
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
|
||||
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
|
||||
- On Windows, port to `windows-sys` version 0.48.0.
|
||||
- On Web, fix pen treated as mouse input.
|
||||
- On Web, send mouse position on button release as well.
|
||||
- On Web, fix touch input not gaining or loosing focus.
|
||||
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
|
||||
- On Web, prevent clicks on the canvas to select text.
|
||||
- Make `WindowBuilder` `Send + Sync`.
|
||||
- Make iOS `MonitorHandle` and `VideoMode` usable from other threads.
|
||||
- Make iOS windows usable from other threads.
|
||||
- On Android, add force data to touch events.
|
||||
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
|
||||
- On Android, fix `DeviceId` to contain device id's.
|
||||
- On Orbital, fix `ModifiersChanged` not being sent.
|
||||
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
|
||||
- On Wayland, add `Window::drag_resize_window` method.
|
||||
- On Wayland, remove `WINIT_WAYLAND_CSD_THEME` variable.
|
||||
- On Wayland, fix `TouchPhase::Canceled` being sent for moved events.
|
||||
- On Wayland, fix forward compatibility issues.
|
||||
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
|
||||
- On Wayland, fix maximized startup not taking full size on GNOME.
|
||||
- On Wayland, fix maximized window creation and window geometry handling.
|
||||
- On Wayland, fix window not checking that it actually got initial configure event.
|
||||
- On Wayland, make double clicking and moving the CSD frame more reliable.
|
||||
- On Wayland, support `Occluded` event with xdg-shell v6
|
||||
- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor.
|
||||
- On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback.
|
||||
- On Web, `EventLoopProxy` now implements `Send`.
|
||||
- On Web, `Window` now implements `Send` and `Sync`.
|
||||
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
|
||||
- On Web, use the correct canvas size when calculating the new size during scale factor change,
|
||||
instead of using the output bitmap size.
|
||||
- On Web, scale factor and dark mode detection are now more robust.
|
||||
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
|
||||
- On Web, fix scale factor resize suggestion always overwriting the canvas size.
|
||||
- On macOS, fix crash when dropping `Window`.
|
||||
- On Web, use `Window.requestIdleCallback()` for `ControlFlow::Poll` when available.
|
||||
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to
|
||||
the canvas size will be reported through `WindowEvent::Resized`.
|
||||
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
|
||||
- On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position.
|
||||
- On Web, add Fullscreen API compatibility for Safari.
|
||||
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and `DeviceEvent::Key` support.
|
||||
- On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback.
|
||||
- On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
|
||||
- On Web, allow event loops to be recreated with `spawn`.
|
||||
- On Web, enable event propagation.
|
||||
- On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time.
|
||||
- On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window.
|
||||
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and
|
||||
`DeviceEvent::Key` support.
|
||||
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events
|
||||
on Wayland, X11, Windows, macOS and Web.
|
||||
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during transient activation.
|
||||
- On Web, fix pen treated as mouse input.
|
||||
- On Web, fix pointer button events not being processed when a buttons is already pressed.
|
||||
- On Web, fix scale factor resize suggestion always overwriting the canvas size.
|
||||
- On Web, fix some `WindowBuilder` methods doing nothing.
|
||||
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
|
||||
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
|
||||
- On Web, fix touch input not gaining or loosing focus.
|
||||
- On Web, fix touch location to be as accurate as mouse position.
|
||||
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
|
||||
- On Web, implement `Window::focus_window()`.
|
||||
- On Web, implement `Window::set_(min|max)_inner_size()`.
|
||||
- On Web, implement `WindowEvent::Occluded`.
|
||||
- On Web, never return a `MonitorHandle`.
|
||||
- On Web, prevent clicks on the canvas to select text.
|
||||
- On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected.
|
||||
- On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`.
|
||||
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
|
||||
- On Web, scale factor and dark mode detection are now more robust.
|
||||
- On Web, send mouse position on button release as well.
|
||||
- On Web, take all transient activations on the canvas and window into account to queue a fullscreen request.
|
||||
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events.
|
||||
- On Web, use the correct canvas size when calculating the new size during scale factor change, instead of using the output bitmap size.
|
||||
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
|
||||
- On Web: fix position of touch events to be relative to the canvas.
|
||||
- On Windows, add `drag_resize_window` method support.
|
||||
- On Windows, add horizontal MouseWheel `DeviceEvent`.
|
||||
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
|
||||
- On Windows, fix IME APIs not working when from non event loop thread.
|
||||
- On Windows, fix `CursorEnter/Left` not being sent when grabbing the mouse.
|
||||
- On Windows, fix `RedrawRequested` not being delivered when calling `Window::request_redraw` from `RedrawRequested`.
|
||||
- On Windows, port to `windows-sys` version 0.48.0.
|
||||
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
|
||||
- On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`.
|
||||
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
|
||||
- On X11, set `visual_id` in returned `raw-window-handle`.
|
||||
- On iOS, add ability to change the status bar style.
|
||||
- On iOS, add force data to touch events when using the Apple Pencil.
|
||||
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
|
||||
- On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground.
|
||||
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
|
||||
- On macOS, fix assertion when pressing `Globe` key.
|
||||
- On macOS, fix crash in `window.set_minimized(false)`.
|
||||
- On macOS, fix crash when dropping `Window`.
|
||||
|
||||
# 0.28.7
|
||||
|
||||
- Fix window size sometimes being invalid when resizing on macOS 14 Sonoma.
|
||||
|
||||
# 0.28.6
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ may be worth creating a separate crate that extends Winit's API to add that func
|
||||
When reporting an issue, in order to help the maintainers understand what the problem is, please make
|
||||
your description of the issue as detailed as possible:
|
||||
|
||||
- if it is a bug, please provide clear explanation of what happens, what should happen, and how to
|
||||
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
|
||||
reproduce the issue, ideally by providing a minimal program exhibiting the problem
|
||||
- if it is a feature request, please provide a clear argumentation about why you believe this feature
|
||||
should be supported by winit
|
||||
@@ -21,7 +21,7 @@ your description of the issue as detailed as possible:
|
||||
When making a code contribution to winit, before opening your pull request, please make sure that:
|
||||
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.65.
|
||||
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
|
||||
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
|
||||
were not tested, and what should be tested, so that a maintainer or another contributor can test them
|
||||
- you updated any relevant documentation in winit
|
||||
- you left comments in your code explaining any part that is not straightforward, so that the
|
||||
@@ -34,7 +34,7 @@ When making a code contribution to winit, before opening your pull request, plea
|
||||
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
|
||||
should be updated.
|
||||
|
||||
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
|
||||
Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy
|
||||
is that a PR must be approved by at least two maintainers of winit before being merged, including
|
||||
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
|
||||
|
||||
@@ -46,27 +46,26 @@ Once your PR is deemed ready, the merging maintainer will take care of resolving
|
||||
|
||||
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
|
||||
|
||||
If you are interested in being pinged when testing is needed for a certain platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
|
||||
If you are interested in being pinged when testing is needed for a specific platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
|
||||
|
||||
## Release process
|
||||
|
||||
Given that winit is a widely used library we should be able to make a patch
|
||||
Given that winit is a widely used library, we should be able to make a patch
|
||||
releases at any time we want without blocking the development of new features.
|
||||
|
||||
To achieve these goals, a new branch is created for every new release. Releases
|
||||
and later patch releases are committed and tagged in this branch.
|
||||
To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch.
|
||||
|
||||
The exact steps for an exemplary `0.2.0` release might look like this:
|
||||
1. Initially the version on the latest master is `0.1.0`
|
||||
1. Initially, the version on the latest master is `0.1.0`
|
||||
2. A new `v0.2.x` branch is created for the release
|
||||
3. In the branch, the version is bumped to `v0.2.0`
|
||||
4. The new commit in the branch is tagged `v0.2.0`
|
||||
5. The version is pushed to crates.io
|
||||
6. A GitHub release is created for the `v0.2.0` tag
|
||||
7. On master, the version is bumped to `0.2.0` and the CHANGELOG is updated
|
||||
7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated
|
||||
|
||||
When doing a patch release the process is similar:
|
||||
1. Initially the version of the latest release is `0.2.0`
|
||||
When doing a patch release, the process is similar:
|
||||
1. Initially, the version of the latest release is `0.2.0`
|
||||
2. Checkout the `v0.2.x` branch
|
||||
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
|
||||
4. Follow steps 3-7 of the regular release example
|
||||
|
||||
63
Cargo.toml
63
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.29.0-beta.1"
|
||||
version = "0.29.14"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2021"
|
||||
@@ -13,7 +13,14 @@ categories = ["gui"]
|
||||
rust-version = "1.65.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["serde"]
|
||||
features = [
|
||||
"rwh_04",
|
||||
"rwh_05",
|
||||
"rwh_06",
|
||||
"serde",
|
||||
# Enabled to get docs to compile
|
||||
"android-native-activity",
|
||||
]
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
# These are all tested in CI
|
||||
targets = [
|
||||
@@ -35,9 +42,9 @@ targets = [
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
|
||||
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv", "memmap2"]
|
||||
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
|
||||
wayland-dlopen = ["wayland-backend/dlopen"]
|
||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
|
||||
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
|
||||
@@ -45,39 +52,44 @@ wayland-csd-adwaita-notitle = ["sctk-adwaita"]
|
||||
android-native-activity = ["android-activity/native-activity"]
|
||||
android-game-activity = ["android-activity/game-activity"]
|
||||
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
|
||||
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
|
||||
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
|
||||
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
|
||||
|
||||
[build-dependencies]
|
||||
cfg_aliases = "0.1.1"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
cursor-icon = "1.0.0"
|
||||
cursor-icon = "1.1.0"
|
||||
log = "0.4"
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
once_cell = "1.12"
|
||||
raw_window_handle = { package = "raw-window-handle", version = "0.5", 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 = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
smol_str = "0.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
image = { version = "0.24.0", default-features = false, features = ["png"] }
|
||||
simple_logger = { version = "2.1.0", default_features = false }
|
||||
simple_logger = { version = "4.2.0", default_features = false }
|
||||
winit = { path = ".", features = ["rwh_05"] }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
|
||||
softbuffer = "0.3.0"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
# Coordinate the next winit release android-activity 0.5 release
|
||||
android-activity = "0.5.0-beta.1"
|
||||
ndk = "0.8.0-beta.0"
|
||||
ndk-sys = "0.5.0-beta.0"
|
||||
android-activity = "0.5.0"
|
||||
ndk = { version = "0.8.0", default-features = false }
|
||||
ndk-sys = "0.5.0"
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
core-foundation = "0.9.3"
|
||||
objc2 = "0.4.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.22.3"
|
||||
core-graphics = "0.23.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.icrate]
|
||||
version = "0.0.4"
|
||||
@@ -140,21 +152,22 @@ features = [
|
||||
]
|
||||
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
|
||||
ahash = { version = "0.8.3", features = ["no-rng"], optional = true }
|
||||
bytemuck = { version = "1.13.1", default-features = false, optional = true }
|
||||
calloop = "0.12.3"
|
||||
libc = "0.2.64"
|
||||
memmap2 = { version = "0.9.0", optional = true }
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
fnv = { version = "1.0.3", optional = true }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.17.0", default-features = false, features = ["calloop"], optional = true }
|
||||
sctk-adwaita = { version = "0.6.0", default_features = false, optional = true }
|
||||
wayland-client = { version = "0.30.0", optional = true }
|
||||
wayland-backend = { version = "0.1.0", default_features = false, features = ["client_system"], optional = true }
|
||||
wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = true }
|
||||
calloop = "0.10.5"
|
||||
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "xinput", "xkb"], optional = true }
|
||||
xkbcommon-dl = "0.4.0"
|
||||
memmap2 = { version = "0.5.0", optional = true }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
|
||||
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
|
||||
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
|
||||
wayland-client = { version = "0.31.1", optional = true }
|
||||
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
|
||||
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
|
||||
x11-dl = { version = "2.19.1", optional = true }
|
||||
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
||||
xkbcommon-dl = "0.4.2"
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
orbclient = { version = "0.3.42", default-features = false }
|
||||
@@ -164,6 +177,8 @@ redox_syscall = "0.3"
|
||||
package = "web-sys"
|
||||
version = "0.3.64"
|
||||
features = [
|
||||
'AbortController',
|
||||
'AbortSignal',
|
||||
'console',
|
||||
'CssStyleDeclaration',
|
||||
'Document',
|
||||
@@ -179,6 +194,8 @@ features = [
|
||||
'IntersectionObserverEntry',
|
||||
'KeyboardEvent',
|
||||
'MediaQueryList',
|
||||
'MessageChannel',
|
||||
'MessagePort',
|
||||
'Node',
|
||||
'PageTransitionEvent',
|
||||
'PointerEvent',
|
||||
|
||||
21
FEATURES.md
21
FEATURES.md
@@ -1,6 +1,6 @@
|
||||
# Winit Scope
|
||||
|
||||
Winit aims to expose an interface that abstracts over window creation and input handling, and can
|
||||
Winit aims to expose an interface that abstracts over window creation and input handling and can
|
||||
be used to create both games and applications. It supports the following main graphical platforms:
|
||||
- Desktop
|
||||
- Windows 7+ (10+ is tested regularly)
|
||||
@@ -45,10 +45,10 @@ be released and the library will enter maintenance mode. For the most part, new
|
||||
be added past this point. New platform features may be accepted and exposed through point releases.
|
||||
|
||||
### Tier upgrades
|
||||
Some platform features could in theory be exposed across multiple platforms, but have not gone
|
||||
Some platform features could, in theory, be exposed across multiple platforms, but have not gone
|
||||
through the implementation work necessary to function on all platforms. When one of these features
|
||||
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 gets deprecated and become permanently
|
||||
If that gets accepted, the platform-specific functions get deprecated and become permanently
|
||||
exposed through the core, cross-platform API.
|
||||
|
||||
# Features
|
||||
@@ -88,7 +88,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **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
|
||||
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
|
||||
@@ -105,7 +105,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **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 icon**: Changing the cursor icon or hiding the cursor.
|
||||
- **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.
|
||||
@@ -150,13 +150,13 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
* Setting the `UIView` hidpi factor
|
||||
* Valid orientations
|
||||
* Home indicator visibility
|
||||
* Status bar visibility
|
||||
* Deferrring system gestures
|
||||
* Status bar visibility and style
|
||||
* Deferring system gestures
|
||||
* Getting the device idiom
|
||||
* Getting the preferred video mode
|
||||
|
||||
### Web
|
||||
* Get if systems preferred color scheme is "dark"
|
||||
* Get if the systems preferred color scheme is "dark"
|
||||
|
||||
## Usability
|
||||
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
||||
@@ -166,7 +166,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
Legend:
|
||||
|
||||
- ✔️: Works as intended
|
||||
- ▢: Mostly works but some bugs are known
|
||||
- ▢: Mostly works, but some bugs are known
|
||||
- ❌: Missing feature or large bugs making it unusable
|
||||
- **N/A**: Not applicable for this platform
|
||||
- ❓: Unknown status
|
||||
@@ -182,6 +182,7 @@ Legend:
|
||||
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|
||||
|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|
||||
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|
||||
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||
@@ -205,7 +206,7 @@ Legend:
|
||||
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|
||||
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
||||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|
||||
|Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
||||
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|
||||
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|
||||
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
|
||||
The winit maintainers would like to recognize the following former winit
|
||||
contributors, without whom winit would not exist in its current form. We thank
|
||||
them deeply for their time and efforts, and wish them best of luck in their
|
||||
them deeply for their time and efforts and wish them the best of luck in their
|
||||
future endeavors:
|
||||
|
||||
* [@tomaka]: For creating the winit project and guiding it through its early
|
||||
years of existence.
|
||||
* [@vberger]: For diligently creating the Wayland backend, and being its
|
||||
* [@vberger]: For diligently creating the Wayland backend and being its
|
||||
extremely helpful and benevolent maintainer for years.
|
||||
* [@francesca64]: For taking over the responsibility of maintaining almost every
|
||||
winit backend, and standardizing HiDPI support across all of them.
|
||||
* [@Osspial]: For heroically landing EventLoop 2.0, and valiantly ushering in a
|
||||
winit backend and standardizing HiDPI support across all of them.
|
||||
* [@Osspial]: For heroically landing EventLoop 2.0 and valiantly ushering in a
|
||||
vastly more sustainable era of winit.
|
||||
* [@goddessfreya]: For selflessly taking over maintainership of glutin, and her
|
||||
* [@goddessfreya]: For selflessly taking over maintainership of glutin and her
|
||||
stellar dedication to improving both winit and glutin.
|
||||
* [@ArturKovacs]: For consistently maintaining the macOS backend, and his
|
||||
immense involvement in designing and implementing the new keyboard API.
|
||||
* [@ArturKovacs]: For consistently maintaining the macOS backend and for his immense involvement in designing and implementing the new keyboard API.
|
||||
|
||||
[@tomaka]: https://github.com/tomaka
|
||||
[@vberger]: https://github.com/vberger
|
||||
|
||||
115
README.md
115
README.md
@@ -6,14 +6,14 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.29.0-beta.1"
|
||||
winit = "0.29.14"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
|
||||
For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md).
|
||||
|
||||
For features _outside_ the scope of winit, see [Missing features provided by other crates](https://github.com/rust-windowing/winit/wiki/Missing-features-provided-by-other-crates) in the wiki.
|
||||
For features _outside_ the scope of winit, see [Are we GUI Yet?](https://areweguiyet.com/) and [Are we game yet?](https://arewegameyet.rs/), depending on what kind of project you're looking to do.
|
||||
|
||||
## Contact Us
|
||||
|
||||
@@ -26,39 +26,12 @@ Join us in any of these:
|
||||
|
||||
Winit is a window creation and management library. It can create windows and lets you handle
|
||||
events (for example: the window being resized, a key being pressed, a mouse movement, etc.)
|
||||
produced by window.
|
||||
produced by the window.
|
||||
|
||||
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to
|
||||
show something on the window you need to use the platform-specific getters provided by winit, or
|
||||
another library.
|
||||
|
||||
```rust
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Winit is only officially supported on the latest stable version of the Rust compiler.
|
||||
|
||||
### Cargo Features
|
||||
|
||||
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
|
||||
@@ -67,6 +40,34 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
|
||||
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
|
||||
* `mint`: Enables mint (math interoperability standard types) conversions.
|
||||
|
||||
## MSRV Policy
|
||||
|
||||
This crate's Minimum Supported Rust Version (MSRV) is **1.65**. Changes to
|
||||
the MSRV will be accompanied by a minor version bump.
|
||||
|
||||
As a **tentative** policy, the upper bound of the MSRV is given by the following
|
||||
formula:
|
||||
|
||||
```
|
||||
min(sid, stable - 3)
|
||||
```
|
||||
|
||||
Where `sid` is the current version of `rustc` provided by [Debian Sid], and
|
||||
`stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability.
|
||||
|
||||
[Debian Sid]: https://packages.debian.org/sid/rustc
|
||||
|
||||
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
|
||||
capped at the latest stable version of Rust minus three. This inconsistency is
|
||||
not reflected in Cargo metadata, as it is not powerful enough to expose this
|
||||
restriction.
|
||||
|
||||
All crates in the [`rust-windowing`] organizations have the
|
||||
same MSRV policy.
|
||||
|
||||
[`rust-windowing`]: https://github.com/rust-windowing
|
||||
|
||||
### Platform-specific usage
|
||||
|
||||
#### Wayland
|
||||
@@ -84,7 +85,7 @@ either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
|
||||
create a `<canvas>` element which you can then retrieve][web canvas getter] and
|
||||
insert it into the DOM yourself.
|
||||
|
||||
For example code using Winit with WebAssembly, check out the [web example]. For
|
||||
For the example code using Winit with WebAssembly, check out the [web example]. For
|
||||
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
|
||||
book].
|
||||
|
||||
@@ -95,7 +96,7 @@ book].
|
||||
|
||||
#### Android
|
||||
|
||||
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/0.7.0/ndk/) crate.
|
||||
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/latest/ndk/) crate.
|
||||
|
||||
Native Android applications need some form of "glue" crate that is responsible
|
||||
for defining the main entry point for your Rust application as well as tracking
|
||||
@@ -107,13 +108,14 @@ glue crate (prior to `0.28` it used
|
||||
|
||||
The version of the glue crate that your application depends on _must_ match the
|
||||
version that Winit depends on because the glue crate is responsible for your
|
||||
application's main entrypoint. If Cargo resolves multiple versions they will
|
||||
application's main entry point. If Cargo resolves multiple versions, they will
|
||||
clash.
|
||||
|
||||
`winit` glue compatibility table:
|
||||
|
||||
| winit | ndk-glue |
|
||||
| :---: | :--------------------------: |
|
||||
| 0.29 | `android-activity = "0.5"` |
|
||||
| 0.28 | `android-activity = "0.4"` |
|
||||
| 0.27 | `ndk-glue = "0.7"` |
|
||||
| 0.26 | `ndk-glue = "0.5"` |
|
||||
@@ -124,7 +126,7 @@ The recommended way to avoid a conflict with the glue version is to avoid explic
|
||||
depending on the `android-activity` crate, and instead consume the API that
|
||||
is re-exported by Winit under `winit::platform::android::activity::*`
|
||||
|
||||
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
|
||||
Running on an Android device needs a dynamic system library. Add this to Cargo.toml:
|
||||
|
||||
```toml
|
||||
[lib]
|
||||
@@ -132,14 +134,14 @@ name = "main"
|
||||
crate-type = ["cdylib"]
|
||||
```
|
||||
|
||||
All Android applications are based on an `Activity` subclass and the
|
||||
All Android applications are based on an `Activity` subclass, and the
|
||||
`android-activity` crate is designed to support different choices for this base
|
||||
class. Your application _must_ specify the base class it needs via a feature flag:
|
||||
|
||||
| Base Class | Feature Flag | Notes |
|
||||
| :--------------: | :---------------: | :-----: |
|
||||
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
|
||||
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
|
||||
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
|
||||
|
||||
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
|
||||
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
|
||||
@@ -148,40 +150,13 @@ class. Your application _must_ specify the base class it needs via a feature fla
|
||||
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
|
||||
[Gradle]: https://developer.android.com/studio/build
|
||||
|
||||
For example, add this to Cargo.toml:
|
||||
```toml
|
||||
winit = { version = "0.28", features = [ "android-native-activity" ] }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.11.0"
|
||||
```
|
||||
|
||||
And, for example, define an entry point for your library like this:
|
||||
```rust
|
||||
#[cfg(target_os = "android")]
|
||||
use winit::platform::android::activity::AndroidApp;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[no_mangle]
|
||||
fn android_main(app: AndroidApp) {
|
||||
use winit::platform::android::EventLoopBuilderExtAndroid;
|
||||
|
||||
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Trace));
|
||||
|
||||
let event_loop = EventLoopBuilder::with_user_event()
|
||||
.with_android_app(app)
|
||||
.build();
|
||||
_main(event_loop);
|
||||
}
|
||||
```
|
||||
|
||||
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
|
||||
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
|
||||
|
||||
##### Converting from `ndk-glue` to `android-activity`
|
||||
|
||||
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be:
|
||||
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
|
||||
1. Remove `ndk-glue` from your `Cargo.toml`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.28", features = [ "android-native-activity" ] }`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.14", features = [ "android-native-activity" ] }`
|
||||
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
|
||||
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
|
||||
|
||||
@@ -192,13 +167,13 @@ doing anything; this includes creating windows, fetching monitors, drawing,
|
||||
and so on, see issues [#2238], [#2051] and [#2087].
|
||||
|
||||
If you encounter problems, you should try doing your initialization inside
|
||||
`Event::NewEvents(StartCause::Init)`.
|
||||
`Event::Resumed`.
|
||||
|
||||
#### iOS
|
||||
|
||||
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
|
||||
by all UI related code, see issue [#1705]. You should consider creating your windows
|
||||
inside `Event::NewEvents(StartCause::Init)`.
|
||||
by all UI-related code (see issue [#1705]). It would be best to consider creating your windows
|
||||
inside `Event::Resumed`.
|
||||
|
||||
|
||||
[#2238]: https://github.com/rust-windowing/winit/issues/2238
|
||||
@@ -208,5 +183,5 @@ inside `Event::NewEvents(StartCause::Init)`.
|
||||
|
||||
#### Redox OS
|
||||
|
||||
Redox OS has some functionality not present yet, that will be implemented when
|
||||
Redox OS has some functionality not yet present that will be implemented when
|
||||
its orbital display server provides it.
|
||||
|
||||
@@ -9,4 +9,5 @@ disallowed-methods = [
|
||||
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
|
||||
{ path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
|
||||
]
|
||||
|
||||
14
deny.toml
14
deny.toml
@@ -31,16 +31,10 @@ multiple-versions = "deny"
|
||||
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
||||
deny = []
|
||||
skip = [
|
||||
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
|
||||
{ name = "nix" }, # differing version - as of 2023-03-02 whis can be solved with `cargo update && cargo update -p calloop --precise 0.10.2`
|
||||
{ name = "memoffset"}, # due to different nix versions.
|
||||
{ name = "memmap2" }, # sctk uses a different version until the next update
|
||||
{ name = "libloading" }, # x11rb uses a different version until the next update
|
||||
{ name = "syn" }, # https://github.com/rust-mobile/ndk/issues/392 and https://github.com/rustwasm/wasm-bindgen/issues/3390
|
||||
{ name = "num_enum"}, # See above ^, waiting for release
|
||||
{ name = "num_enum_derive"},# See above ^, waiting for release
|
||||
{ name = "miniz_oxide"}, # https://github.com/rust-lang/flate2-rs/issues/340
|
||||
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
|
||||
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
|
||||
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
|
||||
{ name = "libloading" }, # x11rb uses a different version until the next update
|
||||
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
|
||||
]
|
||||
skip-tree = []
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ These images are used in the documentation of `winit`.
|
||||
## keyboard_*.svg
|
||||
|
||||
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
|
||||
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It is
|
||||
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
|
||||
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
|
||||
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
|
||||
which have been released under the same license as a derivative work.
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
#[cfg(any(x11_platform, macos_platform, windows_platform))]
|
||||
#[cfg(all(
|
||||
feature = "rwh_06",
|
||||
any(x11_platform, macos_platform, windows_platform)
|
||||
))]
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[cfg(any(x11_platform, macos_platform, windows_platform))]
|
||||
#[cfg(all(
|
||||
feature = "rwh_06",
|
||||
any(x11_platform, macos_platform, windows_platform)
|
||||
))]
|
||||
#[allow(deprecated)]
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use winit::{
|
||||
dpi::{LogicalPosition, LogicalSize, Position},
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||
window::raw_window_handle::HasRawWindowHandle,
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
raw_window_handle::HasRawWindowHandle,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
|
||||
@@ -19,7 +26,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
event_loop: &EventLoopWindowTarget<()>,
|
||||
windows: &mut HashMap<WindowId, Window>,
|
||||
) {
|
||||
let parent = parent.raw_window_handle();
|
||||
let parent = parent.raw_window_handle().unwrap();
|
||||
let mut builder = WindowBuilder::new()
|
||||
.with_title("child window")
|
||||
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
|
||||
@@ -46,14 +53,12 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
println!("parent window: {parent_window:?})");
|
||||
|
||||
event_loop.run(move |event: Event<()>, event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
event_loop.run(move |event: Event<()>, elwt| {
|
||||
if let Event::WindowEvent { event, window_id } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
windows.clear();
|
||||
*control_flow = ControlFlow::Exit;
|
||||
elwt.exit();
|
||||
}
|
||||
WindowEvent::CursorEntered { device_id: _ } => {
|
||||
// On x11, println when the cursor entered in a window even if the child window is created
|
||||
@@ -70,19 +75,23 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
},
|
||||
..
|
||||
} => {
|
||||
spawn_child_window(&parent_window, event_loop, &mut windows);
|
||||
spawn_child_window(&parent_window, elwt, &mut windows);
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
fill::fill_window(window);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if let Event::RedrawRequested(wid) = event {
|
||||
if let Some(window) = windows.get(&wid) {
|
||||
fill::fill_window(window);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))]
|
||||
#[cfg(not(all(
|
||||
feature = "rwh_06",
|
||||
any(x11_platform, macos_platform, windows_platform)
|
||||
)))]
|
||||
fn main() {
|
||||
panic!("This example is supported only on x11, macOS, and Windows.");
|
||||
panic!("This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature enabled.");
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ use web_time as time;
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::{Key, NamedKey},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
let mut wait_cancelled = false;
|
||||
let mut close_requested = false;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
event_loop.run(move |event, elwt| {
|
||||
use winit::event::StartCause;
|
||||
println!("{event:?}");
|
||||
match event {
|
||||
@@ -88,11 +88,14 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
request_redraw = !request_redraw;
|
||||
println!("\nrequest_redraw: {request_redraw}\n");
|
||||
}
|
||||
Key::Escape => {
|
||||
Key::Named(NamedKey::Escape) => {
|
||||
close_requested = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
@@ -101,25 +104,24 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
}
|
||||
|
||||
match mode {
|
||||
Mode::Wait => control_flow.set_wait(),
|
||||
Mode::Wait => elwt.set_control_flow(ControlFlow::Wait),
|
||||
Mode::WaitUntil => {
|
||||
if !wait_cancelled {
|
||||
control_flow.set_wait_until(time::Instant::now() + WAIT_TIME);
|
||||
elwt.set_control_flow(ControlFlow::WaitUntil(
|
||||
time::Instant::now() + WAIT_TIME,
|
||||
));
|
||||
}
|
||||
}
|
||||
Mode::Poll => {
|
||||
thread::sleep(POLL_SLEEP_TIME);
|
||||
control_flow.set_poll();
|
||||
elwt.set_control_flow(ControlFlow::Poll);
|
||||
}
|
||||
};
|
||||
|
||||
if close_requested {
|
||||
control_flow.set_exit();
|
||||
elwt.exit();
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_window_id) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -19,40 +19,33 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
let mut cursor_idx = 0;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
|
||||
window.set_cursor_icon(CURSORS[cursor_idx]);
|
||||
if cursor_idx < CURSORS.len() - 1 {
|
||||
cursor_idx += 1;
|
||||
} else {
|
||||
cursor_idx = 0;
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
|
||||
window.set_cursor_icon(CURSORS[cursor_idx]);
|
||||
if cursor_idx < CURSORS.len() - 1 {
|
||||
cursor_idx += 1;
|
||||
} else {
|
||||
cursor_idx = 0;
|
||||
}
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
elwt.exit();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::{Key, ModifiersState},
|
||||
keyboard::{Key, ModifiersState, NamedKey},
|
||||
window::{CursorGrabMode, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -22,56 +22,52 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
let mut modifiers = ModifiersState::default();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let result = match key {
|
||||
Key::Escape => {
|
||||
control_flow.set_exit();
|
||||
event_loop.run(move |event, elwt| match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let result = match key {
|
||||
Key::Named(NamedKey::Escape) => {
|
||||
elwt.exit();
|
||||
Ok(())
|
||||
}
|
||||
Key::Character(ch) => match ch.to_lowercase().as_str() {
|
||||
"g" => window.set_cursor_grab(CursorGrabMode::Confined),
|
||||
"l" => window.set_cursor_grab(CursorGrabMode::Locked),
|
||||
"a" => window.set_cursor_grab(CursorGrabMode::None),
|
||||
"h" => {
|
||||
window.set_cursor_visible(modifiers.shift_key());
|
||||
Ok(())
|
||||
}
|
||||
Key::Character(ch) => match ch.to_lowercase().as_str() {
|
||||
"g" => window.set_cursor_grab(CursorGrabMode::Confined),
|
||||
"l" => window.set_cursor_grab(CursorGrabMode::Locked),
|
||||
"a" => window.set_cursor_grab(CursorGrabMode::None),
|
||||
"h" => {
|
||||
window.set_cursor_visible(modifiers.shift_key());
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
},
|
||||
_ => Ok(()),
|
||||
};
|
||||
},
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
println!("error: {err}");
|
||||
}
|
||||
if let Err(err) = result {
|
||||
println!("error: {err}");
|
||||
}
|
||||
WindowEvent::ModifiersChanged(new) => modifiers = new.state(),
|
||||
_ => (),
|
||||
},
|
||||
Event::DeviceEvent { event, .. } => match event {
|
||||
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {delta:?}"),
|
||||
DeviceEvent::Button { button, state } => match state {
|
||||
ElementState::Pressed => println!("mouse button {button} pressed"),
|
||||
ElementState::Released => println!("mouse button {button} released"),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => fill::fill_window(&window),
|
||||
}
|
||||
WindowEvent::ModifiersChanged(new) => modifiers = new.state(),
|
||||
WindowEvent::RedrawRequested => fill::fill_window(&window),
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
Event::DeviceEvent { event, .. } => match event {
|
||||
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {delta:?}"),
|
||||
DeviceEvent::Button { button, state } => match state {
|
||||
ElementState::Pressed => println!("mouse button {button} pressed"),
|
||||
ElementState::Released => println!("mouse button {button} released"),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,20 +40,19 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
}
|
||||
});
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::UserEvent(event) => println!("user event: {event:?}"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
event_loop.run(move |event, elwt| match event {
|
||||
Event::UserEvent(event) => println!("user event: {event:?}"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => elwt.exit(),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -20,18 +20,16 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
let mut switched = false;
|
||||
let mut entered_id = window_2.id();
|
||||
let mut cursor_location = None;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
event_loop.run(move |event, elwt| match event {
|
||||
Event::NewEvents(StartCause::Init) => {
|
||||
eprintln!("Switch which window is to be dragged by pressing \"x\".")
|
||||
}
|
||||
Event::WindowEvent { event, window_id } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Pressed,
|
||||
button: MouseButton::Left,
|
||||
..
|
||||
} => {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::CursorMoved { position, .. } => cursor_location = Some(position),
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
let window = if (window_id == window_1.id() && switched)
|
||||
|| (window_id == window_2.id() && !switched)
|
||||
{
|
||||
@@ -40,7 +38,15 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
&window_1
|
||||
};
|
||||
|
||||
window.drag_window().unwrap()
|
||||
match (button, state) {
|
||||
(MouseButton::Left, ElementState::Pressed) => window.drag_window().unwrap(),
|
||||
(MouseButton::Right, ElementState::Released) => {
|
||||
if let Some(position) = cursor_location {
|
||||
window.show_window_menu(position);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
WindowEvent::CursorEntered { .. } => {
|
||||
entered_id = window_id;
|
||||
@@ -54,20 +60,35 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
..
|
||||
},
|
||||
..
|
||||
} if c == "x" => {
|
||||
switched = !switched;
|
||||
name_windows(entered_id, switched, &window_1, &window_2);
|
||||
println!("Switched!")
|
||||
} => match c.as_str() {
|
||||
"x" => {
|
||||
switched = !switched;
|
||||
name_windows(entered_id, switched, &window_1, &window_2);
|
||||
println!("Switched!")
|
||||
}
|
||||
"d" => {
|
||||
let window = if (window_id == window_1.id() && switched)
|
||||
|| (window_id == window_2.id() && !switched)
|
||||
{
|
||||
&window_2
|
||||
} else {
|
||||
&window_1
|
||||
};
|
||||
|
||||
window.set_decorations(!window.is_decorated());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
if window_id == window_1.id() {
|
||||
fill::fill_window(&window_1);
|
||||
} else if window_id == window_2.id() {
|
||||
fill::fill_window(&window_2);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(wid) => {
|
||||
if wid == window_1.id() {
|
||||
fill::fill_window(&window_1);
|
||||
} else if wid == window_2.id() {
|
||||
fill::fill_window(&window_2);
|
||||
}
|
||||
}
|
||||
|
||||
_ => (),
|
||||
})
|
||||
}
|
||||
|
||||
56
examples/focus.rs
Normal file
56
examples/focus.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
//! Example for focusing a window.
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
#[cfg(not(wasm_platform))]
|
||||
use std::time;
|
||||
#[cfg(wasm_platform)]
|
||||
use web_time as time;
|
||||
use winit::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut deadline = time::Instant::now() + time::Duration::from_secs(3);
|
||||
event_loop.run(move |event, elwt| {
|
||||
match event {
|
||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
||||
// Timeout reached; focus the window.
|
||||
println!("Re-focusing the window.");
|
||||
deadline += time::Duration::from_secs(3);
|
||||
window.focus_window();
|
||||
}
|
||||
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
// Notify the windowing system that we'll be presenting to the window.
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
elwt.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(deadline));
|
||||
})
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::keyboard::Key;
|
||||
use winit::keyboard::{Key, NamedKey};
|
||||
use winit::window::{Fullscreen, WindowBuilder};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -52,12 +52,10 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
println!("- I\tToggle mIn size limit");
|
||||
println!("- A\tToggle mAx size limit");
|
||||
|
||||
event_loop.run(move |event, elwt, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
@@ -67,7 +65,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
},
|
||||
..
|
||||
} => match key {
|
||||
Key::Escape => control_flow.set_exit(),
|
||||
Key::Named(NamedKey::Escape) => elwt.exit(),
|
||||
// WARNING: Consider using `key_without_modifers()` if available on your platform.
|
||||
// See the `key_binding` example
|
||||
Key::Character(ch) => match ch.to_lowercase().as_str() {
|
||||
@@ -155,12 +153,11 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,70 +22,65 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
let mut close_requested = false;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
// `CloseRequested` is sent when the close button on the window is pressed (or
|
||||
// through whatever other mechanisms the window manager provides for closing a
|
||||
// window). If you don't handle this event, the close button won't actually do
|
||||
// anything.
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
// `CloseRequested` is sent when the close button on the window is pressed (or
|
||||
// through whatever other mechanisms the window manager provides for closing a
|
||||
// window). If you don't handle this event, the close button won't actually do
|
||||
// anything.
|
||||
// A common thing to do here is prompt the user if they have unsaved work.
|
||||
// Creating a proper dialog box for that is far beyond the scope of this
|
||||
// example, so here we'll just respond to the Y and N keys.
|
||||
println!("Are you ready to bid your window farewell? [Y/N]");
|
||||
close_requested = true;
|
||||
|
||||
// A common thing to do here is prompt the user if they have unsaved work.
|
||||
// Creating a proper dialog box for that is far beyond the scope of this
|
||||
// example, so here we'll just respond to the Y and N keys.
|
||||
println!("Are you ready to bid your window farewell? [Y/N]");
|
||||
close_requested = true;
|
||||
|
||||
// In applications where you can safely close the window without further
|
||||
// action from the user, this is generally where you'd handle cleanup before
|
||||
// closing the window. How to close the window is detailed in the handler for
|
||||
// the Y key.
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
// WARNING: Consider using `key_without_modifers()` if available on your platform.
|
||||
// See the `key_binding` example
|
||||
match key.as_ref() {
|
||||
Key::Character("y") => {
|
||||
if close_requested {
|
||||
// This is where you'll want to do any cleanup you need.
|
||||
println!("Buh-bye!");
|
||||
|
||||
// For a single-window application like this, you'd normally just
|
||||
// break out of the event loop here. If you wanted to keep running the
|
||||
// event loop (i.e. if it's a multi-window application), you need to
|
||||
// drop the window. That closes it, and results in `Destroyed` being
|
||||
// sent.
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
Key::Character("n") => {
|
||||
if close_requested {
|
||||
println!("Your window will continue to stay by your side.");
|
||||
close_requested = false;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
// In applications where you can safely close the window without further
|
||||
// action from the user, this is generally where you'd handle cleanup before
|
||||
// closing the window. How to close the window is detailed in the handler for
|
||||
// the Y key.
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
// WARNING: Consider using `key_without_modifers()` if available on your platform.
|
||||
// See the `key_binding` example
|
||||
match key.as_ref() {
|
||||
Key::Character("y") => {
|
||||
if close_requested {
|
||||
// This is where you'll want to do any cleanup you need.
|
||||
println!("Buh-bye!");
|
||||
|
||||
// For a single-window application like this, you'd normally just
|
||||
// break out of the event loop here. If you wanted to keep running the
|
||||
// event loop (i.e. if it's a multi-window application), you need to
|
||||
// drop the window. That closes it, and results in `Destroyed` being
|
||||
// sent.
|
||||
elwt.exit();
|
||||
}
|
||||
}
|
||||
Key::Character("n") => {
|
||||
if close_requested {
|
||||
println!("Your window will continue to stay by your side.");
|
||||
close_requested = false;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
107
examples/ime.rs
107
examples/ime.rs
@@ -5,8 +5,8 @@ use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
event::{ElementState, Event, Ime, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::{Key, KeyCode},
|
||||
event_loop::EventLoop,
|
||||
keyboard::NamedKey,
|
||||
window::{ImePurpose, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -39,71 +39,56 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
|
||||
let mut ime_pos = PhysicalPosition::new(0.0, 0.0);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CursorMoved { position, .. },
|
||||
..
|
||||
} => {
|
||||
cursor_position = position;
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
println!(
|
||||
"Setting ime position to {}, {}",
|
||||
cursor_position.x, cursor_position.y
|
||||
);
|
||||
ime_pos = cursor_position;
|
||||
if may_show_ime {
|
||||
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
cursor_position = position;
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::Ime(event),
|
||||
..
|
||||
} => {
|
||||
println!("{event:?}");
|
||||
may_show_ime = event != Ime::Disabled;
|
||||
if may_show_ime {
|
||||
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Released,
|
||||
..
|
||||
} => {
|
||||
println!(
|
||||
"Setting ime position to {}, {}",
|
||||
cursor_position.x, cursor_position.y
|
||||
);
|
||||
ime_pos = cursor_position;
|
||||
if may_show_ime {
|
||||
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::KeyboardInput { event, .. },
|
||||
..
|
||||
} => {
|
||||
println!("key: {event:?}");
|
||||
WindowEvent::Ime(event) => {
|
||||
println!("{event:?}");
|
||||
may_show_ime = event != Ime::Disabled;
|
||||
if may_show_ime {
|
||||
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput { event, .. } => {
|
||||
println!("key: {event:?}");
|
||||
|
||||
if event.state == ElementState::Pressed && event.physical_key == KeyCode::F2 {
|
||||
ime_allowed = !ime_allowed;
|
||||
window.set_ime_allowed(ime_allowed);
|
||||
println!("\nIME allowed: {ime_allowed}\n");
|
||||
if event.state == ElementState::Pressed && event.logical_key == NamedKey::F2 {
|
||||
ime_allowed = !ime_allowed;
|
||||
window.set_ime_allowed(ime_allowed);
|
||||
println!("\nIME allowed: {ime_allowed}\n");
|
||||
}
|
||||
if event.state == ElementState::Pressed && event.logical_key == NamedKey::F3 {
|
||||
ime_purpose = match ime_purpose {
|
||||
ImePurpose::Normal => ImePurpose::Password,
|
||||
ImePurpose::Password => ImePurpose::Terminal,
|
||||
_ => ImePurpose::Normal,
|
||||
};
|
||||
window.set_ime_purpose(ime_purpose);
|
||||
println!("\nIME purpose: {ime_purpose:?}\n");
|
||||
}
|
||||
}
|
||||
if event.state == ElementState::Pressed && event.logical_key == Key::F3 {
|
||||
ime_purpose = match ime_purpose {
|
||||
ImePurpose::Normal => ImePurpose::Password,
|
||||
ImePurpose::Password => ImePurpose::Terminal,
|
||||
_ => ImePurpose::Normal,
|
||||
};
|
||||
window.set_ime_purpose(ime_purpose);
|
||||
println!("\nIME purpose: {ime_purpose:?}\n");
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{ElementState, Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
keyboard::{Key, ModifiersState},
|
||||
// WARNING: This is not available on all platforms (for example on the web).
|
||||
platform::modifier_supplement::KeyEventExtModifierSupplement,
|
||||
@@ -31,12 +31,10 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
let mut modifiers = ModifiersState::default();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::ModifiersChanged(new) => {
|
||||
modifiers = new.state();
|
||||
}
|
||||
@@ -54,12 +52,11 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
@@ -34,12 +34,10 @@ In both cases the example window should move like the content of a scroll area i
|
||||
In other words, the deltas indicate the direction in which to move the content (in this case the window)."
|
||||
);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::MouseWheel { delta, .. } => match delta {
|
||||
winit::event::MouseScrollDelta::LineDelta(x, y) => {
|
||||
println!("mouse wheel Line Delta: ({x},{y})");
|
||||
@@ -57,12 +55,11 @@ In other words, the deltas indicate the direction in which to move the content (
|
||||
window.set_outer_position(pos)
|
||||
}
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::{Key, ModifiersState},
|
||||
keyboard::{Key, ModifiersState, NamedKey},
|
||||
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel},
|
||||
};
|
||||
|
||||
@@ -65,17 +65,17 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
},
|
||||
..
|
||||
} => {
|
||||
use Key::{ArrowLeft, ArrowRight};
|
||||
use NamedKey::{ArrowLeft, ArrowRight};
|
||||
window.set_title(&format!("{key:?}"));
|
||||
let state = !modifiers.shift_key();
|
||||
match key {
|
||||
// Cycle through video modes
|
||||
Key::ArrowRight | Key::ArrowLeft => {
|
||||
video_mode_id = match key {
|
||||
ArrowLeft => video_mode_id.saturating_sub(1),
|
||||
ArrowRight => (video_modes.len() - 1).min(video_mode_id + 1),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Key::Named(ArrowRight) | Key::Named(ArrowLeft) => {
|
||||
if key == ArrowLeft {
|
||||
video_mode_id = video_mode_id.saturating_sub(1);
|
||||
} else if key == ArrowRight {
|
||||
video_mode_id = (video_modes.len() - 1).min(video_mode_id + 1);
|
||||
}
|
||||
println!("Picking video mode: {}", video_modes[video_mode_id]);
|
||||
}
|
||||
// WARNING: Consider using `key_without_modifers()` if available on your platform.
|
||||
@@ -173,11 +173,10 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
}
|
||||
});
|
||||
}
|
||||
event_loop.run(move |event, _event_loop, control_flow| {
|
||||
match !window_senders.is_empty() {
|
||||
true => control_flow.set_wait(),
|
||||
false => control_flow.set_exit(),
|
||||
};
|
||||
event_loop.run(move |event, elwt| {
|
||||
if window_senders.is_empty() {
|
||||
elwt.exit()
|
||||
}
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => match event {
|
||||
WindowEvent::CloseRequested
|
||||
@@ -186,7 +185,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
event:
|
||||
KeyEvent {
|
||||
state: ElementState::Released,
|
||||
logical_key: Key::Escape,
|
||||
logical_key: Key::Named(NamedKey::Escape),
|
||||
..
|
||||
},
|
||||
..
|
||||
|
||||
@@ -4,9 +4,9 @@ use std::collections::HashMap;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{ElementState, Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
keyboard::{Key, NamedKey},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
@@ -26,45 +26,39 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
println!("Press N to open a new window.");
|
||||
|
||||
event_loop.run(move |event, event_loop, control_flow| {
|
||||
control_flow.set_wait();
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, window_id } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("Window {window_id:?} has received the signal to close");
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("Window {window_id:?} has received the signal to close");
|
||||
// This drops the window, causing it to close.
|
||||
windows.remove(&window_id);
|
||||
|
||||
// This drops the window, causing it to close.
|
||||
windows.remove(&window_id);
|
||||
|
||||
if windows.is_empty() {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
if windows.is_empty() {
|
||||
elwt.exit();
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
state: ElementState::Pressed,
|
||||
logical_key: Key::Character(c),
|
||||
..
|
||||
},
|
||||
is_synthetic: false,
|
||||
..
|
||||
} if matches!(c.as_ref(), "n" | "N") => {
|
||||
let window = Window::new(event_loop).unwrap();
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event,
|
||||
is_synthetic: false,
|
||||
..
|
||||
} if event.state == ElementState::Pressed => match event.logical_key {
|
||||
Key::Named(NamedKey::Escape) => elwt.exit(),
|
||||
Key::Character(c) if c == "n" || c == "N" => {
|
||||
let window = Window::new(elwt).unwrap();
|
||||
println!("Opened a new window: {:?}", window.id());
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
fill::fill_window(window);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Event::RedrawRequested(window_id) => {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
fill::fill_window(window);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,27 +19,24 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
event_loop.run(move |event, elwt| {
|
||||
println!("{event:?}");
|
||||
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Released,
|
||||
..
|
||||
} => {
|
||||
window.request_redraw();
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
println!("\nredrawing!\n");
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
println!("\nredrawing!\n");
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,17 +33,18 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
}
|
||||
});
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
event_loop.run(move |event, elwt| {
|
||||
println!("{event:?}");
|
||||
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
} => elwt.exit(),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
println!("\nredrawing!\n");
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::KeyCode,
|
||||
keyboard::{KeyCode, PhysicalKey},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -27,16 +27,14 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
physical_key: KeyCode::Space,
|
||||
physical_key: PhysicalKey::Code(KeyCode::Space),
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
@@ -46,12 +44,11 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
println!("Resizable: {resizable}");
|
||||
window.set_resizable(resizable);
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ mod example {
|
||||
|
||||
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::keyboard::Key;
|
||||
use winit::platform::startup_notify::{
|
||||
EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify,
|
||||
};
|
||||
@@ -32,60 +31,53 @@ mod example {
|
||||
let mut counter = 0;
|
||||
let mut create_first_window = false;
|
||||
|
||||
event_loop.run(move |event, elwt, flow| {
|
||||
event_loop.run(move |event, elwt| {
|
||||
match event {
|
||||
Event::Resumed => create_first_window = true,
|
||||
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
} => {
|
||||
if logical_key == Key::Character("n".into()) {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
// Request a new activation token on this window.
|
||||
// Once we get it we will use it to create a window.
|
||||
window
|
||||
.request_activation_token()
|
||||
.expect("Failed to request activation token.");
|
||||
Event::WindowEvent { window_id, event } => match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
if logical_key == "n" {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
// Request a new activation token on this window.
|
||||
// Once we get it we will use it to create a window.
|
||||
window
|
||||
.request_activation_token()
|
||||
.expect("Failed to request activation token.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::CloseRequested,
|
||||
} => {
|
||||
// Remove the window from the map.
|
||||
windows.remove(&window_id);
|
||||
if windows.is_empty() {
|
||||
flow.set_exit();
|
||||
return;
|
||||
WindowEvent::CloseRequested => {
|
||||
// Remove the window from the map.
|
||||
windows.remove(&window_id);
|
||||
if windows.is_empty() {
|
||||
elwt.exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::ActivationTokenDone { token, .. },
|
||||
..
|
||||
} => {
|
||||
current_token = Some(token);
|
||||
}
|
||||
|
||||
Event::RedrawRequested(id) => {
|
||||
if let Some(window) = windows.get(&id) {
|
||||
super::fill::fill_window(window);
|
||||
WindowEvent::ActivationTokenDone { token, .. } => {
|
||||
current_token = Some(token);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
super::fill::fill_window(window);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// See if we've passed the deadline.
|
||||
@@ -109,8 +101,6 @@ mod example {
|
||||
counter += 1;
|
||||
create_first_window = false;
|
||||
}
|
||||
|
||||
flow.set_wait();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::{Theme, WindowBuilder},
|
||||
};
|
||||
@@ -27,53 +27,42 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
println!(" (L) Light theme");
|
||||
println!(" (D) Dark theme");
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::ThemeChanged(theme),
|
||||
window_id,
|
||||
..
|
||||
} if window_id == window.id() => {
|
||||
println!("Theme is changed: {theme:?}")
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key.as_ref() {
|
||||
Key::Character("A" | "a") => {
|
||||
println!("Theme was: {:?}", window.theme());
|
||||
window.set_theme(None);
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { window_id, event } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::ThemeChanged(theme) if window_id == window.id() => {
|
||||
println!("Theme is changed: {theme:?}")
|
||||
}
|
||||
Key::Character("L" | "l") => {
|
||||
println!("Theme was: {:?}", window.theme());
|
||||
window.set_theme(Some(Theme::Light));
|
||||
}
|
||||
Key::Character("D" | "d") => {
|
||||
println!("Theme was: {:?}", window.theme());
|
||||
window.set_theme(Some(Theme::Dark));
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key.as_ref() {
|
||||
Key::Character("A" | "a") => {
|
||||
println!("Theme was: {:?}", window.theme());
|
||||
window.set_theme(None);
|
||||
}
|
||||
Key::Character("L" | "l") => {
|
||||
println!("Theme was: {:?}", window.theme());
|
||||
window.set_theme(Some(Theme::Light));
|
||||
}
|
||||
Key::Character("D" | "d") => {
|
||||
println!("Theme was: {:?}", window.theme());
|
||||
window.set_theme(Some(Theme::Dark));
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
println!("\nredrawing!\n");
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
println!("\nredrawing!\n");
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use web_time::Instant;
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -27,22 +27,25 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
let timer_length = Duration::new(1, 0);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
event_loop.run(move |event, elwt| {
|
||||
println!("{event:?}");
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => {
|
||||
control_flow.set_wait_until(Instant::now() + timer_length);
|
||||
elwt.set_control_flow(ControlFlow::WaitUntil(Instant::now() + timer_length));
|
||||
}
|
||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
||||
control_flow.set_wait_until(Instant::now() + timer_length);
|
||||
elwt.set_control_flow(ControlFlow::WaitUntil(Instant::now() + timer_length));
|
||||
println!("\nTimer\n");
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
} => elwt.exit(),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -19,12 +19,10 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
println!("Only supported on macOS at the moment.");
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::TouchpadMagnify { delta, .. } => {
|
||||
if delta > 0.0 {
|
||||
println!("Zoomed in {delta}");
|
||||
@@ -42,10 +40,11 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
println!("Rotated clockwise {delta}");
|
||||
}
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if let Event::RedrawRequested(_) = event {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,19 +22,17 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
window.set_title("A fantastic window!");
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
event_loop.run(move |event, elwt| {
|
||||
println!("{event:?}");
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,17 +7,30 @@
|
||||
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
|
||||
//! also be used to fill the window buffer, but they are more complicated to use.
|
||||
|
||||
use winit::window::Window;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::cleanup_window;
|
||||
pub use platform::fill_window;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub(super) fn fill_window(window: &Window) {
|
||||
use softbuffer::{Context, Surface};
|
||||
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
|
||||
mod platform {
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use softbuffer::{Context, Surface};
|
||||
use winit::window::Window;
|
||||
use winit::window::WindowId;
|
||||
|
||||
thread_local! {
|
||||
// NOTE: You should never do things like that, create context and drop it before
|
||||
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
||||
// ManuallyDrop to prevent destructors from running.
|
||||
//
|
||||
// A static, thread-local map of graphics contexts to open windows.
|
||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
|
||||
}
|
||||
|
||||
/// The graphics context used to draw to a window.
|
||||
struct GraphicsContext {
|
||||
/// The global softbuffer context.
|
||||
@@ -35,52 +48,69 @@ pub(super) fn fill_window(window: &Window) {
|
||||
}
|
||||
}
|
||||
|
||||
fn surface(&mut self, w: &Window) -> &mut Surface {
|
||||
self.surfaces.entry(w.id()).or_insert_with(|| {
|
||||
unsafe { Surface::new(&self.context, w) }
|
||||
fn create_surface(&mut self, window: &Window) -> &mut Surface {
|
||||
self.surfaces.entry(window.id()).or_insert_with(|| {
|
||||
unsafe { Surface::new(&self.context, window) }
|
||||
.expect("Failed to create a softbuffer surface")
|
||||
})
|
||||
}
|
||||
|
||||
fn destroy_surface(&mut self, window: &Window) {
|
||||
self.surfaces.remove(&window.id());
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
// NOTE: You should never do things like that, create context and drop it before
|
||||
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
||||
// ManuallyDrop to prevent destructors from running.
|
||||
//
|
||||
// A static, thread-local map of graphics contexts to open windows.
|
||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
|
||||
pub fn fill_window(window: &Window) {
|
||||
GC.with(|gc| {
|
||||
let size = window.inner_size();
|
||||
let (Some(width), Some(height)) =
|
||||
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface = gc
|
||||
.get_or_insert_with(|| GraphicsContext::new(window))
|
||||
.create_surface(window);
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xFF181818;
|
||||
|
||||
surface
|
||||
.resize(width, height)
|
||||
.expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface
|
||||
.buffer_mut()
|
||||
.expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(DARK_GRAY);
|
||||
buffer
|
||||
.present()
|
||||
.expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
}
|
||||
|
||||
GC.with(|gc| {
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface = gc
|
||||
.get_or_insert_with(|| GraphicsContext::new(window))
|
||||
.surface(window);
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xFF181818;
|
||||
let size = window.inner_size();
|
||||
|
||||
surface
|
||||
.resize(
|
||||
NonZeroU32::new(size.width).expect("Width must be greater than zero"),
|
||||
NonZeroU32::new(size.height).expect("Height must be greater than zero"),
|
||||
)
|
||||
.expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface
|
||||
.buffer_mut()
|
||||
.expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(DARK_GRAY);
|
||||
buffer
|
||||
.present()
|
||||
.expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(window: &Window) {
|
||||
GC.with(|gc| {
|
||||
let mut gc = gc.borrow_mut();
|
||||
if let Some(context) = gc.as_mut() {
|
||||
context.destroy_surface(window);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub(super) fn fill_window(_window: &Window) {
|
||||
// No-op on mobile platforms.
|
||||
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
|
||||
mod platform {
|
||||
pub fn fill_window(_window: &winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(_window: &winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::KeyCode,
|
||||
keyboard::Key,
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -21,9 +21,7 @@ pub fn main() -> Result<(), impl std::error::Error> {
|
||||
#[cfg(wasm_platform)]
|
||||
let log_list = wasm::insert_canvas_and_create_log_list(&window);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
event_loop.run(move |event, elwt| {
|
||||
#[cfg(wasm_platform)]
|
||||
wasm::log_event(&log_list, &event);
|
||||
|
||||
@@ -31,7 +29,7 @@ pub fn main() -> Result<(), impl std::error::Error> {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
} if window_id == window.id() => elwt.exit(),
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
@@ -41,13 +39,13 @@ pub fn main() -> Result<(), impl std::error::Error> {
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
physical_key: KeyCode::KeyF,
|
||||
logical_key: Key::Character(c),
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
} if window_id == window.id() => {
|
||||
} if window_id == window.id() && c == "f" => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
@@ -65,7 +63,10 @@ mod wasm {
|
||||
|
||||
use softbuffer::{Surface, SurfaceExtWeb};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use winit::{event::Event, window::Window};
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn run() {
|
||||
@@ -116,6 +117,10 @@ mod wasm {
|
||||
// So we implement this basic logging system into the page to give developers an easy alternative.
|
||||
// As a bonus its also kind of handy on desktop.
|
||||
let event = match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => None,
|
||||
Event::WindowEvent { event, .. } => Some(format!("{event:?}")),
|
||||
Event::Resumed | Event::Suspended => Some(format!("{event:?}")),
|
||||
_ => None,
|
||||
|
||||
@@ -7,12 +7,11 @@ pub fn main() {
|
||||
#[cfg(wasm_platform)]
|
||||
mod wasm {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
use winit::{
|
||||
dpi::PhysicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
platform::web::WindowBuilderExtWebSys,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
@@ -47,18 +46,14 @@ This example demonstrates the desired future functionality which will possibly b
|
||||
// Render once with the size info we currently have
|
||||
render_circle(&canvas, window.inner_size());
|
||||
|
||||
let _ = event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::Resized(resize),
|
||||
window_id,
|
||||
} if window_id == window.id() => {
|
||||
render_circle(&canvas, resize);
|
||||
}
|
||||
_ => (),
|
||||
let _ = event_loop.run(move |event, _| match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::Resized(resize),
|
||||
window_id,
|
||||
} if window_id == window.id() => {
|
||||
render_circle(&canvas, resize);
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,23 +20,23 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
event_loop.run(move |event, elwt| {
|
||||
println!("{event:?}");
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
// Notify the windowing system that we'll be presenting to the window.
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
// Notify the windowing system that we'll be presenting to the window.
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -31,45 +31,38 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
event_loop.listen_device_events(DeviceEvents::Always);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key.as_ref() {
|
||||
Key::Character("F" | "f") => {
|
||||
let buttons = window.enabled_buttons();
|
||||
window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE);
|
||||
}
|
||||
Key::Character("G" | "g") => {
|
||||
let buttons = window.enabled_buttons();
|
||||
window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE);
|
||||
}
|
||||
Key::Character("H" | "h") => {
|
||||
let buttons = window.enabled_buttons();
|
||||
window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE);
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { window_id, event } = event {
|
||||
match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key.as_ref() {
|
||||
Key::Character("F" | "f") => {
|
||||
let buttons = window.enabled_buttons();
|
||||
window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE);
|
||||
}
|
||||
Key::Character("G" | "g") => {
|
||||
let buttons = window.enabled_buttons();
|
||||
window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE);
|
||||
}
|
||||
Key::Character("H" | "h") => {
|
||||
let buttons = window.enabled_buttons();
|
||||
window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
WindowEvent::CloseRequested if window_id == window.id() => elwt.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use winit::{
|
||||
dpi::{LogicalSize, PhysicalSize},
|
||||
event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent},
|
||||
event_loop::{DeviceEvents, EventLoop},
|
||||
keyboard::{Key, KeyCode},
|
||||
keyboard::{Key, KeyCode, PhysicalKey},
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -38,9 +38,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
event_loop.listen_device_events(DeviceEvents::Always);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
event_loop.run(move |event, elwt| {
|
||||
match event {
|
||||
// This used to use the virtual key, but the new API
|
||||
// only provides the `physical_key` (`Code`).
|
||||
@@ -53,14 +51,14 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
}),
|
||||
..
|
||||
} => match physical_key {
|
||||
KeyCode::KeyM => {
|
||||
PhysicalKey::Code(KeyCode::KeyM) => {
|
||||
if minimized {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
window.focus_window();
|
||||
}
|
||||
}
|
||||
KeyCode::KeyV => {
|
||||
PhysicalKey::Code(KeyCode::KeyV) => {
|
||||
if !visible {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
@@ -68,75 +66,71 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: Key::Character(key_str),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key_str.as_ref() {
|
||||
// WARNING: Consider using `key_without_modifers()` if available on your platform.
|
||||
// See the `key_binding` example
|
||||
"e" => {
|
||||
fn area(size: PhysicalSize<u32>) -> u32 {
|
||||
size.width * size.height
|
||||
}
|
||||
Event::WindowEvent { window_id, event } => match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: Key::Character(key_str),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key_str.as_ref() {
|
||||
// WARNING: Consider using `key_without_modifers()` if available on your platform.
|
||||
// See the `key_binding` example
|
||||
"e" => {
|
||||
fn area(size: PhysicalSize<u32>) -> u32 {
|
||||
size.width * size.height
|
||||
}
|
||||
|
||||
let monitor = window.current_monitor().unwrap();
|
||||
if let Some(mode) = monitor
|
||||
.video_modes()
|
||||
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
|
||||
{
|
||||
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
|
||||
} else {
|
||||
eprintln!("no video modes available");
|
||||
let monitor = window.current_monitor().unwrap();
|
||||
if let Some(mode) = monitor
|
||||
.video_modes()
|
||||
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
|
||||
{
|
||||
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
|
||||
} else {
|
||||
eprintln!("no video modes available");
|
||||
}
|
||||
}
|
||||
}
|
||||
"f" => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
let monitor = window.current_monitor();
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
|
||||
"f" => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
let monitor = window.current_monitor();
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
"p" => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
|
||||
"p" => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
|
||||
}
|
||||
}
|
||||
}
|
||||
"m" => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
"q" => {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
"v" => {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
}
|
||||
"x" => {
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
"m" => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
"q" => {
|
||||
elwt.exit();
|
||||
}
|
||||
"v" => {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
}
|
||||
"x" => {
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
WindowEvent::CloseRequested if window_id == window.id() => elwt.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::{CursorIcon, ResizeDirection, WindowBuilder},
|
||||
};
|
||||
@@ -27,12 +27,12 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
let mut border = false;
|
||||
let mut cursor_location = None;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
event_loop.run(move |event, elwt| match event {
|
||||
Event::NewEvents(StartCause::Init) => {
|
||||
eprintln!("Press 'B' to toggle borderless")
|
||||
}
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
if !window.is_decorated() {
|
||||
let new_location =
|
||||
@@ -68,11 +68,12 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
border = !border;
|
||||
window.set_decorations(border);
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::path::Path;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::Event,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::{Icon, WindowBuilder},
|
||||
};
|
||||
@@ -33,20 +33,16 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
use winit::event::WindowEvent::*;
|
||||
match event {
|
||||
CloseRequested => control_flow.set_exit(),
|
||||
DroppedFile(path) => {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::DroppedFile(path) => {
|
||||
window.set_window_icon(Some(load_icon(&path)));
|
||||
}
|
||||
WindowEvent::RedrawRequested => fill::fill_window(&window),
|
||||
_ => (),
|
||||
}
|
||||
} else if let Event::RedrawRequested(_) = event {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
error::EventLoopError,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
platform::run_ondemand::EventLoopExtRunOnDemand,
|
||||
platform::run_on_demand::EventLoopExtRunOnDemand,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
|
||||
@@ -30,8 +30,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> {
|
||||
let mut app = App::default();
|
||||
|
||||
event_loop.run_ondemand(move |event, event_loop, control_flow| {
|
||||
control_flow.set_wait();
|
||||
event_loop.run_on_demand(move |event, elwt| {
|
||||
println!("Run {idx}: {:?}", event);
|
||||
|
||||
if let Some(window) = &app.window {
|
||||
@@ -41,10 +40,14 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
window_id,
|
||||
} if window.id() == window_id => {
|
||||
println!("--------------------------------------------------------- Window {idx} CloseRequested");
|
||||
fill::cleanup_window(window);
|
||||
app.window = None;
|
||||
}
|
||||
Event::AboutToWait => window.request_redraw(),
|
||||
Event::RedrawRequested(_) => {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
fill::fill_window(window);
|
||||
}
|
||||
_ => (),
|
||||
@@ -57,7 +60,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
} if id == window_id => {
|
||||
println!("--------------------------------------------------------- Window {idx} Destroyed");
|
||||
app.window_id = None;
|
||||
control_flow.set_exit();
|
||||
elwt.exit();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@@ -65,7 +68,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Fantastic window number one!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(event_loop)
|
||||
.build(elwt)
|
||||
.unwrap();
|
||||
app.window_id = Some(window.id());
|
||||
app.window = Some(window);
|
||||
@@ -31,41 +31,38 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
let mut option_as_alt = window.option_as_alt();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
event_loop.run(move |event, elwt| match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => elwt.exit(),
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Pressed,
|
||||
button: MouseButton::Left,
|
||||
..
|
||||
} => {
|
||||
option_as_alt = match option_as_alt {
|
||||
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
|
||||
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
|
||||
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
|
||||
OptionAsAlt::Both => OptionAsAlt::None,
|
||||
};
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Pressed,
|
||||
button: MouseButton::Left,
|
||||
..
|
||||
} => {
|
||||
option_as_alt = match option_as_alt {
|
||||
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
|
||||
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
|
||||
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
|
||||
OptionAsAlt::Both => OptionAsAlt::None,
|
||||
};
|
||||
|
||||
println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}");
|
||||
window.set_option_as_alt(option_as_alt);
|
||||
}
|
||||
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}");
|
||||
window.set_option_as_alt(option_as_alt);
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
|
||||
_ => (),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ fn main() -> std::process::ExitCode {
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
@@ -32,9 +32,7 @@ fn main() -> std::process::ExitCode {
|
||||
|
||||
'main: loop {
|
||||
let timeout = Some(Duration::ZERO);
|
||||
let status = event_loop.pump_events(timeout, |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
let status = event_loop.pump_events(timeout, |event, elwt| {
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
// Print only Window events to reduce noise
|
||||
println!("{event:?}");
|
||||
@@ -44,11 +42,14 @@ fn main() -> std::process::ExitCode {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
} if window_id == window.id() => elwt.exit(),
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -2,9 +2,9 @@ use log::debug;
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{ElementState, Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
keyboard::NamedKey,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -24,27 +24,13 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
let mut has_increments = true;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: Key::Space,
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
window_id,
|
||||
} if window_id == window.id() => {
|
||||
event_loop.run(move |event, elwt| match event {
|
||||
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::KeyboardInput { event, .. }
|
||||
if event.logical_key == NamedKey::Space
|
||||
&& event.state == ElementState::Released =>
|
||||
{
|
||||
has_increments = !has_increments;
|
||||
|
||||
let new_increments = match window.resize_increments() {
|
||||
@@ -54,11 +40,13 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
debug!("Had increments: {}", new_increments.is_none());
|
||||
window.set_resize_increments(new_increments);
|
||||
}
|
||||
Event::AboutToWait => window.request_redraw(),
|
||||
Event::RedrawRequested(_) => {
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
Event::AboutToWait => window.request_redraw(),
|
||||
|
||||
_ => (),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
keyboard::{Key, NamedKey},
|
||||
platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
@@ -30,78 +30,73 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
println!("Press N to open a new window.");
|
||||
|
||||
event_loop.run(move |event, event_loop, control_flow| {
|
||||
control_flow.set_wait();
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, window_id } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("Window {window_id:?} has received the signal to close");
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("Window {window_id:?} has received the signal to close");
|
||||
// This drops the window, causing it to close.
|
||||
windows.remove(&window_id);
|
||||
|
||||
// This drops the window, causing it to close.
|
||||
windows.remove(&window_id);
|
||||
|
||||
if windows.is_empty() {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
if windows.is_empty() {
|
||||
elwt.exit();
|
||||
}
|
||||
WindowEvent::Resized(_) => {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
WindowEvent::Resized(_) => {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
window.request_redraw();
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
state: ElementState::Pressed,
|
||||
logical_key,
|
||||
..
|
||||
},
|
||||
is_synthetic: false,
|
||||
..
|
||||
} => match logical_key.as_ref() {
|
||||
Key::Character("t") => {
|
||||
let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier();
|
||||
let window = WindowBuilder::new()
|
||||
.with_tabbing_identifier(&tabbing_id)
|
||||
.build(event_loop)
|
||||
.unwrap();
|
||||
println!("Added a new tab: {:?}", window.id());
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
Key::Character("w") => {
|
||||
let _ = windows.remove(&window_id);
|
||||
}
|
||||
Key::ArrowRight => {
|
||||
windows.get(&window_id).unwrap().select_next_tab();
|
||||
}
|
||||
Key::ArrowLeft => {
|
||||
windows.get(&window_id).unwrap().select_previous_tab();
|
||||
}
|
||||
Key::Character(ch) => {
|
||||
if let Ok(index) = ch.parse::<NonZeroUsize>() {
|
||||
let index = index.get();
|
||||
// Select the last tab when pressing `9`.
|
||||
let window = windows.get(&window_id).unwrap();
|
||||
if index == 9 {
|
||||
window.select_tab_at_index(window.num_tabs() - 1)
|
||||
} else {
|
||||
window.select_tab_at_index(index - 1);
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
state: ElementState::Pressed,
|
||||
logical_key,
|
||||
..
|
||||
},
|
||||
is_synthetic: false,
|
||||
..
|
||||
} => match logical_key.as_ref() {
|
||||
Key::Character("t") => {
|
||||
let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier();
|
||||
let window = WindowBuilder::new()
|
||||
.with_tabbing_identifier(&tabbing_id)
|
||||
.build(elwt)
|
||||
.unwrap();
|
||||
println!("Added a new tab: {:?}", window.id());
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
Key::Character("w") => {
|
||||
let _ = windows.remove(&window_id);
|
||||
}
|
||||
Key::Named(NamedKey::ArrowRight) => {
|
||||
windows.get(&window_id).unwrap().select_next_tab();
|
||||
}
|
||||
Key::Named(NamedKey::ArrowLeft) => {
|
||||
windows.get(&window_id).unwrap().select_previous_tab();
|
||||
}
|
||||
Key::Character(ch) => {
|
||||
if let Ok(index) = ch.parse::<NonZeroUsize>() {
|
||||
let index = index.get();
|
||||
// Select the last tab when pressing `9`.
|
||||
let window = windows.get(&window_id).unwrap();
|
||||
if index == 9 {
|
||||
window.select_tab_at_index(window.num_tabs() - 1)
|
||||
} else {
|
||||
window.select_tab_at_index(index - 1);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
fill::fill_window(window);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Event::RedrawRequested(window_id) => {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
fill::fill_window(window);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,18 +32,19 @@ mod imple {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
event_loop.run(move |event, elwt| {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
} if window_id == window.id() => elwt.exit(),
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
// Notify the windowing system that we'll be presenting to the window.
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(&window);
|
||||
|
||||
40
src/dpi.rs
40
src/dpi.rs
@@ -4,13 +4,13 @@
|
||||
//!
|
||||
//! Modern computer screens don't have a consistent relationship between resolution and size.
|
||||
//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
|
||||
//! normally being less than a quarter the size of their desktop counterparts. What's more, neither
|
||||
//! desktop nor mobile screens are consistent resolutions within their own size classes - common
|
||||
//! typically being less than a quarter the size of their desktop counterparts. Moreover, neither
|
||||
//! desktop nor mobile screens have consistent resolutions within their own size classes - common
|
||||
//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
|
||||
//! and beyond.
|
||||
//!
|
||||
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
|
||||
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen,
|
||||
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen and
|
||||
//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
|
||||
//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
|
||||
//! problematic with text rendering, where quarter-sized text becomes a significant legibility
|
||||
@@ -25,12 +25,12 @@
|
||||
//!
|
||||
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is
|
||||
//! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
|
||||
//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device
|
||||
//! for example, a button that's usually 50 pixels across would be 100 pixels across on a device
|
||||
//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
|
||||
//!
|
||||
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
|
||||
//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's
|
||||
//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather
|
||||
//! usually a mistake since there's no consistent mapping between the scale factor and the screen's
|
||||
//! actual DPI. Unless printing to a physical medium, you should work in scaled pixels rather
|
||||
//! than any DPI-dependent units.
|
||||
//!
|
||||
//! ### Position and Size types
|
||||
@@ -42,11 +42,11 @@
|
||||
//! coordinates as input, allowing you to use the most convenient coordinate system for your
|
||||
//! particular application.
|
||||
//!
|
||||
//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the
|
||||
//! Winit's position and size types are generic over their exact pixel type, `P`, to allow the
|
||||
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
|
||||
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
|
||||
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
|
||||
//! will truncate the fractional part of the float, rather than properly round to the nearest
|
||||
//! will truncate the fractional part of the float rather than properly round to the nearest
|
||||
//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the
|
||||
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
|
||||
//! int, although rounding lessens the problem.
|
||||
@@ -55,35 +55,35 @@
|
||||
//!
|
||||
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
|
||||
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
|
||||
//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your
|
||||
//! application's UI elements and adjust how the platform changes the window's size to reflect the new
|
||||
//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor
|
||||
//! monitor or if the user changes their DPI settings. This allows you to rescale your application's
|
||||
//! UI elements and adjust how the platform changes the window's size to reflect the new scale
|
||||
//! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor
|
||||
//! can be found by calling [`window.scale_factor()`].
|
||||
//!
|
||||
//! ## How is the scale factor calculated?
|
||||
//!
|
||||
//! Scale factor is calculated differently on different platforms:
|
||||
//! The scale factor is calculated differently on different platforms:
|
||||
//!
|
||||
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
|
||||
//! display settings. While users are free to select any option they want, they're only given a
|
||||
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
|
||||
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7. The scale factor is
|
||||
//! global and changing it requires logging out. See [this article][windows_1] for technical
|
||||
//! details.
|
||||
//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
|
||||
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
|
||||
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
|
||||
//! the specific value varies across devices.
|
||||
//! - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific
|
||||
//! displays. When available, the user may pick a per-monitor scaling factor from a set of
|
||||
//! pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default,
|
||||
//! but the specific value varies across devices.
|
||||
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
|
||||
//! currently uses a three-pronged approach:
|
||||
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
|
||||
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable if present.
|
||||
//! + If not present, use the value set in `Xft.dpi` in Xresources.
|
||||
//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
|
||||
//!
|
||||
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
|
||||
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
|
||||
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
|
||||
//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always
|
||||
//! integers (most often 1 or 2).
|
||||
//! - **Wayland:** The scale factor is suggested by the compositor for each window individually. The
|
||||
//! monitor scale factor may differ from the window scale factor.
|
||||
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
|
||||
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
|
||||
//! information.
|
||||
|
||||
111
src/event.rs
111
src/event.rs
@@ -7,25 +7,24 @@
|
||||
//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! let mut control_flow = ControlFlow::Poll;
|
||||
//! let mut start_cause = StartCause::Init;
|
||||
//!
|
||||
//! while control_flow != ControlFlow::Exit {
|
||||
//! event_handler(NewEvents(start_cause), ..., &mut control_flow);
|
||||
//! while !elwt.exiting() {
|
||||
//! event_handler(NewEvents(start_cause), elwt);
|
||||
//!
|
||||
//! for e in (window events, user events, device events) {
|
||||
//! event_handler(e, ..., &mut control_flow);
|
||||
//! event_handler(e, elwt);
|
||||
//! }
|
||||
//!
|
||||
//! for w in (redraw windows) {
|
||||
//! event_handler(RedrawRequested(w), ..., &mut control_flow);
|
||||
//! event_handler(RedrawRequested(w), elwt);
|
||||
//! }
|
||||
//!
|
||||
//! event_handler(AboutToWait, ..., &mut control_flow);
|
||||
//! start_cause = wait_if_necessary(control_flow);
|
||||
//! event_handler(AboutToWait, elwt);
|
||||
//! start_cause = wait_if_necessary();
|
||||
//! }
|
||||
//!
|
||||
//! event_handler(LoopExiting, ..., &mut control_flow);
|
||||
//! event_handler(LoopExiting, elwt);
|
||||
//! ```
|
||||
//!
|
||||
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
|
||||
@@ -62,7 +61,7 @@ pub enum Event<T: 'static> {
|
||||
///
|
||||
/// This event type is useful as a place to put code that should be done before you start
|
||||
/// processing events, such as updating frame timing information for benchmarking or checking
|
||||
/// the [`StartCause`][crate::event::StartCause] to see if a timer set by
|
||||
/// the [`StartCause`] to see if a timer set by
|
||||
/// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed.
|
||||
NewEvents(StartCause),
|
||||
|
||||
@@ -218,26 +217,40 @@ pub enum Event<T: 'static> {
|
||||
/// ups and also lots of corresponding `AboutToWait` events.
|
||||
///
|
||||
/// This is not an ideal event to drive application rendering from and instead applications
|
||||
/// should render in response to [`Event::RedrawRequested`](crate::event::Event::RedrawRequested)
|
||||
/// events.
|
||||
/// should render in response to [`WindowEvent::RedrawRequested`] events.
|
||||
AboutToWait,
|
||||
|
||||
/// Emitted when a window should be redrawn.
|
||||
///
|
||||
/// This gets triggered in two scenarios:
|
||||
/// - The OS has performed an operation that's invalidated the window's contents (such as
|
||||
/// resizing the window).
|
||||
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
|
||||
///
|
||||
/// Winit will aggregate duplicate redraw requests into a single event, to
|
||||
/// help avoid duplicating rendering work.
|
||||
RedrawRequested(WindowId),
|
||||
|
||||
/// Emitted when the event loop is being shut down.
|
||||
///
|
||||
/// This is irreversible - if this event is emitted, it is guaranteed to be the last event that
|
||||
/// gets emitted. You generally want to treat this as a "do on quit" event.
|
||||
LoopExiting,
|
||||
|
||||
/// Emitted when the application has received a memory warning.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// ### Android
|
||||
///
|
||||
/// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The application
|
||||
/// must [release memory] or risk being killed.
|
||||
///
|
||||
/// [`onLowMemory`]: https://developer.android.com/reference/android/app/Application.html#onLowMemory()
|
||||
/// [release memory]: https://developer.android.com/topic/performance/memory#release
|
||||
///
|
||||
/// ### iOS
|
||||
///
|
||||
/// On iOS, the `MemoryWarning` event is emitted in response to an [`applicationDidReceiveMemoryWarning`]
|
||||
/// callback. The application must free as much memory as possible or risk being terminated, see
|
||||
/// [how to respond to memory warnings].
|
||||
///
|
||||
/// [`applicationDidReceiveMemoryWarning`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni
|
||||
/// [how to respond to memory warnings]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle/responding_to_memory_warnings
|
||||
///
|
||||
/// ### Others
|
||||
///
|
||||
/// - **macOS / Wayland / Windows / Orbital:** Unsupported.
|
||||
MemoryWarning,
|
||||
}
|
||||
|
||||
impl<T> Event<T> {
|
||||
@@ -250,10 +263,10 @@ impl<T> Event<T> {
|
||||
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
|
||||
NewEvents(cause) => Ok(NewEvents(cause)),
|
||||
AboutToWait => Ok(AboutToWait),
|
||||
RedrawRequested(wid) => Ok(RedrawRequested(wid)),
|
||||
LoopExiting => Ok(LoopExiting),
|
||||
Suspended => Ok(Suspended),
|
||||
Resumed => Ok(Resumed),
|
||||
MemoryWarning => Ok(MemoryWarning),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -545,15 +558,39 @@ pub enum WindowEvent {
|
||||
/// This is different to window visibility as it depends on whether the window is closed,
|
||||
/// minimised, set invisible, or fully occluded by another window.
|
||||
///
|
||||
/// Platform-specific behavior:
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// ### iOS
|
||||
///
|
||||
/// On iOS, the `Occluded(false)` event is emitted in response to an [`applicationWillEnterForeground`]
|
||||
/// callback which means the application should start preparing its data. The `Occluded(true)` event is
|
||||
/// emitted in response to an [`applicationDidEnterBackground`] callback which means the application
|
||||
/// should free resources (according to the [iOS application lifecycle]).
|
||||
///
|
||||
/// [`applicationWillEnterForeground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623076-applicationwillenterforeground
|
||||
/// [`applicationDidEnterBackground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground
|
||||
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
|
||||
///
|
||||
/// ### Others
|
||||
///
|
||||
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
||||
/// - **iOS / Android / Wayland / Windows / Orbital:** Unsupported.
|
||||
/// - **Android / Wayland / Windows / Orbital:** Unsupported.
|
||||
///
|
||||
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
|
||||
Occluded(bool),
|
||||
|
||||
/// Emitted when a window should be redrawn.
|
||||
///
|
||||
/// This gets triggered in two scenarios:
|
||||
/// - The OS has performed an operation that's invalidated the window's contents (such as
|
||||
/// resizing the window).
|
||||
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
|
||||
///
|
||||
/// Winit will aggregate duplicate redraw requests into a single event, to
|
||||
/// help avoid duplicating rendering work.
|
||||
RedrawRequested,
|
||||
}
|
||||
|
||||
/// Identifier of an input device.
|
||||
@@ -575,7 +612,8 @@ impl DeviceId {
|
||||
///
|
||||
/// **Passing this into a winit function will result in undefined behavior.**
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
DeviceId(platform_impl::DeviceId::dummy())
|
||||
#[allow(unused_unsafe)]
|
||||
DeviceId(unsafe { platform_impl::DeviceId::dummy() })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,10 +659,6 @@ pub enum DeviceEvent {
|
||||
},
|
||||
|
||||
Key(RawKeyEvent),
|
||||
|
||||
Text {
|
||||
codepoint: char,
|
||||
},
|
||||
}
|
||||
|
||||
/// Describes a keyboard input as a raw device event.
|
||||
@@ -637,7 +671,7 @@ pub enum DeviceEvent {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct RawKeyEvent {
|
||||
pub physical_key: keyboard::KeyCode,
|
||||
pub physical_key: keyboard::PhysicalKey,
|
||||
pub state: ElementState,
|
||||
}
|
||||
|
||||
@@ -669,12 +703,12 @@ pub struct KeyEvent {
|
||||
/// `Fn` and `FnLock` key events are *exceedingly unlikely* to be emitted by Winit. These keys
|
||||
/// are usually handled at the hardware or OS level, and aren't surfaced to applications. If
|
||||
/// you somehow see this in the wild, we'd like to know :)
|
||||
pub physical_key: keyboard::KeyCode,
|
||||
pub physical_key: keyboard::PhysicalKey,
|
||||
|
||||
// Allowing `broken_intra_doc_links` for `logical_key`, because
|
||||
// `key_without_modifiers` is not available on all platforms
|
||||
#[cfg_attr(
|
||||
not(any(target_os = "macos", target_os = "windows", target_os = "linux")),
|
||||
not(any(windows_platform, macos_platform, x11_platform, wayland_platform)),
|
||||
allow(rustdoc::broken_intra_doc_links)
|
||||
)]
|
||||
/// This value is affected by all modifiers except <kbd>Ctrl</kbd>.
|
||||
@@ -709,7 +743,7 @@ pub struct KeyEvent {
|
||||
/// An additional difference from `logical_key` is that
|
||||
/// this field stores the text representation of any key
|
||||
/// that has such a representation. For example when
|
||||
/// `logical_key` is `Key::Enter`, this field is `Some("\r")`.
|
||||
/// `logical_key` is `Key::Named(NamedKey::Enter)`, this field is `Some("\r")`.
|
||||
///
|
||||
/// This is `None` if the current keypress cannot
|
||||
/// be interpreted as text.
|
||||
@@ -942,7 +976,10 @@ pub struct Touch {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - Only available on **iOS** 9.0+, **Windows** 8+, and **Web**.
|
||||
/// - Only available on **iOS** 9.0+, **Windows** 8+, **Web**, and **Android**.
|
||||
/// - **Android**: This will never be [None]. If the device doesn't support pressure
|
||||
/// sensitivity, force will either be 0.0 or 1.0. Also see the
|
||||
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
|
||||
pub force: Option<Force>,
|
||||
/// Unique identifier of a finger.
|
||||
pub id: u64,
|
||||
@@ -1032,7 +1069,7 @@ impl ElementState {
|
||||
///
|
||||
/// **macOS:** `Back` and `Forward` might not work with all hardware.
|
||||
/// **Orbital:** `Back` and `Forward` are unsupported due to orbital not supporting them.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
@@ -1123,7 +1160,6 @@ mod tests {
|
||||
let wid = unsafe { WindowId::dummy() };
|
||||
x(UserEvent(()));
|
||||
x(NewEvents(event::StartCause::Init));
|
||||
x(RedrawRequested(wid));
|
||||
x(AboutToWait);
|
||||
x(LoopExiting);
|
||||
x(Suspended);
|
||||
@@ -1222,7 +1258,6 @@ mod tests {
|
||||
button: 0,
|
||||
state: event::ElementState::Pressed,
|
||||
});
|
||||
with_device_event(Text { codepoint: 'a' });
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
//! handle events.
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::{error, fmt};
|
||||
|
||||
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
|
||||
#[cfg(not(wasm_platform))]
|
||||
use std::time::{Duration, Instant};
|
||||
#[cfg(wasm_platform)]
|
||||
@@ -147,28 +148,21 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set by the user callback given to the [`EventLoop::run`] method.
|
||||
/// Set through [`EventLoopWindowTarget::set_control_flow()`].
|
||||
///
|
||||
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
|
||||
///
|
||||
/// Defaults to [`Poll`].
|
||||
/// Defaults to [`Wait`].
|
||||
///
|
||||
/// ## Persistency
|
||||
///
|
||||
/// Almost every change is persistent between multiple calls to the event loop closure within a
|
||||
/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset.
|
||||
/// Changes are **not** persistent between multiple calls to `run_ondemand` - issuing a new call will
|
||||
/// reset the control flow to [`Poll`].
|
||||
///
|
||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
||||
/// [`Poll`]: Self::Poll
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// [`Wait`]: Self::Wait
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum ControlFlow {
|
||||
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
||||
/// whether or not new events are available to process.
|
||||
Poll,
|
||||
|
||||
/// When the current loop iteration finishes, suspend the thread until another event arrives.
|
||||
#[default]
|
||||
Wait,
|
||||
|
||||
/// When the current loop iteration finishes, suspend the thread until either another event
|
||||
@@ -180,88 +174,22 @@ pub enum ControlFlow {
|
||||
///
|
||||
/// [`Poll`]: Self::Poll
|
||||
WaitUntil(Instant),
|
||||
|
||||
/// Send a [`LoopExiting`] event and stop the event loop. This variant is *sticky* - once set,
|
||||
/// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
|
||||
/// result in the `control_flow` parameter being reset to `ExitWithCode`.
|
||||
///
|
||||
/// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this
|
||||
/// with exit code 0.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS / Web:** The supplied exit code is unused.
|
||||
/// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used,
|
||||
/// which can cause surprises with negative exit values (`-42` would end up as `214`). See
|
||||
/// [`std::process::exit`].
|
||||
///
|
||||
/// [`LoopExiting`]: Event::LoopExiting
|
||||
/// [`Exit`]: ControlFlow::Exit
|
||||
ExitWithCode(i32),
|
||||
}
|
||||
|
||||
impl ControlFlow {
|
||||
/// Alias for [`ExitWithCode`]`(0)`.
|
||||
///
|
||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const Exit: Self = Self::ExitWithCode(0);
|
||||
|
||||
/// Sets this to [`Poll`].
|
||||
///
|
||||
/// [`Poll`]: Self::Poll
|
||||
pub fn set_poll(&mut self) {
|
||||
*self = Self::Poll;
|
||||
}
|
||||
|
||||
/// Sets this to [`Wait`].
|
||||
///
|
||||
/// [`Wait`]: Self::Wait
|
||||
pub fn set_wait(&mut self) {
|
||||
*self = Self::Wait;
|
||||
}
|
||||
|
||||
/// Sets this to [`WaitUntil`]`(instant)`.
|
||||
///
|
||||
/// [`WaitUntil`]: Self::WaitUntil
|
||||
pub fn set_wait_until(&mut self, instant: Instant) {
|
||||
*self = Self::WaitUntil(instant);
|
||||
}
|
||||
|
||||
/// Sets this to wait until a timeout has expired.
|
||||
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
|
||||
///
|
||||
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
|
||||
/// instead set to [`Wait`].
|
||||
///
|
||||
/// [`WaitUntil`]: Self::WaitUntil
|
||||
/// [`Wait`]: Self::Wait
|
||||
pub fn set_wait_timeout(&mut self, timeout: Duration) {
|
||||
pub fn wait_duration(timeout: Duration) -> Self {
|
||||
match Instant::now().checked_add(timeout) {
|
||||
Some(instant) => self.set_wait_until(instant),
|
||||
None => self.set_wait(),
|
||||
Some(instant) => Self::WaitUntil(instant),
|
||||
None => Self::Wait,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets this to [`ExitWithCode`]`(code)`.
|
||||
///
|
||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
||||
pub fn set_exit_with_code(&mut self, code: i32) {
|
||||
*self = Self::ExitWithCode(code);
|
||||
}
|
||||
|
||||
/// Sets this to [`Exit`].
|
||||
///
|
||||
/// [`Exit`]: Self::Exit
|
||||
pub fn set_exit(&mut self) {
|
||||
*self = Self::Exit;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ControlFlow {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self::Poll
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop<()> {
|
||||
@@ -286,32 +214,37 @@ impl<T> EventLoop<T> {
|
||||
/// Since the closure is `'static`, it must be a `move` closure if it needs to
|
||||
/// access any data from the calling context.
|
||||
///
|
||||
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
|
||||
/// event loop's behavior.
|
||||
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11 / Wayland:** The program terminates with exit code 1 if the display server
|
||||
/// disconnects.
|
||||
/// - **iOS:** Will never return to the caller and so values not passed to this function will
|
||||
/// *not* be dropped before the process exits.
|
||||
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript exception
|
||||
/// (that Rust doesn't see) that will also mean that the rest of the function is never executed
|
||||
/// and any values not passed to this function will *not* be dropped.
|
||||
///
|
||||
/// Web applications are recommended to use `spawn()` instead of `run()` to avoid the need
|
||||
/// Web applications are recommended to use
|
||||
#[cfg_attr(
|
||||
wasm_platform,
|
||||
doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]"
|
||||
)]
|
||||
#[cfg_attr(not(wasm_platform), doc = "`EventLoopExtWebSys::spawn()`")]
|
||||
/// [^1] instead of [`run()`] to avoid the need
|
||||
/// for the Javascript exception trick, and to make it clearer that the event loop runs
|
||||
/// asynchronously (via the browser's own, internal, event loop) and doesn't block the
|
||||
/// current thread of execution like it does on other platforms.
|
||||
///
|
||||
/// This function won't be available with `target_feature = "exception-handling"`.
|
||||
///
|
||||
/// [`ControlFlow`]: crate::event_loop::ControlFlow
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
|
||||
/// [`run()`]: Self::run()
|
||||
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on WASM.
|
||||
#[inline]
|
||||
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
|
||||
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(Event<T>, &EventLoopWindowTarget<T>),
|
||||
{
|
||||
self.event_loop.run(event_handler)
|
||||
}
|
||||
@@ -324,10 +257,46 @@ impl<T> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> HasRawDisplayHandle for EventLoop<T> {
|
||||
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
|
||||
fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
self.event_loop.window_target().p.raw_display_handle()
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
rwh_06::HasDisplayHandle::display_handle(&**self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
|
||||
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
|
||||
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
|
||||
rwh_05::HasRawDisplayHandle::raw_display_handle(&**self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
impl<T> AsFd for EventLoop<T> {
|
||||
/// Get the underlying [EventLoop]'s `fd` which you can register
|
||||
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
|
||||
/// loop must be polled with the [`pump_events`] API.
|
||||
///
|
||||
/// [`calloop`]: https://crates.io/crates/calloop
|
||||
/// [`mio`]: https://crates.io/crates/mio
|
||||
/// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.event_loop.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
impl<T> AsRawFd for EventLoop<T> {
|
||||
/// Get the underlying [EventLoop]'s raw `fd` which you can register
|
||||
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
|
||||
/// loop must be polled with the [`pump_events`] API.
|
||||
///
|
||||
/// [`calloop`]: https://crates.io/crates/calloop
|
||||
/// [`mio`]: https://crates.io/crates/mio
|
||||
/// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.event_loop.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +324,7 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **Wayland:** Always returns `None`.
|
||||
/// **Wayland / Web:** Always returns `None`.
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
self.p
|
||||
@@ -374,16 +343,49 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
|
||||
///
|
||||
/// [`DeviceEvent`]: crate::event::DeviceEvent
|
||||
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
|
||||
#[cfg(any(x11_platform, wasm_platform, wayland_platform, windows))]
|
||||
self.p.listen_device_events(_allowed);
|
||||
pub fn listen_device_events(&self, allowed: DeviceEvents) {
|
||||
self.p.listen_device_events(allowed);
|
||||
}
|
||||
|
||||
/// Sets the [`ControlFlow`].
|
||||
pub fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||
self.p.set_control_flow(control_flow)
|
||||
}
|
||||
|
||||
/// Gets the current [`ControlFlow`].
|
||||
pub fn control_flow(&self) -> ControlFlow {
|
||||
self.p.control_flow()
|
||||
}
|
||||
|
||||
/// This exits the event loop.
|
||||
///
|
||||
/// See [`LoopExiting`](Event::LoopExiting).
|
||||
pub fn exit(&self) {
|
||||
self.p.exit()
|
||||
}
|
||||
|
||||
/// Returns if the [`EventLoop`] is about to stop.
|
||||
///
|
||||
/// See [`exit()`](Self::exit).
|
||||
pub fn exiting(&self) -> bool {
|
||||
self.p.exiting()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> HasRawDisplayHandle for EventLoopWindowTarget<T> {
|
||||
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
|
||||
fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
self.p.raw_display_handle()
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl<T> rwh_06::HasDisplayHandle for EventLoopWindowTarget<T> {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = self.p.raw_display_handle_rwh_06()?;
|
||||
// SAFETY: The display will never be deallocated while the event loop is alive.
|
||||
Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoopWindowTarget<T> {
|
||||
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
|
||||
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
|
||||
self.p.raw_display_handle_rwh_05()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,16 +459,16 @@ pub enum DeviceEvents {
|
||||
/// executed and removed from the list.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AsyncRequestSerial {
|
||||
serial: u64,
|
||||
serial: usize,
|
||||
}
|
||||
|
||||
impl AsyncRequestSerial {
|
||||
// TODO(kchibisov) remove `cfg` when the clipboard will be added.
|
||||
// TODO(kchibisov): Remove `cfg` when the clipboard will be added.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn get() -> Self {
|
||||
static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0);
|
||||
// NOTE: we rely on wrap around here, while the user may just request
|
||||
// in the loop u64::MAX times that's issue is considered on them.
|
||||
static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0);
|
||||
// NOTE: We rely on wrap around here, while the user may just request
|
||||
// in the loop usize::MAX times that's issue is considered on them.
|
||||
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
|
||||
Self { serial }
|
||||
}
|
||||
|
||||
@@ -49,11 +49,7 @@ impl fmt::Display for BadIcon {
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadIcon {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl Error for BadIcon {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct RgbaIcon {
|
||||
|
||||
332
src/keyboard.rs
332
src/keyboard.rs
@@ -82,7 +82,7 @@ pub use smol_str::SmolStr;
|
||||
///
|
||||
/// - Correctly match key press and release events.
|
||||
/// - On non-web platforms, support assigning keybinds to virtually any key through a UI.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum NativeKeyCode {
|
||||
Unidentified,
|
||||
@@ -185,26 +185,112 @@ impl std::fmt::Debug for NativeKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NativeKeyCode> for NativeKey {
|
||||
#[inline]
|
||||
fn from(code: NativeKeyCode) -> Self {
|
||||
match code {
|
||||
NativeKeyCode::Unidentified => NativeKey::Unidentified,
|
||||
NativeKeyCode::Android(x) => NativeKey::Android(x),
|
||||
NativeKeyCode::MacOS(x) => NativeKey::MacOS(x),
|
||||
NativeKeyCode::Windows(x) => NativeKey::Windows(x),
|
||||
NativeKeyCode::Xkb(x) => NativeKey::Xkb(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<NativeKey> for NativeKeyCode {
|
||||
#[allow(clippy::cmp_owned)] // uses less code than direct match; target is stack allocated
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &NativeKey) -> bool {
|
||||
NativeKey::from(*self) == *rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<NativeKeyCode> for NativeKey {
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &NativeKeyCode) -> bool {
|
||||
rhs == self
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the location of a physical key.
|
||||
///
|
||||
/// This type is a superset of [`KeyCode`], including an [`Unidentified`](Self::Unidentified)
|
||||
/// variant.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum PhysicalKey {
|
||||
/// A known key code
|
||||
Code(KeyCode),
|
||||
/// This variant is used when the key cannot be translated to a [`KeyCode`]
|
||||
///
|
||||
/// The native keycode is provided (if available) so you're able to more reliably match
|
||||
/// key-press and key-release events by hashing the [`PhysicalKey`]. It is also possible to use
|
||||
/// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
|
||||
Unidentified(NativeKeyCode),
|
||||
}
|
||||
|
||||
impl From<KeyCode> for PhysicalKey {
|
||||
#[inline]
|
||||
fn from(code: KeyCode) -> Self {
|
||||
PhysicalKey::Code(code)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NativeKeyCode> for PhysicalKey {
|
||||
#[inline]
|
||||
fn from(code: NativeKeyCode) -> Self {
|
||||
PhysicalKey::Unidentified(code)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<KeyCode> for PhysicalKey {
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &KeyCode) -> bool {
|
||||
match self {
|
||||
PhysicalKey::Code(ref code) => code == rhs,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<PhysicalKey> for KeyCode {
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &PhysicalKey) -> bool {
|
||||
rhs == self
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<NativeKeyCode> for PhysicalKey {
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &NativeKeyCode) -> bool {
|
||||
match self {
|
||||
PhysicalKey::Unidentified(ref code) => code == rhs,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<PhysicalKey> for NativeKeyCode {
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &PhysicalKey) -> bool {
|
||||
rhs == self
|
||||
}
|
||||
}
|
||||
|
||||
/// Code representing the location of a physical key
|
||||
///
|
||||
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few
|
||||
/// exceptions:
|
||||
/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and
|
||||
/// "SuperRight" here.
|
||||
/// - The key that the specification calls "Super" is reported as `Unidentified` here.
|
||||
/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`.
|
||||
///
|
||||
/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum KeyCode {
|
||||
/// This variant is used when the key cannot be translated to any other variant.
|
||||
///
|
||||
/// The native keycode is provided (if available) so you're able to more reliably match
|
||||
/// key-press and key-release events by hashing the [`KeyCode`]. It is also possible to use
|
||||
/// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
|
||||
Unidentified(NativeKeyCode),
|
||||
/// <kbd>`</kbd> on a US keyboard. This is also called a backtick or grave.
|
||||
/// This is the <kbd>半角</kbd>/<kbd>全角</kbd>/<kbd>漢字</kbd>
|
||||
/// (hankaku/zenkaku/kanji) key on Japanese keyboards
|
||||
@@ -648,7 +734,7 @@ pub enum KeyCode {
|
||||
F35,
|
||||
}
|
||||
|
||||
/// Key represents the meaning of a keypress.
|
||||
/// A [`Key::Named`] value
|
||||
///
|
||||
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few
|
||||
/// exceptions:
|
||||
@@ -656,32 +742,12 @@ pub enum KeyCode {
|
||||
/// another key which the specification calls `Super`. That does not exist here.)
|
||||
/// - The `Space` variant here, can be identified by the character it generates in the
|
||||
/// specificaiton.
|
||||
/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`.
|
||||
/// - The `Dead` variant here, can specify the character which is inserted when pressing the
|
||||
/// dead-key twice.
|
||||
///
|
||||
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Key<Str = SmolStr> {
|
||||
/// A key string that corresponds to the character typed by the user, taking into account the
|
||||
/// user’s current locale setting, and any system-level keyboard mapping overrides that are in
|
||||
/// effect.
|
||||
Character(Str),
|
||||
|
||||
/// This variant is used when the key cannot be translated to any other variant.
|
||||
///
|
||||
/// The native key is provided (if available) in order to allow the user to specify keybindings
|
||||
/// for keys which are not defined by this API, mainly through some sort of UI.
|
||||
Unidentified(NativeKey),
|
||||
|
||||
/// Contains the text representation of the dead-key when available.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
/// - **Web:** Always contains `None`
|
||||
Dead(Option<char>),
|
||||
|
||||
pub enum NamedKey {
|
||||
/// The `Alt` (Alternative) key.
|
||||
///
|
||||
/// This key enables the alternate modifier function for interpreting concurrent or subsequent
|
||||
@@ -1385,83 +1451,131 @@ pub enum Key<Str = SmolStr> {
|
||||
F35,
|
||||
}
|
||||
|
||||
macro_rules! map_match {
|
||||
(
|
||||
$to_match:expr,
|
||||
// Custom match arms
|
||||
{ $( $from:pat => $to:expr ),* },
|
||||
// The enum's name
|
||||
$prefix:path,
|
||||
// Trivial match arms for unit variants
|
||||
{ $( $t:tt ),* }) => {
|
||||
match $to_match {
|
||||
$( $from => $to, )*
|
||||
$( Key::$t => Key::$t, )*
|
||||
/// Key represents the meaning of a keypress.
|
||||
///
|
||||
/// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
|
||||
/// additions:
|
||||
/// - All simple variants are wrapped under the `Named` variant
|
||||
/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`.
|
||||
/// - The `Dead` variant here, can specify the character which is inserted when pressing the
|
||||
/// dead-key twice.
|
||||
///
|
||||
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Key<Str = SmolStr> {
|
||||
/// A simple (unparameterised) action
|
||||
Named(NamedKey),
|
||||
|
||||
/// A key string that corresponds to the character typed by the user, taking into account the
|
||||
/// user’s current locale setting, and any system-level keyboard mapping overrides that are in
|
||||
/// effect.
|
||||
Character(Str),
|
||||
|
||||
/// This variant is used when the key cannot be translated to any other variant.
|
||||
///
|
||||
/// The native key is provided (if available) in order to allow the user to specify keybindings
|
||||
/// for keys which are not defined by this API, mainly through some sort of UI.
|
||||
Unidentified(NativeKey),
|
||||
|
||||
/// Contains the text representation of the dead-key when available.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
/// - **Web:** Always contains `None`
|
||||
Dead(Option<char>),
|
||||
}
|
||||
|
||||
impl From<NamedKey> for Key {
|
||||
#[inline]
|
||||
fn from(action: NamedKey) -> Self {
|
||||
Key::Named(action)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NativeKey> for Key {
|
||||
#[inline]
|
||||
fn from(code: NativeKey) -> Self {
|
||||
Key::Unidentified(code)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Str> PartialEq<NamedKey> for Key<Str> {
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &NamedKey) -> bool {
|
||||
match self {
|
||||
Key::Named(ref a) => a == rhs,
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl<Str: PartialEq<str>> PartialEq<str> for Key<Str> {
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &str) -> bool {
|
||||
match self {
|
||||
Key::Character(ref s) => s == rhs,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Str: PartialEq<str>> PartialEq<&str> for Key<Str> {
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &&str) -> bool {
|
||||
self == *rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl<Str> PartialEq<NativeKey> for Key<Str> {
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &NativeKey) -> bool {
|
||||
match self {
|
||||
Key::Unidentified(ref code) => code == rhs,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Str> PartialEq<Key<Str>> for NativeKey {
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &Key<Str>) -> bool {
|
||||
rhs == self
|
||||
}
|
||||
}
|
||||
|
||||
impl Key<SmolStr> {
|
||||
/// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on
|
||||
/// `Key`. All other variants remain unchanged.
|
||||
pub fn as_ref(&self) -> Key<&str> {
|
||||
map_match!(
|
||||
self,
|
||||
{
|
||||
Key::Character(ch) => Key::Character(ch.as_str()),
|
||||
Key::Dead(d) => Key::Dead(*d),
|
||||
Key::Unidentified(u) => Key::Unidentified(u.clone())
|
||||
},
|
||||
Key,
|
||||
{
|
||||
Alt, AltGraph, CapsLock, Control, Fn, FnLock, NumLock, ScrollLock, Shift, Symbol,
|
||||
SymbolLock, Meta, Hyper, Super, Enter, Tab, Space, ArrowDown, ArrowLeft,
|
||||
ArrowRight, ArrowUp, End, Home, PageDown, PageUp, Backspace, Clear, Copy, CrSel,
|
||||
Cut, Delete, EraseEof, ExSel, Insert, Paste, Redo, Undo, Accept, Again, Attn,
|
||||
Cancel, ContextMenu, Escape, Execute, Find, Help, Pause, Play, Props, Select,
|
||||
ZoomIn, ZoomOut, BrightnessDown, BrightnessUp, Eject, LogOff, Power, PowerOff,
|
||||
PrintScreen, Hibernate, Standby, WakeUp, AllCandidates, Alphanumeric, CodeInput,
|
||||
Compose, Convert, FinalMode, GroupFirst, GroupLast, GroupNext, GroupPrevious,
|
||||
ModeChange, NextCandidate, NonConvert, PreviousCandidate, Process, SingleCandidate,
|
||||
HangulMode, HanjaMode, JunjaMode, Eisu, Hankaku, Hiragana, HiraganaKatakana,
|
||||
KanaMode, KanjiMode, Katakana, Romaji, Zenkaku, ZenkakuHankaku, Soft1, Soft2,
|
||||
Soft3, Soft4, ChannelDown, ChannelUp, Close, MailForward, MailReply, MailSend,
|
||||
MediaClose, MediaFastForward, MediaPause, MediaPlay, MediaPlayPause, MediaRecord,
|
||||
MediaRewind, MediaStop, MediaTrackNext, MediaTrackPrevious, New, Open, Print, Save,
|
||||
SpellCheck, Key11, Key12, AudioBalanceLeft, AudioBalanceRight, AudioBassBoostDown,
|
||||
AudioBassBoostToggle, AudioBassBoostUp, AudioFaderFront, AudioFaderRear,
|
||||
AudioSurroundModeNext, AudioTrebleDown, AudioTrebleUp, AudioVolumeDown,
|
||||
AudioVolumeUp, AudioVolumeMute, MicrophoneToggle, MicrophoneVolumeDown,
|
||||
MicrophoneVolumeUp, MicrophoneVolumeMute, SpeechCorrectionList, SpeechInputToggle,
|
||||
LaunchApplication1, LaunchApplication2, LaunchCalendar, LaunchContacts, LaunchMail,
|
||||
LaunchMediaPlayer, LaunchMusicPlayer, LaunchPhone, LaunchScreenSaver,
|
||||
LaunchSpreadsheet, LaunchWebBrowser, LaunchWebCam, LaunchWordProcessor,
|
||||
BrowserBack, BrowserFavorites, BrowserForward, BrowserHome, BrowserRefresh,
|
||||
BrowserSearch, BrowserStop, AppSwitch, Call, Camera, CameraFocus, EndCall, GoBack,
|
||||
GoHome, HeadsetHook, LastNumberRedial, Notification, MannerMode, VoiceDial, TV,
|
||||
TV3DMode, TVAntennaCable, TVAudioDescription, TVAudioDescriptionMixDown,
|
||||
TVAudioDescriptionMixUp, TVContentsMenu, TVDataService, TVInput, TVInputComponent1,
|
||||
TVInputComponent2, TVInputComposite1, TVInputComposite2, TVInputHDMI1,
|
||||
TVInputHDMI2, TVInputHDMI3, TVInputHDMI4, TVInputVGA1, TVMediaContext, TVNetwork,
|
||||
TVNumberEntry, TVPower, TVRadioService, TVSatellite, TVSatelliteBS, TVSatelliteCS,
|
||||
TVSatelliteToggle, TVTerrestrialAnalog, TVTerrestrialDigital, TVTimer, AVRInput,
|
||||
AVRPower, ColorF0Red, ColorF1Green, ColorF2Yellow, ColorF3Blue, ColorF4Grey,
|
||||
ColorF5Brown, ClosedCaptionToggle, Dimmer, DisplaySwap, DVR, Exit, FavoriteClear0,
|
||||
FavoriteClear1, FavoriteClear2, FavoriteClear3, FavoriteRecall0, FavoriteRecall1,
|
||||
FavoriteRecall2, FavoriteRecall3, FavoriteStore0, FavoriteStore1, FavoriteStore2,
|
||||
FavoriteStore3, Guide, GuideNextDay, GuidePreviousDay, Info, InstantReplay, Link,
|
||||
ListProgram, LiveContent, Lock, MediaApps, MediaAudioTrack, MediaLast,
|
||||
MediaSkipBackward, MediaSkipForward, MediaStepBackward, MediaStepForward,
|
||||
MediaTopMenu, NavigateIn, NavigateNext, NavigateOut, NavigatePrevious,
|
||||
NextFavoriteChannel, NextUserProfile, OnDemand, Pairing, PinPDown, PinPMove,
|
||||
PinPToggle, PinPUp, PlaySpeedDown, PlaySpeedReset, PlaySpeedUp, RandomToggle,
|
||||
RcLowBattery, RecordSpeedNext, RfBypass, ScanChannelsToggle, ScreenModeNext,
|
||||
Settings, SplitScreenToggle, STBInput, STBPower, Subtitle, Teletext, VideoModeNext,
|
||||
Wink, ZoomToggle, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15,
|
||||
F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31,
|
||||
F32, F33, F34, F35
|
||||
}
|
||||
)
|
||||
match self {
|
||||
Key::Named(a) => Key::Named(*a),
|
||||
Key::Character(ch) => Key::Character(ch.as_str()),
|
||||
Key::Dead(d) => Key::Dead(*d),
|
||||
Key::Unidentified(u) => Key::Unidentified(u.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NamedKey {
|
||||
/// Convert an action to its approximate textual equivalent.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use winit::keyboard::NamedKey;
|
||||
///
|
||||
/// assert_eq!(NamedKey::Enter.to_text(), Some("\r"));
|
||||
/// assert_eq!(NamedKey::F20.to_text(), None);
|
||||
/// ```
|
||||
pub fn to_text(&self) -> Option<&str> {
|
||||
match self {
|
||||
NamedKey::Enter => Some("\r"),
|
||||
NamedKey::Backspace => Some("\x08"),
|
||||
NamedKey::Tab => Some("\t"),
|
||||
NamedKey::Space => Some(" "),
|
||||
NamedKey::Escape => Some("\x1b"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1471,20 +1585,16 @@ impl Key {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use winit::keyboard::Key;
|
||||
/// use winit::keyboard::{NamedKey, Key};
|
||||
///
|
||||
/// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
|
||||
/// assert_eq!(Key::Enter.to_text(), Some("\r"));
|
||||
/// assert_eq!(Key::F20.to_text(), None);
|
||||
/// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
|
||||
/// assert_eq!(Key::Named(NamedKey::F20).to_text(), None);
|
||||
/// ```
|
||||
pub fn to_text(&self) -> Option<&str> {
|
||||
match self {
|
||||
Key::Named(action) => action.to_text(),
|
||||
Key::Character(ch) => Some(ch.as_str()),
|
||||
Key::Enter => Some("\r"),
|
||||
Key::Backspace => Some("\x08"),
|
||||
Key::Tab => Some("\t"),
|
||||
Key::Space => Some(" "),
|
||||
Key::Escape => Some("\x1b"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1572,7 +1682,7 @@ bitflags! {
|
||||
/// Represents the current state of the keyboard modifiers
|
||||
///
|
||||
/// Each flag represents a modifier and is set if this modifier is active.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ModifiersState: u32 {
|
||||
/// The "shift" key.
|
||||
const SHIFT = 0b100;
|
||||
|
||||
108
src/lib.rs
108
src/lib.rs
@@ -10,12 +10,12 @@
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Once this is done there are two ways to create a [`Window`]:
|
||||
//! Once this is done, there are two ways to create a [`Window`]:
|
||||
//!
|
||||
//! - Calling [`Window::new(&event_loop)`][window_new].
|
||||
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
|
||||
//!
|
||||
//! The first method is the simplest, and will give you default values for everything. The second
|
||||
//! The first method is the simplest and will give you default values for everything. The second
|
||||
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
|
||||
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
|
||||
//!
|
||||
@@ -26,60 +26,81 @@
|
||||
//! window or a key getting pressed while the window is focused. Devices can generate
|
||||
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
|
||||
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
|
||||
//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired.
|
||||
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if desired.
|
||||
//!
|
||||
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
|
||||
//! You can retrieve events by calling [`EventLoop::run()`]. This function will
|
||||
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
|
||||
//! will run until the `control_flow` argument given to the closure is set to
|
||||
//! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which
|
||||
//! point [`Event`]`::`[`LoopExiting`] is emitted and the entire program terminates.
|
||||
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
|
||||
//!
|
||||
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
||||
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
|
||||
//! most other platforms. However, this model can be re-implemented to an extent with
|
||||
//! [`EventLoopExtPumpEvents::pump_events`]. See that method's documentation for more reasons about why
|
||||
//! it's discouraged, beyond compatibility reasons.
|
||||
#![cfg_attr(
|
||||
any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform
|
||||
),
|
||||
doc = "[`EventLoopExtPumpEvents::pump_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_events()]"
|
||||
)]
|
||||
#![cfg_attr(
|
||||
not(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform
|
||||
)),
|
||||
doc = "`EventLoopExtPumpEvents::pump_events()`"
|
||||
)]
|
||||
//! [^1]. See that method's documentation for more reasons about why
|
||||
//! it's discouraged beyond compatibility reasons.
|
||||
//!
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use winit::{
|
||||
//! event::{Event, WindowEvent},
|
||||
//! event_loop::EventLoop,
|
||||
//! event_loop::{ControlFlow, EventLoop},
|
||||
//! window::WindowBuilder,
|
||||
//! };
|
||||
//!
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
//!
|
||||
//! event_loop.run(move |event, _, control_flow| {
|
||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
||||
//! // dispatched any events. This is ideal for games and similar applications.
|
||||
//! control_flow.set_poll();
|
||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
||||
//! // dispatched any events. This is ideal for games and similar applications.
|
||||
//! event_loop.set_control_flow(ControlFlow::Poll);
|
||||
//!
|
||||
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
|
||||
//! // This is ideal for non-game applications that only update in response to user
|
||||
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
||||
//! control_flow.set_wait();
|
||||
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
|
||||
//! // This is ideal for non-game applications that only update in response to user
|
||||
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
||||
//! event_loop.set_control_flow(ControlFlow::Wait);
|
||||
//!
|
||||
//! event_loop.run(move |event, elwt| {
|
||||
//! match event {
|
||||
//! Event::WindowEvent {
|
||||
//! event: WindowEvent::CloseRequested,
|
||||
//! ..
|
||||
//! } => {
|
||||
//! println!("The close button was pressed; stopping");
|
||||
//! control_flow.set_exit();
|
||||
//! elwt.exit();
|
||||
//! },
|
||||
//! Event::AboutToWait => {
|
||||
//! // Application update code.
|
||||
//!
|
||||
//! // Queue a RedrawRequested event.
|
||||
//! //
|
||||
//! // You only need to call this if you've determined that you need to redraw, in
|
||||
//! // You only need to call this if you've determined that you need to redraw in
|
||||
//! // applications which do not always need to. Applications that redraw continuously
|
||||
//! // can just render here instead.
|
||||
//! // can render here instead.
|
||||
//! window.request_redraw();
|
||||
//! },
|
||||
//! Event::RedrawRequested(_) => {
|
||||
//! Event::WindowEvent {
|
||||
//! event: WindowEvent::RedrawRequested,
|
||||
//! ..
|
||||
//! } => {
|
||||
//! // Redraw the application.
|
||||
//! //
|
||||
//! // It's preferable for applications that do not render continuously to render in
|
||||
@@ -91,16 +112,16 @@
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
|
||||
//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`]
|
||||
//! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
|
||||
//! compared to the value returned by [`Window::id()`] to determine which [`Window`]
|
||||
//! dispatched the event.
|
||||
//!
|
||||
//! # Drawing on the window
|
||||
//!
|
||||
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
|
||||
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you to
|
||||
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the
|
||||
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
|
||||
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
||||
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
||||
//!
|
||||
//! Note that many platforms will display garbage data in the window's client area if the
|
||||
//! application doesn't render anything to the window by the time the desktop compositor is ready to
|
||||
@@ -109,36 +130,36 @@
|
||||
//! window visible only once you're ready to render into it.
|
||||
//!
|
||||
//! [`EventLoop`]: event_loop::EventLoop
|
||||
//! [`EventLoopExtPumpEvents::pump_events`]: ./platform/pump_events/trait.EventLoopExtPumpEvents.html#tymethod.pump_events
|
||||
//! [`EventLoop::new()`]: event_loop::EventLoop::new
|
||||
//! [event_loop_run]: event_loop::EventLoop::run
|
||||
//! [`ControlFlow`]: event_loop::ControlFlow
|
||||
//! [`Exit`]: event_loop::ControlFlow::Exit
|
||||
//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode
|
||||
//! [`EventLoop::run()`]: event_loop::EventLoop::run
|
||||
//! [`exit()`]: event_loop::EventLoopWindowTarget::exit
|
||||
//! [`Window`]: window::Window
|
||||
//! [`WindowId`]: window::WindowId
|
||||
//! [`WindowBuilder`]: window::WindowBuilder
|
||||
//! [window_new]: window::Window::new
|
||||
//! [window_builder_new]: window::WindowBuilder::new
|
||||
//! [window_builder_build]: window::WindowBuilder::build
|
||||
//! [window_id_fn]: window::Window::id
|
||||
//! [`Event`]: event::Event
|
||||
//! [`Window::id()`]: window::Window::id
|
||||
//! [`WindowEvent`]: event::WindowEvent
|
||||
//! [`DeviceEvent`]: event::DeviceEvent
|
||||
//! [`UserEvent`]: event::Event::UserEvent
|
||||
//! [`LoopExiting`]: event::Event::LoopExiting
|
||||
//! [`platform`]: platform
|
||||
//! [`Event::UserEvent`]: event::Event::UserEvent
|
||||
//! [`Event::LoopExiting`]: event::Event::LoopExiting
|
||||
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
|
||||
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
|
||||
//! [^1]: `EventLoopExtPumpEvents::pump_events()` is only available on Windows, macOS, Android, X11 and Wayland.
|
||||
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
#![deny(clippy::all)]
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
|
||||
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub use rwh_06 as raw_window_handle;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
@@ -160,3 +181,18 @@ mod platform_impl;
|
||||
pub mod window;
|
||||
|
||||
pub mod platform;
|
||||
|
||||
/// Wrapper for objects which winit will access on the main thread so they are effectively `Send`
|
||||
/// and `Sync`, since they always execute on a single thread.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Winit can run only one event loop at a time, and the event loop itself is tied to some thread.
|
||||
/// The objects could be sent across the threads, but once passed to winit, they execute on the
|
||||
/// main thread if the platform demands it. Thus, marking such objects as `Send + Sync` is safe.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct SendSyncWrapper<T>(pub(crate) T);
|
||||
|
||||
unsafe impl<T> Send for SendSyncWrapper<T> {}
|
||||
unsafe impl<T> Sync for SendSyncWrapper<T> {}
|
||||
|
||||
@@ -108,20 +108,12 @@ impl MonitorHandle {
|
||||
/// Returns a human-readable name of the monitor.
|
||||
///
|
||||
/// Returns `None` if the monitor doesn't exist anymore.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Always returns None
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
self.inner.name()
|
||||
}
|
||||
|
||||
/// Returns the monitor's resolution.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Always returns (0,0)
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.inner.size()
|
||||
@@ -129,10 +121,6 @@ impl MonitorHandle {
|
||||
|
||||
/// Returns the top-left corner position of the monitor relative to the larger full
|
||||
/// screen area.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Always returns (0,0)
|
||||
#[inline]
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
self.inner.position()
|
||||
@@ -150,15 +138,18 @@ impl MonitorHandle {
|
||||
self.inner.refresh_rate_millihertz()
|
||||
}
|
||||
|
||||
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
|
||||
/// pixels and vice versa, use [`Window::scale_factor`].
|
||||
///
|
||||
/// See the [`dpi`](crate::dpi) module for more information.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
|
||||
/// - **Wayland:** May differ from [`Window::scale_factor`].
|
||||
/// - **Android:** Always returns 1.0.
|
||||
/// - **Web:** Always returns 1.0
|
||||
///
|
||||
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.inner.scale_factor()
|
||||
|
||||
@@ -84,5 +84,10 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
|
||||
/// use winit::platform::android::activity::AndroidApp;
|
||||
/// ```
|
||||
pub mod activity {
|
||||
// We enable the `"native-activity"` feature just so that we can build the
|
||||
// docs, but it'll be very confusing for users to see the docs with that
|
||||
// feature enabled, so we avoid inlining it so that they're forced to view
|
||||
// it on the crate's own docs.rs page.
|
||||
#[doc(no_inline)]
|
||||
pub use android_activity::*;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use icrate::Foundation::MainThreadMarker;
|
||||
use objc2::rc::Id;
|
||||
|
||||
use crate::{
|
||||
@@ -68,11 +69,25 @@ pub trait WindowExtIOS {
|
||||
///
|
||||
/// The default is to prefer showing the status bar.
|
||||
///
|
||||
/// This changes the value returned by
|
||||
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc),
|
||||
/// and then calls
|
||||
/// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc).
|
||||
/// This sets the value of the
|
||||
/// [`prefersStatusBarHidden`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc)
|
||||
/// property.
|
||||
///
|
||||
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
|
||||
/// is also called for you.
|
||||
fn set_prefers_status_bar_hidden(&self, hidden: bool);
|
||||
|
||||
/// Sets the preferred status bar style for the [`Window`].
|
||||
///
|
||||
/// The default is system-defined.
|
||||
///
|
||||
/// This sets the value of the
|
||||
/// [`preferredStatusBarStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc)
|
||||
/// property.
|
||||
///
|
||||
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
|
||||
/// is also called for you.
|
||||
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle);
|
||||
}
|
||||
|
||||
impl WindowExtIOS for Window {
|
||||
@@ -106,6 +121,12 @@ impl WindowExtIOS for Window {
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to iOS.
|
||||
@@ -117,7 +138,7 @@ pub trait WindowBuilderExtIOS {
|
||||
///
|
||||
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
||||
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
|
||||
fn with_scale_factor(self, scale_factor: f64) -> WindowBuilder;
|
||||
fn with_scale_factor(self, scale_factor: f64) -> Self;
|
||||
|
||||
/// Sets the valid orientations for the [`Window`].
|
||||
///
|
||||
@@ -125,7 +146,7 @@ pub trait WindowBuilderExtIOS {
|
||||
///
|
||||
/// This sets the initial value returned by
|
||||
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
|
||||
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
|
||||
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> Self;
|
||||
|
||||
/// Sets whether the [`Window`] prefers the home indicator hidden.
|
||||
///
|
||||
@@ -135,7 +156,7 @@ pub trait WindowBuilderExtIOS {
|
||||
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
|
||||
///
|
||||
/// This only has an effect on iOS 11.0+.
|
||||
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder;
|
||||
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> Self;
|
||||
|
||||
/// Sets the screen edges for which the system gestures will take a lower priority than the
|
||||
/// application's touch handling.
|
||||
@@ -144,10 +165,7 @@ pub trait WindowBuilderExtIOS {
|
||||
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
|
||||
///
|
||||
/// This only has an effect on iOS 11.0+.
|
||||
fn with_preferred_screen_edges_deferring_system_gestures(
|
||||
self,
|
||||
edges: ScreenEdge,
|
||||
) -> WindowBuilder;
|
||||
fn with_preferred_screen_edges_deferring_system_gestures(self, edges: ScreenEdge) -> Self;
|
||||
|
||||
/// Sets whether the [`Window`] prefers the status bar hidden.
|
||||
///
|
||||
@@ -155,43 +173,54 @@ pub trait WindowBuilderExtIOS {
|
||||
///
|
||||
/// This sets the initial value returned by
|
||||
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
|
||||
fn with_prefers_status_bar_hidden(self, hidden: bool) -> WindowBuilder;
|
||||
fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self;
|
||||
|
||||
/// Sets the style of the [`Window`]'s status bar.
|
||||
///
|
||||
/// The default is system-defined.
|
||||
///
|
||||
/// This sets the initial value returned by
|
||||
/// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc),
|
||||
fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtIOS for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder {
|
||||
fn with_scale_factor(mut self, scale_factor: f64) -> Self {
|
||||
self.platform_specific.scale_factor = Some(scale_factor);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> WindowBuilder {
|
||||
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self {
|
||||
self.platform_specific.valid_orientations = valid_orientations;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder {
|
||||
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self {
|
||||
self.platform_specific.prefers_home_indicator_hidden = hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_preferred_screen_edges_deferring_system_gestures(
|
||||
mut self,
|
||||
edges: ScreenEdge,
|
||||
) -> WindowBuilder {
|
||||
fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
|
||||
self.platform_specific
|
||||
.preferred_screen_edges_deferring_system_gestures = edges;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder {
|
||||
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self {
|
||||
self.platform_specific.prefers_status_bar_hidden = hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
|
||||
self.platform_specific.preferred_status_bar_style = status_bar_style;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`MonitorHandle`] that are specific to iOS.
|
||||
@@ -210,7 +239,9 @@ pub trait MonitorHandleExtIOS {
|
||||
impl MonitorHandleExtIOS for MonitorHandle {
|
||||
#[inline]
|
||||
fn ui_screen(&self) -> *mut c_void {
|
||||
Id::as_ptr(self.inner.ui_screen()) as *mut c_void
|
||||
// SAFETY: The marker is only used to get the pointer of the screen
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -267,3 +298,11 @@ bitflags! {
|
||||
| ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum StatusBarStyle {
|
||||
#[default]
|
||||
Default,
|
||||
LightContent,
|
||||
DarkContent,
|
||||
}
|
||||
|
||||
@@ -183,92 +183,88 @@ pub enum ActivationPolicy {
|
||||
/// - `with_fullsize_content_view`
|
||||
pub trait WindowBuilderExtMacOS {
|
||||
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
|
||||
fn with_movable_by_window_background(self, movable_by_window_background: bool)
|
||||
-> WindowBuilder;
|
||||
fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self;
|
||||
/// Makes the titlebar transparent and allows the content to appear behind it.
|
||||
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder;
|
||||
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
|
||||
/// Hides the window title.
|
||||
fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder;
|
||||
fn with_title_hidden(self, title_hidden: bool) -> Self;
|
||||
/// Hides the window titlebar.
|
||||
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder;
|
||||
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
|
||||
/// Hides the window titlebar buttons.
|
||||
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
|
||||
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self;
|
||||
/// Makes the window content appear behind the titlebar.
|
||||
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
|
||||
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
|
||||
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
|
||||
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self;
|
||||
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self;
|
||||
fn with_has_shadow(self, has_shadow: bool) -> Self;
|
||||
/// Window accepts click-through mouse events.
|
||||
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
|
||||
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self;
|
||||
/// Defines the window tabbing identifier.
|
||||
///
|
||||
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
|
||||
fn with_tabbing_identifier(self, identifier: &str) -> WindowBuilder;
|
||||
fn with_tabbing_identifier(self, identifier: &str) -> Self;
|
||||
/// Set how the <kbd>Option</kbd> keys are interpreted.
|
||||
///
|
||||
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
|
||||
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> WindowBuilder;
|
||||
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_movable_by_window_background(
|
||||
mut self,
|
||||
movable_by_window_background: bool,
|
||||
) -> WindowBuilder {
|
||||
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
|
||||
self.platform_specific.movable_by_window_background = movable_by_window_background;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder {
|
||||
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
|
||||
self.platform_specific.titlebar_transparent = titlebar_transparent;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder {
|
||||
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
|
||||
self.platform_specific.titlebar_hidden = titlebar_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder {
|
||||
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
|
||||
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder {
|
||||
fn with_title_hidden(mut self, title_hidden: bool) -> Self {
|
||||
self.platform_specific.title_hidden = title_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder {
|
||||
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
|
||||
self.platform_specific.fullsize_content_view = fullsize_content_view;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
|
||||
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
|
||||
self.platform_specific.disallow_hidpi = disallow_hidpi;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder {
|
||||
fn with_has_shadow(mut self, has_shadow: bool) -> Self {
|
||||
self.platform_specific.has_shadow = has_shadow;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> WindowBuilder {
|
||||
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
|
||||
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> WindowBuilder {
|
||||
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
|
||||
self.platform_specific
|
||||
.tabbing_identifier
|
||||
.replace(tabbing_identifier.to_string());
|
||||
@@ -276,7 +272,7 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
|
||||
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
|
||||
self.platform_specific.option_as_alt = option_as_alt;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//!
|
||||
//! And the following platform-specific modules:
|
||||
//!
|
||||
//! - `run_ondemand` (available on `windows`, `unix`, `macos`, `android`)
|
||||
//! - `run_on_demand` (available on `windows`, `unix`, `macos`, `android`)
|
||||
//! - `pump_events` (available on `windows`, `unix`, `macos`, `android`)
|
||||
//!
|
||||
//! However only the module corresponding to the platform you're compiling to will be available.
|
||||
@@ -42,7 +42,7 @@ pub mod x11;
|
||||
x11_platform,
|
||||
wayland_platform
|
||||
))]
|
||||
pub mod run_ondemand;
|
||||
pub mod run_on_demand;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
@@ -53,5 +53,13 @@ pub mod run_ondemand;
|
||||
))]
|
||||
pub mod pump_events;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
orbital_platform,
|
||||
docsrs
|
||||
))]
|
||||
pub mod modifier_supplement;
|
||||
pub mod scancode;
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
};
|
||||
|
||||
/// The return status for `pump_events`
|
||||
@@ -63,7 +63,7 @@ pub trait EventLoopExtPumpEvents {
|
||||
///
|
||||
/// 'main: loop {
|
||||
/// let timeout = Some(Duration::ZERO);
|
||||
/// let status = event_loop.pump_events(timeout, |event, _, control_flow| {
|
||||
/// let status = event_loop.pump_events(timeout, |event, elwt| {
|
||||
/// # if let Event::WindowEvent { event, .. } = &event {
|
||||
/// # // Print only Window events to reduce noise
|
||||
/// # println!("{event:?}");
|
||||
@@ -73,7 +73,7 @@ pub trait EventLoopExtPumpEvents {
|
||||
/// Event::WindowEvent {
|
||||
/// event: WindowEvent::CloseRequested,
|
||||
/// window_id,
|
||||
/// } if window_id == window.id() => control_flow.set_exit(),
|
||||
/// } if window_id == window.id() => elwt.exit(),
|
||||
/// Event::AboutToWait => {
|
||||
/// window.request_redraw();
|
||||
/// }
|
||||
@@ -174,7 +174,7 @@ pub trait EventLoopExtPumpEvents {
|
||||
/// callback.
|
||||
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
|
||||
@@ -182,7 +182,7 @@ impl<T> EventLoopExtPumpEvents for EventLoop<T> {
|
||||
|
||||
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
|
||||
{
|
||||
self.event_loop.pump_events(timeout, event_handler)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
error::EventLoopError,
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
};
|
||||
|
||||
#[cfg(doc)]
|
||||
@@ -17,7 +17,7 @@ pub trait EventLoopExtRunOnDemand {
|
||||
///
|
||||
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures
|
||||
/// and it is possible to return control back to the caller without
|
||||
/// consuming the `EventLoop` (by setting the `control_flow` to [`ControlFlow::Exit`]) and
|
||||
/// consuming the `EventLoop` (by using [`exit()`]) and
|
||||
/// so the event loop can be re-run after it has exit.
|
||||
///
|
||||
/// It's expected that each run of the loop will be for orthogonal instantiations of your
|
||||
@@ -28,17 +28,16 @@ pub trait EventLoopExtRunOnDemand {
|
||||
/// to while maintaining the full state of your application. (If you need something like this
|
||||
/// you can look at the [`EventLoopExtPumpEvents::pump_events()`] API)
|
||||
///
|
||||
/// Each time `run_ondemand` is called the `event_handler` can expect to receive a
|
||||
/// Each time `run_on_demand` is called the `event_handler` can expect to receive a
|
||||
/// `NewEvents(Init)` and `Resumed` event (even on platforms that have no suspend/resume
|
||||
/// lifecycle) - which can be used to consistently initialize application state.
|
||||
///
|
||||
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
|
||||
/// event loop's behavior.
|
||||
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
|
||||
///
|
||||
/// # Caveats
|
||||
/// - This extension isn't available on all platforms, since it's not always possible to
|
||||
/// return to the caller (specifically this is impossible on iOS and Web - though with
|
||||
/// the Web backend it is possible to use `spawn()` more than once instead).
|
||||
/// - This extension isn't available on all platforms, since it's not always possible to return
|
||||
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
|
||||
/// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead).
|
||||
/// - No [`Window`] state can be carried between separate runs of the event loop.
|
||||
///
|
||||
/// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you specifically need
|
||||
@@ -57,18 +56,34 @@ pub trait EventLoopExtRunOnDemand {
|
||||
/// polled to ask for new events. Events are delivered via callbacks based
|
||||
/// on an event loop that is internal to the browser itself.
|
||||
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS.
|
||||
fn run_ondemand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
///
|
||||
#[cfg_attr(
|
||||
not(wasm_platform),
|
||||
doc = "[^1]: `spawn()` is only available on `wasm` platforms."
|
||||
)]
|
||||
///
|
||||
/// [`exit()`]: EventLoopWindowTarget::exit()
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
|
||||
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
|
||||
type UserEvent = T;
|
||||
|
||||
fn run_ondemand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
|
||||
{
|
||||
self.event_loop.run_ondemand(event_handler)
|
||||
self.event_loop.window_target().clear_exit();
|
||||
self.event_loop.run_on_demand(event_handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
/// Clear exit status.
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.p.clear_exit()
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
|
||||
|
||||
use crate::keyboard::KeyCode;
|
||||
use crate::keyboard::{KeyCode, PhysicalKey};
|
||||
|
||||
// TODO: Describe what this value contains for each platform
|
||||
|
||||
/// Additional methods for the [`KeyCode`] type that allow the user to access the platform-specific
|
||||
/// Additional methods for the [`PhysicalKey`] type that allow the user to access the platform-specific
|
||||
/// scancode.
|
||||
///
|
||||
/// [`KeyCode`]: crate::keyboard::KeyCode
|
||||
pub trait KeyCodeExtScancode {
|
||||
/// [`PhysicalKey`]: crate::keyboard::PhysicalKey
|
||||
pub trait PhysicalKeyExtScancode {
|
||||
/// The raw value of the platform-specific physical key identifier.
|
||||
///
|
||||
/// Returns `Some(key_id)` if the conversion was succesful; returns `None` otherwise.
|
||||
@@ -18,13 +18,28 @@ pub trait KeyCodeExtScancode {
|
||||
/// - **Wayland/X11**: A 32-bit linux scancode, which is X11/Wayland keycode subtracted by 8.
|
||||
fn to_scancode(self) -> Option<u32>;
|
||||
|
||||
/// Constructs a `KeyCode` from a platform-specific physical key identifier.
|
||||
/// Constructs a `PhysicalKey` from a platform-specific physical key identifier.
|
||||
///
|
||||
/// Note that this conversion may be lossy, i.e. converting the returned `KeyCode` back
|
||||
/// Note that this conversion may be lossy, i.e. converting the returned `PhysicalKey` back
|
||||
/// using `to_scancode` might not yield the original value.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
/// - **Wayland/X11**: A 32-bit linux scancode. When building from X11/Wayland keycode subtract
|
||||
/// `8` to get the value you wanted.
|
||||
fn from_scancode(scancode: u32) -> KeyCode;
|
||||
fn from_scancode(scancode: u32) -> PhysicalKey;
|
||||
}
|
||||
|
||||
impl PhysicalKeyExtScancode for KeyCode
|
||||
where
|
||||
PhysicalKey: PhysicalKeyExtScancode,
|
||||
{
|
||||
#[inline]
|
||||
fn from_scancode(scancode: u32) -> PhysicalKey {
|
||||
<PhysicalKey as PhysicalKeyExtScancode>::from_scancode(scancode)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_scancode(self) -> Option<u32> {
|
||||
<PhysicalKey as PhysicalKeyExtScancode>::to_scancode(PhysicalKey::Code(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,12 +85,7 @@ impl WindowBuilderExtStartupNotify for WindowBuilder {
|
||||
///
|
||||
/// This is wise to do before running child processes,
|
||||
/// which may not to support the activation token.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// While the function is safe internally, it mutates the global environment
|
||||
/// state for the process, hence unsafe.
|
||||
pub unsafe fn reset_activation_token_env() {
|
||||
pub fn reset_activation_token_env() {
|
||||
env::remove_var(X11_VAR);
|
||||
env::remove_var(WAYLAND_VAR);
|
||||
}
|
||||
@@ -98,12 +93,7 @@ pub unsafe fn reset_activation_token_env() {
|
||||
/// Set environment variables responsible for activation token.
|
||||
///
|
||||
/// This could be used before running daemon processes.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// While the function is safe internally, it mutates the global environment
|
||||
/// state for the process, hence unsafe.
|
||||
pub unsafe fn set_activation_token_env(token: ActivationToken) {
|
||||
pub fn set_activation_token_env(token: ActivationToken) {
|
||||
env::set_var(X11_VAR, &token._token);
|
||||
env::set_var(WAYLAND_VAR, token._token);
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::ControlFlow;
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::event_loop::EventLoopWindowTarget;
|
||||
use crate::window::{Window, WindowBuilder};
|
||||
use crate::SendSyncWrapper;
|
||||
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
@@ -48,8 +48,8 @@ impl WindowExtWebSys for Window {
|
||||
}
|
||||
|
||||
pub trait WindowBuilderExtWebSys {
|
||||
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`](crate::window::Window). If
|
||||
/// [`None`], [`WindowBuilder::build()`] will create one.
|
||||
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`],
|
||||
/// [`WindowBuilder::build()`] will create one.
|
||||
///
|
||||
/// In any case, the canvas won't be automatically inserted into the web page.
|
||||
///
|
||||
@@ -82,26 +82,22 @@ pub trait WindowBuilderExtWebSys {
|
||||
|
||||
impl WindowBuilderExtWebSys for WindowBuilder {
|
||||
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
|
||||
self.platform_specific.canvas = canvas;
|
||||
|
||||
self.platform_specific.canvas = SendSyncWrapper(canvas);
|
||||
self
|
||||
}
|
||||
|
||||
fn with_prevent_default(mut self, prevent_default: bool) -> Self {
|
||||
self.platform_specific.prevent_default = prevent_default;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn with_focusable(mut self, focusable: bool) -> Self {
|
||||
self.platform_specific.focusable = focusable;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn with_append(mut self, append: bool) -> Self {
|
||||
self.platform_specific.append = append;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -113,17 +109,31 @@ pub trait EventLoopExtWebSys {
|
||||
|
||||
/// Initializes the winit event loop.
|
||||
///
|
||||
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
|
||||
/// satisfy its `!` return type.
|
||||
/// Unlike
|
||||
#[cfg_attr(
|
||||
all(wasm_platform, target_feature = "exception-handling"),
|
||||
doc = "`run()`"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(all(wasm_platform, target_feature = "exception-handling")),
|
||||
doc = "[`run()`]"
|
||||
)]
|
||||
/// [^1], this returns immediately, and doesn't throw an exception in order to
|
||||
/// satisfy its [`!`] return type.
|
||||
///
|
||||
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop
|
||||
/// by calling this function again. This can be useful if you want to recreate the event loop
|
||||
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
|
||||
/// event loop when switching between tabs on a single page application.
|
||||
///
|
||||
#[cfg_attr(
|
||||
not(all(wasm_platform, target_feature = "exception-handling")),
|
||||
doc = "[`run()`]: EventLoop::run()"
|
||||
)]
|
||||
/// [^1]: `run()` is _not_ available on WASM when the target supports `exception-handling`.
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
|
||||
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
@@ -131,9 +141,62 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
|
||||
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
|
||||
{
|
||||
self.event_loop.spawn(event_handler)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventLoopWindowTargetExtWebSys {
|
||||
/// Sets the strategy for [`ControlFlow::Poll`].
|
||||
///
|
||||
/// See [`PollStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
|
||||
fn set_poll_strategy(&self, strategy: PollStrategy);
|
||||
|
||||
/// Gets the strategy for [`ControlFlow::Poll`].
|
||||
///
|
||||
/// See [`PollStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
|
||||
fn poll_strategy(&self) -> PollStrategy;
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtWebSys for EventLoopWindowTarget<T> {
|
||||
#[inline]
|
||||
fn set_poll_strategy(&self, strategy: PollStrategy) {
|
||||
self.p.set_poll_strategy(strategy);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_strategy(&self) -> PollStrategy {
|
||||
self.p.poll_strategy()
|
||||
}
|
||||
}
|
||||
|
||||
/// Strategy used for [`ControlFlow::Poll`](crate::event_loop::ControlFlow::Poll).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum PollStrategy {
|
||||
/// Uses [`Window.requestIdleCallback()`] to queue the next event loop. If not available
|
||||
/// this will fallback to [`setTimeout()`].
|
||||
///
|
||||
/// This strategy will wait for the browser to enter an idle period before running and might
|
||||
/// be affected by browser throttling.
|
||||
///
|
||||
/// [`Window.requestIdleCallback()`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
|
||||
/// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
|
||||
IdleCallback,
|
||||
/// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available
|
||||
/// this will fallback to [`setTimeout()`].
|
||||
///
|
||||
/// This strategy will run as fast as possible without disturbing users from interacting with
|
||||
/// the page and is not affected by browser throttling.
|
||||
///
|
||||
/// This is the default strategy.
|
||||
///
|
||||
/// [Prioritized Task Scheduling API]: https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API
|
||||
/// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
|
||||
#[default]
|
||||
Scheduler,
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ impl WindowExtWindows for Window {
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Windows.
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
pub trait WindowBuilderExtWindows {
|
||||
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
|
||||
/// This only works when [`WindowBuilder::with_parent_window`] isn't called or set to `None`.
|
||||
@@ -173,7 +174,7 @@ pub trait WindowBuilderExtWindows {
|
||||
/// - An owned window is hidden when its owner is minimized.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
|
||||
fn with_owner_window(self, parent: HWND) -> WindowBuilder;
|
||||
fn with_owner_window(self, parent: HWND) -> Self;
|
||||
|
||||
/// Sets a menu on the window to be created.
|
||||
///
|
||||
@@ -185,13 +186,13 @@ pub trait WindowBuilderExtWindows {
|
||||
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
|
||||
///
|
||||
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu
|
||||
fn with_menu(self, menu: HMENU) -> WindowBuilder;
|
||||
fn with_menu(self, menu: HMENU) -> Self;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
|
||||
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> Self;
|
||||
|
||||
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
|
||||
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
|
||||
fn with_no_redirection_bitmap(self, flag: bool) -> Self;
|
||||
|
||||
/// Enables or disables drag and drop support (enabled by default). Will interfere with other crates
|
||||
/// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of
|
||||
@@ -199,66 +200,66 @@ pub trait WindowBuilderExtWindows {
|
||||
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future.
|
||||
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions.
|
||||
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
|
||||
fn with_drag_and_drop(self, flag: bool) -> WindowBuilder;
|
||||
fn with_drag_and_drop(self, flag: bool) -> Self;
|
||||
|
||||
/// Whether show or hide the window icon in the taskbar.
|
||||
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
|
||||
fn with_skip_taskbar(self, skip: bool) -> Self;
|
||||
|
||||
/// Customize the window class name.
|
||||
fn with_class_name<S: Into<String>>(self, class_name: S) -> WindowBuilder;
|
||||
fn with_class_name<S: Into<String>>(self, class_name: S) -> Self;
|
||||
|
||||
/// Shows or hides the background drop shadow for undecorated windows.
|
||||
///
|
||||
/// The shadow is hidden by default.
|
||||
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
|
||||
fn with_undecorated_shadow(self, shadow: bool) -> WindowBuilder;
|
||||
fn with_undecorated_shadow(self, shadow: bool) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtWindows for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_owner_window(mut self, parent: HWND) -> WindowBuilder {
|
||||
fn with_owner_window(mut self, parent: HWND) -> Self {
|
||||
self.platform_specific.owner = Some(parent);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_menu(mut self, menu: HMENU) -> WindowBuilder {
|
||||
fn with_menu(mut self, menu: HMENU) -> Self {
|
||||
self.platform_specific.menu = Some(menu);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> WindowBuilder {
|
||||
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self {
|
||||
self.platform_specific.taskbar_icon = taskbar_icon;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder {
|
||||
fn with_no_redirection_bitmap(mut self, flag: bool) -> Self {
|
||||
self.platform_specific.no_redirection_bitmap = flag;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_drag_and_drop(mut self, flag: bool) -> WindowBuilder {
|
||||
fn with_drag_and_drop(mut self, flag: bool) -> Self {
|
||||
self.platform_specific.drag_and_drop = flag;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
|
||||
fn with_skip_taskbar(mut self, skip: bool) -> Self {
|
||||
self.platform_specific.skip_taskbar = skip;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_class_name<S: Into<String>>(mut self, class_name: S) -> WindowBuilder {
|
||||
fn with_class_name<S: Into<String>>(mut self, class_name: S) -> Self {
|
||||
self.platform_specific.class_name = class_name.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder {
|
||||
fn with_undecorated_shadow(mut self, shadow: bool) -> Self {
|
||||
self.platform_specific.decoration_shadow = shadow;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ pub trait WindowBuilderExtX11 {
|
||||
/// Build window with the given `general` and `instance` names.
|
||||
///
|
||||
/// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
|
||||
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
|
||||
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "instance", "general"`.
|
||||
///
|
||||
/// For details about application ID conventions, see the
|
||||
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
|
||||
|
||||
@@ -3,10 +3,10 @@ use android_activity::{
|
||||
AndroidApp,
|
||||
};
|
||||
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
|
||||
|
||||
pub fn to_physical_keycode(keycode: Keycode) -> KeyCode {
|
||||
match keycode {
|
||||
pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
|
||||
PhysicalKey::Code(match keycode {
|
||||
Keycode::A => KeyCode::KeyA,
|
||||
Keycode::B => KeyCode::KeyB,
|
||||
Keycode::C => KeyCode::KeyC,
|
||||
@@ -155,8 +155,8 @@ pub fn to_physical_keycode(keycode: Keycode) -> KeyCode {
|
||||
Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep?
|
||||
Keycode::Wakeup => KeyCode::WakeUp,
|
||||
|
||||
keycode => KeyCode::Unidentified(NativeKeyCode::Android(keycode.into())),
|
||||
}
|
||||
keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())),
|
||||
})
|
||||
}
|
||||
|
||||
/// Tries to map the `key_event` to a `KeyMapChar` containing a unicode character or dead key accent
|
||||
@@ -226,17 +226,15 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
|
||||
let native = NativeKey::Android(keycode.into());
|
||||
|
||||
match key_char {
|
||||
Some(KeyMapChar::Unicode(c)) => {
|
||||
Key::Character(smol_str::SmolStr::from_iter([c].into_iter()))
|
||||
}
|
||||
Some(KeyMapChar::Unicode(c)) => Key::Character(smol_str::SmolStr::from_iter([c])),
|
||||
Some(KeyMapChar::CombiningAccent(c)) => Key::Dead(Some(c)),
|
||||
None | Some(KeyMapChar::None) => match keycode {
|
||||
// Using `BrowserHome` instead of `GoHome` according to
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
||||
Home => Key::BrowserHome,
|
||||
Back => Key::BrowserBack,
|
||||
Call => Key::Call,
|
||||
Endcall => Key::EndCall,
|
||||
Home => Key::Named(NamedKey::BrowserHome),
|
||||
Back => Key::Named(NamedKey::BrowserBack),
|
||||
Call => Key::Named(NamedKey::Call),
|
||||
Endcall => Key::Named(NamedKey::EndCall),
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// These should be redundant because they should have already been matched
|
||||
@@ -293,81 +291,81 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
|
||||
At => Key::Character("@".into()),
|
||||
Plus => Key::Character("+".into()),
|
||||
//-------------------------------------------------------------------------------
|
||||
DpadUp => Key::ArrowUp,
|
||||
DpadDown => Key::ArrowDown,
|
||||
DpadLeft => Key::ArrowLeft,
|
||||
DpadRight => Key::ArrowRight,
|
||||
DpadCenter => Key::Enter,
|
||||
DpadUp => Key::Named(NamedKey::ArrowUp),
|
||||
DpadDown => Key::Named(NamedKey::ArrowDown),
|
||||
DpadLeft => Key::Named(NamedKey::ArrowLeft),
|
||||
DpadRight => Key::Named(NamedKey::ArrowRight),
|
||||
DpadCenter => Key::Named(NamedKey::Enter),
|
||||
|
||||
VolumeUp => Key::AudioVolumeUp,
|
||||
VolumeDown => Key::AudioVolumeDown,
|
||||
Power => Key::Power,
|
||||
Camera => Key::Camera,
|
||||
Clear => Key::Clear,
|
||||
VolumeUp => Key::Named(NamedKey::AudioVolumeUp),
|
||||
VolumeDown => Key::Named(NamedKey::AudioVolumeDown),
|
||||
Power => Key::Named(NamedKey::Power),
|
||||
Camera => Key::Named(NamedKey::Camera),
|
||||
Clear => Key::Named(NamedKey::Clear),
|
||||
|
||||
AltLeft => Key::Alt,
|
||||
AltRight => Key::Alt,
|
||||
ShiftLeft => Key::Shift,
|
||||
ShiftRight => Key::Shift,
|
||||
Tab => Key::Tab,
|
||||
Space => Key::Space,
|
||||
Sym => Key::Symbol,
|
||||
Explorer => Key::LaunchWebBrowser,
|
||||
Envelope => Key::LaunchMail,
|
||||
Enter => Key::Enter,
|
||||
Del => Key::Backspace,
|
||||
AltLeft => Key::Named(NamedKey::Alt),
|
||||
AltRight => Key::Named(NamedKey::Alt),
|
||||
ShiftLeft => Key::Named(NamedKey::Shift),
|
||||
ShiftRight => Key::Named(NamedKey::Shift),
|
||||
Tab => Key::Named(NamedKey::Tab),
|
||||
Space => Key::Named(NamedKey::Space),
|
||||
Sym => Key::Named(NamedKey::Symbol),
|
||||
Explorer => Key::Named(NamedKey::LaunchWebBrowser),
|
||||
Envelope => Key::Named(NamedKey::LaunchMail),
|
||||
Enter => Key::Named(NamedKey::Enter),
|
||||
Del => Key::Named(NamedKey::Backspace),
|
||||
|
||||
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
|
||||
Num => Key::Alt,
|
||||
Num => Key::Named(NamedKey::Alt),
|
||||
|
||||
Headsethook => Key::HeadsetHook,
|
||||
Focus => Key::CameraFocus,
|
||||
Headsethook => Key::Named(NamedKey::HeadsetHook),
|
||||
Focus => Key::Named(NamedKey::CameraFocus),
|
||||
|
||||
Notification => Key::Notification,
|
||||
Search => Key::BrowserSearch,
|
||||
MediaPlayPause => Key::MediaPlayPause,
|
||||
MediaStop => Key::MediaStop,
|
||||
MediaNext => Key::MediaTrackNext,
|
||||
MediaPrevious => Key::MediaTrackPrevious,
|
||||
MediaRewind => Key::MediaRewind,
|
||||
MediaFastForward => Key::MediaFastForward,
|
||||
Mute => Key::MicrophoneVolumeMute,
|
||||
PageUp => Key::PageUp,
|
||||
PageDown => Key::PageDown,
|
||||
Notification => Key::Named(NamedKey::Notification),
|
||||
Search => Key::Named(NamedKey::BrowserSearch),
|
||||
MediaPlayPause => Key::Named(NamedKey::MediaPlayPause),
|
||||
MediaStop => Key::Named(NamedKey::MediaStop),
|
||||
MediaNext => Key::Named(NamedKey::MediaTrackNext),
|
||||
MediaPrevious => Key::Named(NamedKey::MediaTrackPrevious),
|
||||
MediaRewind => Key::Named(NamedKey::MediaRewind),
|
||||
MediaFastForward => Key::Named(NamedKey::MediaFastForward),
|
||||
Mute => Key::Named(NamedKey::MicrophoneVolumeMute),
|
||||
PageUp => Key::Named(NamedKey::PageUp),
|
||||
PageDown => Key::Named(NamedKey::PageDown),
|
||||
|
||||
Escape => Key::Escape,
|
||||
ForwardDel => Key::Delete,
|
||||
CtrlLeft => Key::Control,
|
||||
CtrlRight => Key::Control,
|
||||
CapsLock => Key::CapsLock,
|
||||
ScrollLock => Key::ScrollLock,
|
||||
MetaLeft => Key::Super,
|
||||
MetaRight => Key::Super,
|
||||
Function => Key::Fn,
|
||||
Sysrq => Key::PrintScreen,
|
||||
Break => Key::Pause,
|
||||
MoveHome => Key::Home,
|
||||
MoveEnd => Key::End,
|
||||
Insert => Key::Insert,
|
||||
Forward => Key::BrowserForward,
|
||||
MediaPlay => Key::MediaPlay,
|
||||
MediaPause => Key::MediaPause,
|
||||
MediaClose => Key::MediaClose,
|
||||
MediaEject => Key::Eject,
|
||||
MediaRecord => Key::MediaRecord,
|
||||
F1 => Key::F1,
|
||||
F2 => Key::F2,
|
||||
F3 => Key::F3,
|
||||
F4 => Key::F4,
|
||||
F5 => Key::F5,
|
||||
F6 => Key::F6,
|
||||
F7 => Key::F7,
|
||||
F8 => Key::F8,
|
||||
F9 => Key::F9,
|
||||
F10 => Key::F10,
|
||||
F11 => Key::F11,
|
||||
F12 => Key::F12,
|
||||
NumLock => Key::NumLock,
|
||||
Escape => Key::Named(NamedKey::Escape),
|
||||
ForwardDel => Key::Named(NamedKey::Delete),
|
||||
CtrlLeft => Key::Named(NamedKey::Control),
|
||||
CtrlRight => Key::Named(NamedKey::Control),
|
||||
CapsLock => Key::Named(NamedKey::CapsLock),
|
||||
ScrollLock => Key::Named(NamedKey::ScrollLock),
|
||||
MetaLeft => Key::Named(NamedKey::Super),
|
||||
MetaRight => Key::Named(NamedKey::Super),
|
||||
Function => Key::Named(NamedKey::Fn),
|
||||
Sysrq => Key::Named(NamedKey::PrintScreen),
|
||||
Break => Key::Named(NamedKey::Pause),
|
||||
MoveHome => Key::Named(NamedKey::Home),
|
||||
MoveEnd => Key::Named(NamedKey::End),
|
||||
Insert => Key::Named(NamedKey::Insert),
|
||||
Forward => Key::Named(NamedKey::BrowserForward),
|
||||
MediaPlay => Key::Named(NamedKey::MediaPlay),
|
||||
MediaPause => Key::Named(NamedKey::MediaPause),
|
||||
MediaClose => Key::Named(NamedKey::MediaClose),
|
||||
MediaEject => Key::Named(NamedKey::Eject),
|
||||
MediaRecord => Key::Named(NamedKey::MediaRecord),
|
||||
F1 => Key::Named(NamedKey::F1),
|
||||
F2 => Key::Named(NamedKey::F2),
|
||||
F3 => Key::Named(NamedKey::F3),
|
||||
F4 => Key::Named(NamedKey::F4),
|
||||
F5 => Key::Named(NamedKey::F5),
|
||||
F6 => Key::Named(NamedKey::F6),
|
||||
F7 => Key::Named(NamedKey::F7),
|
||||
F8 => Key::Named(NamedKey::F8),
|
||||
F9 => Key::Named(NamedKey::F9),
|
||||
F10 => Key::Named(NamedKey::F10),
|
||||
F11 => Key::Named(NamedKey::F11),
|
||||
F12 => Key::Named(NamedKey::F12),
|
||||
NumLock => Key::Named(NamedKey::NumLock),
|
||||
Numpad0 => Key::Character("0".into()),
|
||||
Numpad1 => Key::Character("1".into()),
|
||||
Numpad2 => Key::Character("2".into()),
|
||||
@@ -384,97 +382,97 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
|
||||
NumpadAdd => Key::Character("+".into()),
|
||||
NumpadDot => Key::Character(".".into()),
|
||||
NumpadComma => Key::Character(",".into()),
|
||||
NumpadEnter => Key::Enter,
|
||||
NumpadEnter => Key::Named(NamedKey::Enter),
|
||||
NumpadEquals => Key::Character("=".into()),
|
||||
NumpadLeftParen => Key::Character("(".into()),
|
||||
NumpadRightParen => Key::Character(")".into()),
|
||||
|
||||
VolumeMute => Key::AudioVolumeMute,
|
||||
Info => Key::Info,
|
||||
ChannelUp => Key::ChannelUp,
|
||||
ChannelDown => Key::ChannelDown,
|
||||
ZoomIn => Key::ZoomIn,
|
||||
ZoomOut => Key::ZoomOut,
|
||||
Tv => Key::TV,
|
||||
Guide => Key::Guide,
|
||||
Dvr => Key::DVR,
|
||||
Bookmark => Key::BrowserFavorites,
|
||||
Captions => Key::ClosedCaptionToggle,
|
||||
Settings => Key::Settings,
|
||||
TvPower => Key::TVPower,
|
||||
TvInput => Key::TVInput,
|
||||
StbPower => Key::STBPower,
|
||||
StbInput => Key::STBInput,
|
||||
AvrPower => Key::AVRPower,
|
||||
AvrInput => Key::AVRInput,
|
||||
ProgRed => Key::ColorF0Red,
|
||||
ProgGreen => Key::ColorF1Green,
|
||||
ProgYellow => Key::ColorF2Yellow,
|
||||
ProgBlue => Key::ColorF3Blue,
|
||||
AppSwitch => Key::AppSwitch,
|
||||
LanguageSwitch => Key::GroupNext,
|
||||
MannerMode => Key::MannerMode,
|
||||
Keycode3dMode => Key::TV3DMode,
|
||||
Contacts => Key::LaunchContacts,
|
||||
Calendar => Key::LaunchCalendar,
|
||||
Music => Key::LaunchMusicPlayer,
|
||||
Calculator => Key::LaunchApplication2,
|
||||
ZenkakuHankaku => Key::ZenkakuHankaku,
|
||||
Eisu => Key::Eisu,
|
||||
Muhenkan => Key::NonConvert,
|
||||
Henkan => Key::Convert,
|
||||
KatakanaHiragana => Key::HiraganaKatakana,
|
||||
Kana => Key::KanjiMode,
|
||||
BrightnessDown => Key::BrightnessDown,
|
||||
BrightnessUp => Key::BrightnessUp,
|
||||
MediaAudioTrack => Key::MediaAudioTrack,
|
||||
Sleep => Key::Standby,
|
||||
Wakeup => Key::WakeUp,
|
||||
Pairing => Key::Pairing,
|
||||
MediaTopMenu => Key::MediaTopMenu,
|
||||
LastChannel => Key::MediaLast,
|
||||
TvDataService => Key::TVDataService,
|
||||
VoiceAssist => Key::VoiceDial,
|
||||
TvRadioService => Key::TVRadioService,
|
||||
TvTeletext => Key::Teletext,
|
||||
TvNumberEntry => Key::TVNumberEntry,
|
||||
TvTerrestrialAnalog => Key::TVTerrestrialAnalog,
|
||||
TvTerrestrialDigital => Key::TVTerrestrialDigital,
|
||||
TvSatellite => Key::TVSatellite,
|
||||
TvSatelliteBs => Key::TVSatelliteBS,
|
||||
TvSatelliteCs => Key::TVSatelliteCS,
|
||||
TvSatelliteService => Key::TVSatelliteToggle,
|
||||
TvNetwork => Key::TVNetwork,
|
||||
TvAntennaCable => Key::TVAntennaCable,
|
||||
TvInputHdmi1 => Key::TVInputHDMI1,
|
||||
TvInputHdmi2 => Key::TVInputHDMI2,
|
||||
TvInputHdmi3 => Key::TVInputHDMI3,
|
||||
TvInputHdmi4 => Key::TVInputHDMI4,
|
||||
TvInputComposite1 => Key::TVInputComposite1,
|
||||
TvInputComposite2 => Key::TVInputComposite2,
|
||||
TvInputComponent1 => Key::TVInputComponent1,
|
||||
TvInputComponent2 => Key::TVInputComponent2,
|
||||
TvInputVga1 => Key::TVInputVGA1,
|
||||
TvAudioDescription => Key::TVAudioDescription,
|
||||
TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp,
|
||||
TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown,
|
||||
TvZoomMode => Key::ZoomToggle,
|
||||
TvContentsMenu => Key::TVContentsMenu,
|
||||
TvMediaContextMenu => Key::TVMediaContext,
|
||||
TvTimerProgramming => Key::TVTimer,
|
||||
Help => Key::Help,
|
||||
NavigatePrevious => Key::NavigatePrevious,
|
||||
NavigateNext => Key::NavigateNext,
|
||||
NavigateIn => Key::NavigateIn,
|
||||
NavigateOut => Key::NavigateOut,
|
||||
MediaSkipForward => Key::MediaSkipForward,
|
||||
MediaSkipBackward => Key::MediaSkipBackward,
|
||||
MediaStepForward => Key::MediaStepForward,
|
||||
MediaStepBackward => Key::MediaStepBackward,
|
||||
Cut => Key::Cut,
|
||||
Copy => Key::Copy,
|
||||
Paste => Key::Paste,
|
||||
Refresh => Key::BrowserRefresh,
|
||||
VolumeMute => Key::Named(NamedKey::AudioVolumeMute),
|
||||
Info => Key::Named(NamedKey::Info),
|
||||
ChannelUp => Key::Named(NamedKey::ChannelUp),
|
||||
ChannelDown => Key::Named(NamedKey::ChannelDown),
|
||||
ZoomIn => Key::Named(NamedKey::ZoomIn),
|
||||
ZoomOut => Key::Named(NamedKey::ZoomOut),
|
||||
Tv => Key::Named(NamedKey::TV),
|
||||
Guide => Key::Named(NamedKey::Guide),
|
||||
Dvr => Key::Named(NamedKey::DVR),
|
||||
Bookmark => Key::Named(NamedKey::BrowserFavorites),
|
||||
Captions => Key::Named(NamedKey::ClosedCaptionToggle),
|
||||
Settings => Key::Named(NamedKey::Settings),
|
||||
TvPower => Key::Named(NamedKey::TVPower),
|
||||
TvInput => Key::Named(NamedKey::TVInput),
|
||||
StbPower => Key::Named(NamedKey::STBPower),
|
||||
StbInput => Key::Named(NamedKey::STBInput),
|
||||
AvrPower => Key::Named(NamedKey::AVRPower),
|
||||
AvrInput => Key::Named(NamedKey::AVRInput),
|
||||
ProgRed => Key::Named(NamedKey::ColorF0Red),
|
||||
ProgGreen => Key::Named(NamedKey::ColorF1Green),
|
||||
ProgYellow => Key::Named(NamedKey::ColorF2Yellow),
|
||||
ProgBlue => Key::Named(NamedKey::ColorF3Blue),
|
||||
AppSwitch => Key::Named(NamedKey::AppSwitch),
|
||||
LanguageSwitch => Key::Named(NamedKey::GroupNext),
|
||||
MannerMode => Key::Named(NamedKey::MannerMode),
|
||||
Keycode3dMode => Key::Named(NamedKey::TV3DMode),
|
||||
Contacts => Key::Named(NamedKey::LaunchContacts),
|
||||
Calendar => Key::Named(NamedKey::LaunchCalendar),
|
||||
Music => Key::Named(NamedKey::LaunchMusicPlayer),
|
||||
Calculator => Key::Named(NamedKey::LaunchApplication2),
|
||||
ZenkakuHankaku => Key::Named(NamedKey::ZenkakuHankaku),
|
||||
Eisu => Key::Named(NamedKey::Eisu),
|
||||
Muhenkan => Key::Named(NamedKey::NonConvert),
|
||||
Henkan => Key::Named(NamedKey::Convert),
|
||||
KatakanaHiragana => Key::Named(NamedKey::HiraganaKatakana),
|
||||
Kana => Key::Named(NamedKey::KanjiMode),
|
||||
BrightnessDown => Key::Named(NamedKey::BrightnessDown),
|
||||
BrightnessUp => Key::Named(NamedKey::BrightnessUp),
|
||||
MediaAudioTrack => Key::Named(NamedKey::MediaAudioTrack),
|
||||
Sleep => Key::Named(NamedKey::Standby),
|
||||
Wakeup => Key::Named(NamedKey::WakeUp),
|
||||
Pairing => Key::Named(NamedKey::Pairing),
|
||||
MediaTopMenu => Key::Named(NamedKey::MediaTopMenu),
|
||||
LastChannel => Key::Named(NamedKey::MediaLast),
|
||||
TvDataService => Key::Named(NamedKey::TVDataService),
|
||||
VoiceAssist => Key::Named(NamedKey::VoiceDial),
|
||||
TvRadioService => Key::Named(NamedKey::TVRadioService),
|
||||
TvTeletext => Key::Named(NamedKey::Teletext),
|
||||
TvNumberEntry => Key::Named(NamedKey::TVNumberEntry),
|
||||
TvTerrestrialAnalog => Key::Named(NamedKey::TVTerrestrialAnalog),
|
||||
TvTerrestrialDigital => Key::Named(NamedKey::TVTerrestrialDigital),
|
||||
TvSatellite => Key::Named(NamedKey::TVSatellite),
|
||||
TvSatelliteBs => Key::Named(NamedKey::TVSatelliteBS),
|
||||
TvSatelliteCs => Key::Named(NamedKey::TVSatelliteCS),
|
||||
TvSatelliteService => Key::Named(NamedKey::TVSatelliteToggle),
|
||||
TvNetwork => Key::Named(NamedKey::TVNetwork),
|
||||
TvAntennaCable => Key::Named(NamedKey::TVAntennaCable),
|
||||
TvInputHdmi1 => Key::Named(NamedKey::TVInputHDMI1),
|
||||
TvInputHdmi2 => Key::Named(NamedKey::TVInputHDMI2),
|
||||
TvInputHdmi3 => Key::Named(NamedKey::TVInputHDMI3),
|
||||
TvInputHdmi4 => Key::Named(NamedKey::TVInputHDMI4),
|
||||
TvInputComposite1 => Key::Named(NamedKey::TVInputComposite1),
|
||||
TvInputComposite2 => Key::Named(NamedKey::TVInputComposite2),
|
||||
TvInputComponent1 => Key::Named(NamedKey::TVInputComponent1),
|
||||
TvInputComponent2 => Key::Named(NamedKey::TVInputComponent2),
|
||||
TvInputVga1 => Key::Named(NamedKey::TVInputVGA1),
|
||||
TvAudioDescription => Key::Named(NamedKey::TVAudioDescription),
|
||||
TvAudioDescriptionMixUp => Key::Named(NamedKey::TVAudioDescriptionMixUp),
|
||||
TvAudioDescriptionMixDown => Key::Named(NamedKey::TVAudioDescriptionMixDown),
|
||||
TvZoomMode => Key::Named(NamedKey::ZoomToggle),
|
||||
TvContentsMenu => Key::Named(NamedKey::TVContentsMenu),
|
||||
TvMediaContextMenu => Key::Named(NamedKey::TVMediaContext),
|
||||
TvTimerProgramming => Key::Named(NamedKey::TVTimer),
|
||||
Help => Key::Named(NamedKey::Help),
|
||||
NavigatePrevious => Key::Named(NamedKey::NavigatePrevious),
|
||||
NavigateNext => Key::Named(NamedKey::NavigateNext),
|
||||
NavigateIn => Key::Named(NamedKey::NavigateIn),
|
||||
NavigateOut => Key::Named(NamedKey::NavigateOut),
|
||||
MediaSkipForward => Key::Named(NamedKey::MediaSkipForward),
|
||||
MediaSkipBackward => Key::Named(NamedKey::MediaSkipBackward),
|
||||
MediaStepForward => Key::Named(NamedKey::MediaStepForward),
|
||||
MediaStepBackward => Key::Named(NamedKey::MediaStepBackward),
|
||||
Cut => Key::Named(NamedKey::Cut),
|
||||
Copy => Key::Named(NamedKey::Copy),
|
||||
Paste => Key::Named(NamedKey::Paste),
|
||||
Refresh => Key::Named(NamedKey::BrowserRefresh),
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Keycodes that don't have a logical Key mapping
|
||||
@@ -557,6 +555,10 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
|
||||
ThumbsUp => Key::Unidentified(native),
|
||||
ThumbsDown => Key::Unidentified(native),
|
||||
ProfileSwitch => Key::Unidentified(native),
|
||||
|
||||
// It's always possible that new versions of Android could introduce
|
||||
// key codes we can't know about at compile time.
|
||||
_ => Key::Unidentified(native),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![cfg(android_platform)]
|
||||
|
||||
use std::{
|
||||
cell::Cell,
|
||||
collections::VecDeque,
|
||||
hash::Hash,
|
||||
sync::{
|
||||
@@ -15,15 +16,12 @@ use android_activity::{
|
||||
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use raw_window_handle::{
|
||||
AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error,
|
||||
event::{self, InnerSizeWriter, StartCause},
|
||||
event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW},
|
||||
event::{self, Force, InnerSizeWriter, StartCause},
|
||||
event_loop::{self, ControlFlow, DeviceEvents, EventLoopWindowTarget as RootELW},
|
||||
platform::pump_events::PumpStatus,
|
||||
window::{
|
||||
self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel,
|
||||
@@ -147,7 +145,6 @@ pub struct EventLoop<T: 'static> {
|
||||
loop_running: bool, // Dispatched `NewEvents<Init>`
|
||||
running: bool,
|
||||
pending_redraw: bool,
|
||||
control_flow: ControlFlow,
|
||||
cause: StartCause,
|
||||
ignore_volume_keys: bool,
|
||||
combining_accent: Option<char>,
|
||||
@@ -168,23 +165,6 @@ impl Default for PlatformSpecificEventLoopAttributes {
|
||||
}
|
||||
}
|
||||
|
||||
fn sticky_exit_callback<T, F>(
|
||||
evt: event::Event<T>,
|
||||
target: &RootELW<T>,
|
||||
control_flow: &mut ControlFlow,
|
||||
callback: &mut F,
|
||||
) where
|
||||
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
// make ControlFlow::ExitWithCode sticky by providing a dummy
|
||||
// control flow reference if it is already ExitWithCode.
|
||||
if let ControlFlow::ExitWithCode(code) = *control_flow {
|
||||
callback(evt, target, &mut ControlFlow::ExitWithCode(code))
|
||||
} else {
|
||||
callback(evt, target, control_flow)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub(crate) fn new(
|
||||
attributes: &PlatformSpecificEventLoopAttributes,
|
||||
@@ -199,6 +179,8 @@ impl<T: 'static> EventLoop<T> {
|
||||
window_target: event_loop::EventLoopWindowTarget {
|
||||
p: EventLoopWindowTarget {
|
||||
app: android_app.clone(),
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
exit: Cell::new(false),
|
||||
redraw_requester: RedrawRequester::new(
|
||||
&redraw_flag,
|
||||
android_app.create_waker(),
|
||||
@@ -213,7 +195,6 @@ impl<T: 'static> EventLoop<T> {
|
||||
loop_running: false,
|
||||
running: false,
|
||||
pending_redraw: false,
|
||||
control_flow: Default::default(),
|
||||
cause: StartCause::Init,
|
||||
ignore_volume_keys: attributes.ignore_volume_keys,
|
||||
combining_accent: None,
|
||||
@@ -222,41 +203,25 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F)
|
||||
where
|
||||
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(event::Event<T>, &RootELW<T>),
|
||||
{
|
||||
trace!("Mainloop iteration");
|
||||
|
||||
let cause = self.cause;
|
||||
let mut control_flow = self.control_flow;
|
||||
let mut pending_redraw = self.pending_redraw;
|
||||
let mut resized = false;
|
||||
|
||||
sticky_exit_callback(
|
||||
event::Event::NewEvents(cause),
|
||||
self.window_target(),
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
callback(event::Event::NewEvents(cause), self.window_target());
|
||||
|
||||
if let Some(event) = main_event {
|
||||
trace!("Handling main event {:?}", event);
|
||||
|
||||
match event {
|
||||
MainEvent::InitWindow { .. } => {
|
||||
sticky_exit_callback(
|
||||
event::Event::Resumed,
|
||||
self.window_target(),
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
callback(event::Event::Resumed, self.window_target());
|
||||
}
|
||||
MainEvent::TerminateWindow { .. } => {
|
||||
sticky_exit_callback(
|
||||
event::Event::Suspended,
|
||||
self.window_target(),
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
callback(event::Event::Suspended, self.window_target());
|
||||
}
|
||||
MainEvent::WindowResized { .. } => resized = true,
|
||||
MainEvent::RedrawNeeded { .. } => pending_redraw = true,
|
||||
@@ -265,26 +230,22 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
MainEvent::GainedFocus => {
|
||||
*HAS_FOCUS.write().unwrap() = true;
|
||||
sticky_exit_callback(
|
||||
callback(
|
||||
event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::Focused(true),
|
||||
},
|
||||
self.window_target(),
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
MainEvent::LostFocus => {
|
||||
*HAS_FOCUS.write().unwrap() = false;
|
||||
sticky_exit_callback(
|
||||
callback(
|
||||
event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::Focused(false),
|
||||
},
|
||||
self.window_target(),
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
MainEvent::ConfigChanged { .. } => {
|
||||
@@ -304,19 +265,11 @@ impl<T: 'static> EventLoop<T> {
|
||||
scale_factor,
|
||||
},
|
||||
};
|
||||
sticky_exit_callback(
|
||||
event,
|
||||
self.window_target(),
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
callback(event, self.window_target());
|
||||
}
|
||||
}
|
||||
MainEvent::LowMemory => {
|
||||
// XXX: how to forward this state to applications?
|
||||
// It seems like ideally winit should support lifecycle and
|
||||
// low-memory events, especially for mobile platforms.
|
||||
warn!("TODO: handle Android LowMemory notification");
|
||||
callback(event::Event::MemoryWarning, self.window_target());
|
||||
}
|
||||
MainEvent::Start => {
|
||||
// XXX: how to forward this state to applications?
|
||||
@@ -363,9 +316,8 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Process input events
|
||||
match android_app.input_events_iter() {
|
||||
Ok(mut input_iter) => loop {
|
||||
let read_event = input_iter.next(|event| {
|
||||
self.handle_input_event(&android_app, event, &mut control_flow, callback)
|
||||
});
|
||||
let read_event =
|
||||
input_iter.next(|event| self.handle_input_event(&android_app, event, callback));
|
||||
|
||||
if !read_event {
|
||||
break;
|
||||
@@ -379,12 +331,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Empty the user event buffer
|
||||
{
|
||||
while let Ok(event) = self.user_events_receiver.try_recv() {
|
||||
sticky_exit_callback(
|
||||
crate::event::Event::UserEvent(event),
|
||||
self.window_target(),
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
callback(crate::event::Event::UserEvent(event), self.window_target());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,26 +348,23 @@ impl<T: 'static> EventLoop<T> {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::Resized(size),
|
||||
};
|
||||
sticky_exit_callback(event, self.window_target(), &mut control_flow, callback);
|
||||
callback(event, self.window_target());
|
||||
}
|
||||
|
||||
pending_redraw |= self.redraw_flag.get_and_reset();
|
||||
if pending_redraw {
|
||||
pending_redraw = false;
|
||||
let event = event::Event::RedrawRequested(window::WindowId(WindowId));
|
||||
sticky_exit_callback(event, self.window_target(), &mut control_flow, callback);
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::RedrawRequested,
|
||||
};
|
||||
callback(event, self.window_target());
|
||||
}
|
||||
}
|
||||
|
||||
// This is always the last event we dispatch before poll again
|
||||
sticky_exit_callback(
|
||||
event::Event::AboutToWait,
|
||||
self.window_target(),
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
callback(event::Event::AboutToWait, self.window_target());
|
||||
|
||||
self.control_flow = control_flow;
|
||||
self.pending_redraw = pending_redraw;
|
||||
}
|
||||
|
||||
@@ -428,17 +372,16 @@ impl<T: 'static> EventLoop<T> {
|
||||
&mut self,
|
||||
android_app: &AndroidApp,
|
||||
event: &InputEvent<'_>,
|
||||
control_flow: &mut ControlFlow,
|
||||
callback: &mut F,
|
||||
) -> InputStatus
|
||||
where
|
||||
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(event::Event<T>, &RootELW<T>),
|
||||
{
|
||||
let mut input_status = InputStatus::Handled;
|
||||
match event {
|
||||
InputEvent::MotionEvent(motion_event) => {
|
||||
let window_id = window::WindowId(WindowId);
|
||||
let device_id = event::DeviceId(DeviceId);
|
||||
let device_id = event::DeviceId(DeviceId(motion_event.device_id()));
|
||||
|
||||
let phase = match motion_event.action() {
|
||||
MotionAction::Down | MotionAction::PointerDown => {
|
||||
@@ -477,10 +420,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
phase,
|
||||
location,
|
||||
id: pointer.pointer_id() as u64,
|
||||
force: None,
|
||||
force: Some(Force::Normalized(pointer.pressure() as f64)),
|
||||
}),
|
||||
};
|
||||
sticky_exit_callback(event, self.window_target(), control_flow, callback);
|
||||
callback(event, self.window_target());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,10 +453,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::KeyboardInput {
|
||||
device_id: event::DeviceId(DeviceId),
|
||||
device_id: event::DeviceId(DeviceId(key.device_id())),
|
||||
event: event::KeyEvent {
|
||||
state,
|
||||
physical_key: keycodes::to_physical_keycode(keycode),
|
||||
physical_key: keycodes::to_physical_key(keycode),
|
||||
logical_key: keycodes::to_logical(key_char, keycode),
|
||||
location: keycodes::to_location(keycode),
|
||||
repeat: key.repeat_count() > 0,
|
||||
@@ -523,7 +466,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
is_synthetic: false,
|
||||
},
|
||||
};
|
||||
sticky_exit_callback(event, self.window_target(), control_flow, callback);
|
||||
callback(event, self.window_target());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -537,14 +480,14 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>),
|
||||
{
|
||||
self.run_ondemand(event_handler)
|
||||
self.run_on_demand(event_handler)
|
||||
}
|
||||
|
||||
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
|
||||
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>),
|
||||
{
|
||||
if self.loop_running {
|
||||
return Err(EventLoopError::AlreadyRunning);
|
||||
@@ -567,7 +510,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(event::Event<T>, &RootELW<T>),
|
||||
{
|
||||
if !self.loop_running {
|
||||
self.loop_running = true;
|
||||
@@ -577,7 +520,6 @@ impl<T: 'static> EventLoop<T> {
|
||||
// than once
|
||||
self.pending_redraw = false;
|
||||
self.cause = StartCause::Init;
|
||||
self.control_flow = ControlFlow::Poll;
|
||||
|
||||
// run the initial loop iteration
|
||||
self.single_iteration(None, &mut callback);
|
||||
@@ -585,21 +527,15 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
// Consider the possibility that the `StartCause::Init` iteration could
|
||||
// request to Exit
|
||||
if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) {
|
||||
if !self.exiting() {
|
||||
self.poll_events_with_timeout(timeout, &mut callback);
|
||||
}
|
||||
if let ControlFlow::ExitWithCode(code) = self.control_flow {
|
||||
if self.exiting() {
|
||||
self.loop_running = false;
|
||||
|
||||
let mut dummy = self.control_flow;
|
||||
sticky_exit_callback(
|
||||
event::Event::LoopExiting,
|
||||
self.window_target(),
|
||||
&mut dummy,
|
||||
&mut callback,
|
||||
);
|
||||
callback(event::Event::LoopExiting, self.window_target());
|
||||
|
||||
PumpStatus::Exit(code)
|
||||
PumpStatus::Exit(0)
|
||||
} else {
|
||||
PumpStatus::Continue
|
||||
}
|
||||
@@ -607,7 +543,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
|
||||
where
|
||||
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(event::Event<T>, &RootELW<T>),
|
||||
{
|
||||
let start = Instant::now();
|
||||
|
||||
@@ -618,14 +554,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
// If we already have work to do then we don't want to block on the next poll
|
||||
Some(Duration::ZERO)
|
||||
} else {
|
||||
let control_flow_timeout = match self.control_flow {
|
||||
let control_flow_timeout = match self.control_flow() {
|
||||
ControlFlow::Wait => None,
|
||||
ControlFlow::Poll => Some(Duration::ZERO),
|
||||
ControlFlow::WaitUntil(wait_deadline) => {
|
||||
Some(wait_deadline.saturating_duration_since(start))
|
||||
}
|
||||
// `ExitWithCode()` will be reset to `Poll` before polling
|
||||
ControlFlow::ExitWithCode(_code) => unreachable!(),
|
||||
};
|
||||
|
||||
min_timeout(control_flow_timeout, timeout)
|
||||
@@ -660,7 +594,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
self.cause = match self.control_flow {
|
||||
self.cause = match self.control_flow() {
|
||||
ControlFlow::Poll => StartCause::Poll,
|
||||
ControlFlow::Wait => StartCause::WaitCancelled {
|
||||
start,
|
||||
@@ -679,8 +613,6 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// `ExitWithCode()` will be reset to `Poll` before polling
|
||||
ControlFlow::ExitWithCode(_code) => unreachable!(),
|
||||
};
|
||||
|
||||
self.single_iteration(main_event, &mut callback);
|
||||
@@ -697,6 +629,14 @@ impl<T: 'static> EventLoop<T> {
|
||||
waker: self.android_app.create_waker(),
|
||||
}
|
||||
}
|
||||
|
||||
fn control_flow(&self) -> ControlFlow {
|
||||
self.window_target.p.control_flow()
|
||||
}
|
||||
|
||||
fn exiting(&self) -> bool {
|
||||
self.window_target.p.exiting()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
@@ -725,6 +665,8 @@ impl<T> EventLoopProxy<T> {
|
||||
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
app: AndroidApp,
|
||||
control_flow: Cell<ControlFlow>,
|
||||
exit: Cell<bool>,
|
||||
redraw_requester: RedrawRequester,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
@@ -740,8 +682,43 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
v
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
|
||||
#[inline]
|
||||
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
|
||||
rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::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::Android(
|
||||
rwh_06::AndroidDisplayHandle::new(),
|
||||
))
|
||||
}
|
||||
|
||||
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(true)
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.exit.set(false)
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
self.exit.get()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -767,11 +744,11 @@ impl From<u64> for WindowId {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct DeviceId;
|
||||
pub struct DeviceId(i32);
|
||||
|
||||
impl DeviceId {
|
||||
pub const fn dummy() -> Self {
|
||||
DeviceId
|
||||
DeviceId(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -871,6 +848,8 @@ impl Window {
|
||||
|
||||
pub fn set_transparent(&self, _transparent: bool) {}
|
||||
|
||||
pub fn set_blur(&self, _blur: bool) {}
|
||||
|
||||
pub fn set_visible(&self, _visibility: bool) {}
|
||||
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
@@ -960,13 +939,19 @@ impl Window {
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show_window_menu(&self, _position: Position) {}
|
||||
|
||||
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
#[cfg(feature = "rwh_04")]
|
||||
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
|
||||
use rwh_04::HasRawWindowHandle;
|
||||
|
||||
if let Some(native_window) = self.app.native_window().as_ref() {
|
||||
native_window.raw_window_handle()
|
||||
} else {
|
||||
@@ -974,8 +959,43 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
|
||||
#[cfg(feature = "rwh_05")]
|
||||
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
||||
use rwh_05::HasRawWindowHandle;
|
||||
|
||||
if let Some(native_window) = self.app.native_window().as_ref() {
|
||||
native_window.raw_window_handle()
|
||||
} else {
|
||||
panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
|
||||
rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
// Allow the usage of HasRawWindowHandle inside this function
|
||||
#[allow(deprecated)]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
use rwh_06::HasRawWindowHandle;
|
||||
|
||||
if let Some(native_window) = self.app.native_window().as_ref() {
|
||||
native_window.raw_window_handle()
|
||||
} else {
|
||||
log::error!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
|
||||
Err(rwh_06::HandleError::Unavailable)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
||||
Ok(rwh_06::RawDisplayHandle::Android(
|
||||
rwh_06::AndroidDisplayHandle::new(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn config(&self) -> ConfigurationRef {
|
||||
@@ -992,6 +1012,8 @@ impl Window {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_content_protected(&self, _protected: bool) {}
|
||||
|
||||
pub fn has_focus(&self) -> bool {
|
||||
*HAS_FOCUS.read().unwrap()
|
||||
}
|
||||
|
||||
@@ -16,19 +16,21 @@ use core_foundation::runloop::{
|
||||
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
|
||||
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
|
||||
};
|
||||
use icrate::Foundation::{CGRect, CGSize, NSInteger, NSOperatingSystemVersion, NSProcessInfo};
|
||||
use icrate::Foundation::{
|
||||
CGRect, CGSize, MainThreadMarker, NSInteger, NSOperatingSystemVersion, NSProcessInfo,
|
||||
};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{msg_send, sel};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::event_loop::{EventHandler, Never};
|
||||
use super::uikit::UIView;
|
||||
use super::view::WinitUIWindow;
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
dpi::PhysicalSize,
|
||||
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
|
||||
event_loop::ControlFlow,
|
||||
platform_impl::platform::event_loop::{EventHandler, EventProxy, EventWrapper, Never},
|
||||
window::WindowId as RootWindowId,
|
||||
};
|
||||
|
||||
@@ -44,6 +46,19 @@ macro_rules! bug_assert {
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventWrapper {
|
||||
StaticEvent(Event<Never>),
|
||||
ScaleFactorChanged(ScaleFactorChanged),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScaleFactorChanged {
|
||||
pub(super) window: Id<WinitUIWindow>,
|
||||
pub(super) suggested_size: PhysicalSize<u32>,
|
||||
pub(super) scale_factor: f64,
|
||||
}
|
||||
|
||||
enum UserCallbackTransitionResult<'a> {
|
||||
Success {
|
||||
event_handler: Box<dyn EventHandler>,
|
||||
@@ -57,7 +72,13 @@ enum UserCallbackTransitionResult<'a> {
|
||||
|
||||
impl Event<Never> {
|
||||
fn is_redraw(&self) -> bool {
|
||||
matches!(self, Event::RedrawRequested(_))
|
||||
matches!(
|
||||
self,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +121,7 @@ enum AppStateImpl {
|
||||
Terminated,
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
pub(crate) struct AppState {
|
||||
// This should never be `None`, except for briefly during a state transition.
|
||||
app_state: Option<AppStateImpl>,
|
||||
control_flow: ControlFlow,
|
||||
@@ -108,24 +129,18 @@ struct AppState {
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
// requires main thread
|
||||
unsafe fn get_mut() -> RefMut<'static, AppState> {
|
||||
pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> {
|
||||
// basically everything in UIKit requires the main thread, so it's pointless to use the
|
||||
// std::sync APIs.
|
||||
// must be mut because plain `static` requires `Sync`
|
||||
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
assert_main_thread!(
|
||||
"bug in winit: `AppState::get_mut()` can only be called on the main thread"
|
||||
);
|
||||
}
|
||||
let mut guard = APP_STATE.borrow_mut();
|
||||
let mut guard = unsafe { APP_STATE.borrow_mut() };
|
||||
if guard.is_none() {
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
unsafe fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
|
||||
let waker = EventLoopWaker::new(CFRunLoopGetMain());
|
||||
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
|
||||
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
|
||||
**guard = Some(AppState {
|
||||
app_state: Some(AppStateImpl::NotLaunched {
|
||||
queued_windows: Vec::new(),
|
||||
@@ -136,7 +151,7 @@ impl AppState {
|
||||
waker,
|
||||
});
|
||||
}
|
||||
init_guard(&mut guard)
|
||||
init_guard(&mut guard);
|
||||
}
|
||||
RefMut::map(guard, |state| state.as_mut().unwrap())
|
||||
}
|
||||
@@ -185,6 +200,10 @@ impl AppState {
|
||||
)
|
||||
}
|
||||
|
||||
fn has_terminated(&self) -> bool {
|
||||
matches!(self.state(), AppStateImpl::Terminated)
|
||||
}
|
||||
|
||||
fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
|
||||
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
|
||||
AppStateImpl::NotLaunched {
|
||||
@@ -219,7 +238,7 @@ impl AppState {
|
||||
};
|
||||
self.set_state(AppStateImpl::ProcessingEvents {
|
||||
event_handler,
|
||||
active_control_flow: ControlFlow::Poll,
|
||||
active_control_flow: self.control_flow,
|
||||
queued_gpu_redraws,
|
||||
});
|
||||
(windows, events)
|
||||
@@ -228,7 +247,7 @@ impl AppState {
|
||||
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() {
|
||||
if !self.has_launched() || self.has_terminated() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -275,7 +294,6 @@ impl AppState {
|
||||
};
|
||||
(waiting_event_handler, event)
|
||||
}
|
||||
(ControlFlow::ExitWithCode(_), _) => bug!("unexpected `ControlFlow` `Exit`"),
|
||||
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
|
||||
};
|
||||
|
||||
@@ -376,7 +394,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
fn events_cleared_transition(&mut self) {
|
||||
if !self.has_launched() {
|
||||
if !self.has_launched() || self.has_terminated() {
|
||||
return;
|
||||
}
|
||||
let (waiting_event_handler, old) = match self.take_state() {
|
||||
@@ -428,12 +446,6 @@ impl AppState {
|
||||
});
|
||||
self.waker.start()
|
||||
}
|
||||
(_, ControlFlow::ExitWithCode(_)) => {
|
||||
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
|
||||
// it is not possible to quit an iOS app gracefully and programatically
|
||||
warn!("`ControlFlow::Exit` ignored on iOS");
|
||||
self.control_flow = old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,12 +455,18 @@ impl AppState {
|
||||
s => bug!("`LoopExiting` happened while not processing events {:?}", s),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) {
|
||||
self.control_flow = control_flow;
|
||||
}
|
||||
|
||||
pub(crate) fn control_flow(&self) -> ControlFlow {
|
||||
self.control_flow
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread and window is a UIWindow
|
||||
// retains window
|
||||
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow>) {
|
||||
let mut this = AppState::get_mut();
|
||||
pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id<WinitUIWindow>) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
match this.state_mut() {
|
||||
&mut AppStateImpl::NotLaunched {
|
||||
ref mut queued_windows,
|
||||
@@ -468,10 +486,8 @@ pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow>) {
|
||||
window.makeKeyAndVisible();
|
||||
}
|
||||
|
||||
// requires main thread and window is a UIWindow
|
||||
// retains window
|
||||
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow>) {
|
||||
let mut this = AppState::get_mut();
|
||||
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUIWindow>) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
match this.state_mut() {
|
||||
&mut AppStateImpl::NotLaunched {
|
||||
ref mut queued_gpu_redraws,
|
||||
@@ -500,24 +516,17 @@ pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow>) {
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn will_launch(queued_event_handler: Box<dyn EventHandler>) {
|
||||
AppState::get_mut().will_launch_transition(queued_event_handler)
|
||||
pub fn will_launch(mtm: MainThreadMarker, queued_event_handler: Box<dyn EventHandler>) {
|
||||
AppState::get_mut(mtm).will_launch_transition(queued_event_handler)
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn did_finish_launching() {
|
||||
let mut this = AppState::get_mut();
|
||||
pub fn did_finish_launching(mtm: MainThreadMarker) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
let windows = match this.state_mut() {
|
||||
AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows),
|
||||
s => bug!("unexpected state {:?}", s),
|
||||
};
|
||||
|
||||
// start waking up the event loop now!
|
||||
bug_assert!(
|
||||
this.control_flow == ControlFlow::Poll,
|
||||
"unexpectedly not setup to `Poll` on launch!"
|
||||
);
|
||||
this.waker.start();
|
||||
|
||||
// have to drop RefMut because the window setup code below can trigger new events
|
||||
@@ -535,7 +544,7 @@ pub unsafe fn did_finish_launching() {
|
||||
// completed. This may result in incorrect visual appearance.
|
||||
// ```
|
||||
let screen = window.screen();
|
||||
let _: () = msg_send![&window, setScreen: ptr::null::<AnyObject>()];
|
||||
let _: () = unsafe { msg_send![&window, setScreen: ptr::null::<AnyObject>()] };
|
||||
window.setScreen(&screen);
|
||||
|
||||
let controller = window.rootViewController();
|
||||
@@ -545,13 +554,13 @@ pub unsafe fn did_finish_launching() {
|
||||
window.makeKeyAndVisible();
|
||||
}
|
||||
|
||||
let (windows, events) = AppState::get_mut().did_finish_launching_transition();
|
||||
let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition();
|
||||
|
||||
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(
|
||||
StartCause::Init,
|
||||
)))
|
||||
.chain(events);
|
||||
handle_nonuser_events(events);
|
||||
handle_nonuser_events(mtm, events);
|
||||
|
||||
// the above window dance hack, could possibly trigger new windows to be created.
|
||||
// we can just set those windows up normally, as they were created after didFinishLaunching
|
||||
@@ -560,27 +569,31 @@ pub unsafe fn did_finish_launching() {
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
// AppState::did_finish_launching handles the special transition `Init`
|
||||
pub unsafe fn handle_wakeup_transition() {
|
||||
let mut this = AppState::get_mut();
|
||||
pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
let wakeup_event = match this.wakeup_transition() {
|
||||
None => return,
|
||||
Some(wakeup_event) => wakeup_event,
|
||||
};
|
||||
drop(this);
|
||||
|
||||
handle_nonuser_event(wakeup_event)
|
||||
handle_nonuser_event(mtm, wakeup_event)
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub(crate) unsafe fn handle_nonuser_event(event: EventWrapper) {
|
||||
handle_nonuser_events(std::iter::once(event))
|
||||
pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) {
|
||||
handle_nonuser_events(mtm, std::iter::once(event))
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
|
||||
let mut this = AppState::get_mut();
|
||||
pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
|
||||
mtm: MainThreadMarker,
|
||||
events: I,
|
||||
) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
if this.has_terminated() {
|
||||
return;
|
||||
}
|
||||
|
||||
let (mut event_handler, active_control_flow, processing_redraws) =
|
||||
match this.try_user_callback_transition() {
|
||||
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
|
||||
@@ -593,7 +606,6 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
|
||||
processing_redraws,
|
||||
} => (event_handler, active_control_flow, processing_redraws),
|
||||
};
|
||||
let mut control_flow = this.control_flow;
|
||||
drop(this);
|
||||
|
||||
for wrapper in events {
|
||||
@@ -607,16 +619,16 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
|
||||
event
|
||||
);
|
||||
}
|
||||
event_handler.handle_nonuser_event(event, &mut control_flow)
|
||||
event_handler.handle_nonuser_event(event)
|
||||
}
|
||||
EventWrapper::EventProxy(proxy) => {
|
||||
handle_event_proxy(&mut event_handler, control_flow, proxy)
|
||||
EventWrapper::ScaleFactorChanged(event) => {
|
||||
handle_hidpi_proxy(&mut event_handler, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut this = AppState::get_mut();
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
let queued_events = match this.state_mut() {
|
||||
&mut AppStateImpl::InUserCallback {
|
||||
ref mut queued_events,
|
||||
@@ -648,7 +660,6 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
|
||||
active_control_flow,
|
||||
}
|
||||
});
|
||||
this.control_flow = control_flow;
|
||||
break;
|
||||
}
|
||||
drop(this);
|
||||
@@ -664,20 +675,18 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
|
||||
event
|
||||
);
|
||||
}
|
||||
event_handler.handle_nonuser_event(event, &mut control_flow)
|
||||
event_handler.handle_nonuser_event(event)
|
||||
}
|
||||
EventWrapper::EventProxy(proxy) => {
|
||||
handle_event_proxy(&mut event_handler, control_flow, proxy)
|
||||
EventWrapper::ScaleFactorChanged(event) => {
|
||||
handle_hidpi_proxy(&mut event_handler, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn handle_user_events() {
|
||||
let mut this = AppState::get_mut();
|
||||
let mut control_flow = this.control_flow;
|
||||
fn handle_user_events(mtm: MainThreadMarker) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
let (mut event_handler, active_control_flow, processing_redraws) =
|
||||
match this.try_user_callback_transition() {
|
||||
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
|
||||
@@ -694,10 +703,10 @@ unsafe fn handle_user_events() {
|
||||
}
|
||||
drop(this);
|
||||
|
||||
event_handler.handle_user_events(&mut control_flow);
|
||||
event_handler.handle_user_events();
|
||||
|
||||
loop {
|
||||
let mut this = AppState::get_mut();
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
let queued_events = match this.state_mut() {
|
||||
&mut AppStateImpl::InUserCallback {
|
||||
ref mut queued_events,
|
||||
@@ -718,29 +727,25 @@ unsafe fn handle_user_events() {
|
||||
queued_gpu_redraws,
|
||||
active_control_flow,
|
||||
});
|
||||
this.control_flow = control_flow;
|
||||
break;
|
||||
}
|
||||
drop(this);
|
||||
|
||||
for wrapper in queued_events {
|
||||
match wrapper {
|
||||
EventWrapper::StaticEvent(event) => {
|
||||
event_handler.handle_nonuser_event(event, &mut control_flow)
|
||||
}
|
||||
EventWrapper::EventProxy(proxy) => {
|
||||
handle_event_proxy(&mut event_handler, control_flow, proxy)
|
||||
EventWrapper::StaticEvent(event) => event_handler.handle_nonuser_event(event),
|
||||
EventWrapper::ScaleFactorChanged(event) => {
|
||||
handle_hidpi_proxy(&mut event_handler, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
event_handler.handle_user_events(&mut control_flow);
|
||||
event_handler.handle_user_events();
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn handle_main_events_cleared() {
|
||||
let mut this = AppState::get_mut();
|
||||
if !this.has_launched() {
|
||||
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() {
|
||||
@@ -749,63 +754,44 @@ pub unsafe fn handle_main_events_cleared() {
|
||||
};
|
||||
drop(this);
|
||||
|
||||
handle_user_events();
|
||||
handle_user_events(mtm);
|
||||
|
||||
let mut this = AppState::get_mut();
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
let redraw_events: Vec<EventWrapper> = this
|
||||
.main_events_cleared_transition()
|
||||
.into_iter()
|
||||
.map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.id()))))
|
||||
.map(|window| {
|
||||
EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
drop(this);
|
||||
|
||||
handle_nonuser_events(redraw_events);
|
||||
handle_nonuser_event(EventWrapper::StaticEvent(Event::AboutToWait));
|
||||
handle_nonuser_events(mtm, redraw_events);
|
||||
handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait));
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn handle_events_cleared() {
|
||||
AppState::get_mut().events_cleared_transition();
|
||||
pub fn handle_events_cleared(mtm: MainThreadMarker) {
|
||||
AppState::get_mut(mtm).events_cleared_transition();
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn terminated() {
|
||||
let mut this = AppState::get_mut();
|
||||
pub fn terminated(mtm: MainThreadMarker) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
let mut event_handler = this.terminated_transition();
|
||||
let mut control_flow = this.control_flow;
|
||||
drop(this);
|
||||
|
||||
event_handler.handle_nonuser_event(Event::LoopExiting, &mut control_flow)
|
||||
event_handler.handle_nonuser_event(Event::LoopExiting)
|
||||
}
|
||||
|
||||
fn handle_event_proxy(
|
||||
event_handler: &mut Box<dyn EventHandler>,
|
||||
control_flow: ControlFlow,
|
||||
proxy: EventProxy,
|
||||
) {
|
||||
match proxy {
|
||||
EventProxy::DpiChangedProxy {
|
||||
suggested_size,
|
||||
scale_factor,
|
||||
window,
|
||||
} => handle_hidpi_proxy(
|
||||
event_handler,
|
||||
control_flow,
|
||||
suggested_size,
|
||||
scale_factor,
|
||||
window,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_hidpi_proxy(
|
||||
event_handler: &mut Box<dyn EventHandler>,
|
||||
mut control_flow: ControlFlow,
|
||||
suggested_size: LogicalSize<f64>,
|
||||
scale_factor: f64,
|
||||
window: Id<WinitUIWindow>,
|
||||
) {
|
||||
let new_inner_size = Arc::new(Mutex::new(suggested_size.to_physical(scale_factor)));
|
||||
fn handle_hidpi_proxy(event_handler: &mut Box<dyn EventHandler>, event: ScaleFactorChanged) {
|
||||
let ScaleFactorChanged {
|
||||
suggested_size,
|
||||
scale_factor,
|
||||
window,
|
||||
} = event;
|
||||
let new_inner_size = Arc::new(Mutex::new(suggested_size));
|
||||
let event = Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
@@ -813,7 +799,7 @@ fn handle_hidpi_proxy(
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
|
||||
},
|
||||
};
|
||||
event_handler.handle_nonuser_event(event, &mut control_flow);
|
||||
event_handler.handle_nonuser_event(event);
|
||||
let (view, screen_frame) = get_view_and_screen_frame(&window);
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
|
||||
@@ -15,61 +15,81 @@ use core_foundation::runloop::{
|
||||
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
use icrate::Foundation::{MainThreadMarker, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::ClassType;
|
||||
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
error::EventLoopError,
|
||||
event::Event,
|
||||
event_loop::{
|
||||
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
|
||||
ControlFlow, DeviceEvents, EventLoopClosed,
|
||||
EventLoopWindowTarget as RootEventLoopWindowTarget,
|
||||
},
|
||||
platform::ios::Idiom,
|
||||
};
|
||||
|
||||
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen};
|
||||
use super::view::WinitUIWindow;
|
||||
use super::{app_state, monitor, view, MonitorHandle};
|
||||
use super::{
|
||||
app_state::AppState,
|
||||
uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum EventWrapper {
|
||||
StaticEvent(Event<Never>),
|
||||
EventProxy(EventProxy),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum EventProxy {
|
||||
DpiChangedProxy {
|
||||
window: Id<WinitUIWindow>,
|
||||
suggested_size: LogicalSize<f64>,
|
||||
scale_factor: f64,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
receiver: Receiver<T>,
|
||||
sender_to_clone: Sender<T>,
|
||||
pub(super) mtm: MainThreadMarker,
|
||||
p: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
monitor::uiscreens(MainThreadMarker::new().unwrap())
|
||||
monitor::uiscreens(self.mtm)
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
Some(MonitorHandle::new(UIScreen::main(
|
||||
MainThreadMarker::new().unwrap(),
|
||||
)))
|
||||
Some(MonitorHandle::new(UIScreen::main(self.mtm)))
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
|
||||
#[inline]
|
||||
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
|
||||
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
||||
Ok(rwh_06::RawDisplayHandle::UiKit(
|
||||
rwh_06::UiKitDisplayHandle::new(),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||
AppState::get_mut(self.mtm).set_control_flow(control_flow)
|
||||
}
|
||||
|
||||
pub(crate) fn control_flow(&self) -> ControlFlow {
|
||||
AppState::get_mut(self.mtm).control_flow()
|
||||
}
|
||||
|
||||
pub(crate) fn exit(&self) {
|
||||
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
|
||||
// it is not possible to quit an iOS app gracefully and programatically
|
||||
warn!("`ControlFlow::Exit` ignored on iOS");
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
mtm: MainThreadMarker,
|
||||
sender: Sender<T>,
|
||||
receiver: Receiver<T>,
|
||||
window_target: RootEventLoopWindowTarget<T>,
|
||||
}
|
||||
|
||||
@@ -80,7 +100,8 @@ impl<T: 'static> EventLoop<T> {
|
||||
pub(crate) fn new(
|
||||
_: &PlatformSpecificEventLoopAttributes,
|
||||
) -> Result<EventLoop<T>, EventLoopError> {
|
||||
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
|
||||
let mtm = MainThreadMarker::new()
|
||||
.expect("On iOS, `EventLoop` must be created on the main thread");
|
||||
|
||||
static mut SINGLETON_INIT: bool = false;
|
||||
unsafe {
|
||||
@@ -92,16 +113,19 @@ impl<T: 'static> EventLoop<T> {
|
||||
SINGLETON_INIT = true;
|
||||
}
|
||||
|
||||
let (sender_to_clone, receiver) = mpsc::channel();
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
|
||||
// this line sets up the main run loop before `UIApplicationMain`
|
||||
setup_control_flow_observers();
|
||||
|
||||
Ok(EventLoop {
|
||||
mtm,
|
||||
sender,
|
||||
receiver,
|
||||
window_target: RootEventLoopWindowTarget {
|
||||
p: EventLoopWindowTarget {
|
||||
receiver,
|
||||
sender_to_clone,
|
||||
mtm,
|
||||
p: PhantomData,
|
||||
},
|
||||
_marker: PhantomData,
|
||||
},
|
||||
@@ -110,10 +134,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
pub fn run<F>(self, event_handler: F) -> !
|
||||
where
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
|
||||
{
|
||||
unsafe {
|
||||
let application = UIApplication::shared(MainThreadMarker::new().unwrap());
|
||||
let application = UIApplication::shared(self.mtm);
|
||||
assert!(
|
||||
application.is_none(),
|
||||
"\
|
||||
@@ -122,16 +146,17 @@ impl<T: 'static> EventLoop<T> {
|
||||
);
|
||||
|
||||
let event_handler = std::mem::transmute::<
|
||||
Box<dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow)>,
|
||||
Box<dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>)>,
|
||||
Box<EventHandlerCallback<T>>,
|
||||
>(Box::new(event_handler));
|
||||
|
||||
let handler = EventLoopHandler {
|
||||
f: event_handler,
|
||||
receiver: self.receiver,
|
||||
event_loop: self.window_target,
|
||||
};
|
||||
|
||||
app_state::will_launch(Box::new(handler));
|
||||
app_state::will_launch(self.mtm, Box::new(handler));
|
||||
|
||||
// Ensure application delegate is initialized
|
||||
view::WinitApplicationDelegate::class();
|
||||
@@ -147,7 +172,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
|
||||
EventLoopProxy::new(self.sender.clone())
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
|
||||
@@ -158,9 +183,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// EventLoopExtIOS
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn idiom(&self) -> Idiom {
|
||||
UIDevice::current(MainThreadMarker::new().unwrap())
|
||||
.userInterfaceIdiom()
|
||||
.into()
|
||||
UIDevice::current(self.mtm).userInterfaceIdiom().into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,12 +261,11 @@ fn setup_control_flow_observers() {
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,13 +285,12 @@ fn setup_control_flow_observers() {
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(),
|
||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
|
||||
kCFRunLoopExit => {} // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,13 +300,12 @@ fn setup_control_flow_observers() {
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
|
||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
|
||||
kCFRunLoopExit => {} // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,16 +346,16 @@ fn setup_control_flow_observers() {
|
||||
#[derive(Debug)]
|
||||
pub enum Never {}
|
||||
|
||||
type EventHandlerCallback<T> =
|
||||
dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static;
|
||||
type EventHandlerCallback<T> = dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>) + 'static;
|
||||
|
||||
pub trait EventHandler: Debug {
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
|
||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>);
|
||||
fn handle_user_events(&mut self);
|
||||
}
|
||||
|
||||
struct EventLoopHandler<T: 'static> {
|
||||
f: Box<EventHandlerCallback<T>>,
|
||||
receiver: Receiver<T>,
|
||||
event_loop: RootEventLoopWindowTarget<T>,
|
||||
}
|
||||
|
||||
@@ -348,17 +368,13 @@ impl<T: 'static> Debug for EventLoopHandler<T> {
|
||||
}
|
||||
|
||||
impl<T: 'static> EventHandler for EventLoopHandler<T> {
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
|
||||
(self.f)(
|
||||
event.map_nonuser_event().unwrap(),
|
||||
&self.event_loop,
|
||||
control_flow,
|
||||
);
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>) {
|
||||
(self.f)(event.map_nonuser_event().unwrap(), &self.event_loop);
|
||||
}
|
||||
|
||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
|
||||
for event in self.event_loop.p.receiver.try_iter() {
|
||||
(self.f)(Event::UserEvent(event), &self.event_loop, control_flow);
|
||||
fn handle_user_events(&mut self) {
|
||||
for event in self.receiver.try_iter() {
|
||||
(self.f)(Event::UserEvent(event), &self.event_loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use icrate::Foundation::{NSInteger, NSUInteger};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
|
||||
|
||||
@@ -58,17 +58,6 @@
|
||||
#![cfg(ios_platform)]
|
||||
#![allow(clippy::let_unit_value)]
|
||||
|
||||
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
|
||||
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
|
||||
// window size/position.
|
||||
macro_rules! assert_main_thread {
|
||||
($($t:tt)*) => {
|
||||
if !::icrate::Foundation::is_main_thread() {
|
||||
panic!($($t)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod app_state;
|
||||
mod event_loop;
|
||||
mod ffi;
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
use std::{
|
||||
collections::{BTreeSet, VecDeque},
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
fmt, hash, ptr,
|
||||
};
|
||||
|
||||
use icrate::Foundation::{MainThreadMarker, NSInteger};
|
||||
use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger};
|
||||
use objc2::mutability::IsRetainable;
|
||||
use objc2::rc::Id;
|
||||
|
||||
use super::uikit::{UIScreen, UIScreenMode};
|
||||
@@ -16,32 +16,59 @@ use crate::{
|
||||
platform_impl::platform::app_state,
|
||||
};
|
||||
|
||||
// TODO(madsmtm): Remove or refactor this
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode>);
|
||||
// Workaround for `MainThreadBound` implementing almost no traits
|
||||
#[derive(Debug)]
|
||||
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
|
||||
|
||||
unsafe impl Send for ScreenModeSendSync {}
|
||||
unsafe impl Sync for ScreenModeSendSync {}
|
||||
impl<T: IsRetainable> Clone for MainThreadBoundDelegateImpls<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
// SAFETY: Marker only used to get the pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
Id::as_ptr(self.0.get(mtm)).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// SAFETY: Marker only used to get the pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
Id::as_ptr(self.0.get(mtm)) == Id::as_ptr(other.0.get(mtm))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable> Eq for MainThreadBoundDelegateImpls<T> {}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: (u32, u32),
|
||||
pub(crate) bit_depth: u16,
|
||||
pub(crate) refresh_rate_millihertz: u32,
|
||||
pub(crate) screen_mode: ScreenModeSendSync,
|
||||
screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
fn new(uiscreen: Id<UIScreen>, screen_mode: Id<UIScreenMode>) -> VideoMode {
|
||||
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
|
||||
fn new(
|
||||
uiscreen: Id<UIScreen>,
|
||||
screen_mode: Id<UIScreenMode>,
|
||||
mtm: MainThreadMarker,
|
||||
) -> VideoMode {
|
||||
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
|
||||
let size = screen_mode.size();
|
||||
VideoMode {
|
||||
size: (size.width as u32, size.height as u32),
|
||||
bit_depth: 32,
|
||||
refresh_rate_millihertz,
|
||||
screen_mode: ScreenModeSendSync(screen_mode),
|
||||
screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)),
|
||||
monitor: MonitorHandle::new(uiscreen),
|
||||
}
|
||||
}
|
||||
@@ -61,18 +88,40 @@ impl VideoMode {
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.monitor.clone()
|
||||
}
|
||||
|
||||
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Id<UIScreenMode> {
|
||||
self.screen_mode.0.get(mtm)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Inner {
|
||||
uiscreen: Id<UIScreen>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct MonitorHandle {
|
||||
inner: Inner,
|
||||
ui_screen: MainThreadBound<Id<UIScreen>>,
|
||||
}
|
||||
|
||||
impl Clone for MonitorHandle {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
ui_screen: self
|
||||
.ui_screen
|
||||
.get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl hash::Hash for MonitorHandle {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
(self as *const Self).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
ptr::eq(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MonitorHandle {}
|
||||
|
||||
impl PartialOrd for MonitorHandle {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
@@ -86,31 +135,6 @@ impl Ord for MonitorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for MonitorHandle {
|
||||
type Target = Inner;
|
||||
|
||||
fn deref(&self) -> &Inner {
|
||||
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for MonitorHandle {
|
||||
fn deref_mut(&mut self) -> &mut Inner {
|
||||
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for MonitorHandle {}
|
||||
unsafe impl Sync for MonitorHandle {}
|
||||
|
||||
impl Drop for MonitorHandle {
|
||||
fn drop(&mut self) {
|
||||
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// TODO: Do this using the proper fmt API
|
||||
@@ -135,59 +159,80 @@ impl fmt::Debug for MonitorHandle {
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
pub(crate) fn new(uiscreen: Id<UIScreen>) -> Self {
|
||||
assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS");
|
||||
pub(crate) fn new(ui_screen: Id<UIScreen>) -> Self {
|
||||
// Holding `Id<UIScreen>` implies we're on the main thread.
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
Self {
|
||||
inner: Inner { uiscreen },
|
||||
ui_screen: MainThreadBound::new(ui_screen, mtm),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
let main = UIScreen::main(MainThreadMarker::new().unwrap());
|
||||
if self.uiscreen == main {
|
||||
Some("Primary".to_string())
|
||||
} else if self.uiscreen == main.mirroredScreen() {
|
||||
Some("Mirrored".to_string())
|
||||
} else {
|
||||
UIScreen::screens(MainThreadMarker::new().unwrap())
|
||||
.iter()
|
||||
.position(|rhs| rhs == &*self.uiscreen)
|
||||
.map(|idx| idx.to_string())
|
||||
}
|
||||
self.ui_screen.get_on_main(|ui_screen, mtm| {
|
||||
let main = UIScreen::main(mtm);
|
||||
if *ui_screen == main {
|
||||
Some("Primary".to_string())
|
||||
} else if *ui_screen == main.mirroredScreen() {
|
||||
Some("Mirrored".to_string())
|
||||
} else {
|
||||
UIScreen::screens(mtm)
|
||||
.iter()
|
||||
.position(|rhs| rhs == &**ui_screen)
|
||||
.map(|idx| idx.to_string())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
let bounds = self.uiscreen.nativeBounds();
|
||||
let bounds = self
|
||||
.ui_screen
|
||||
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
|
||||
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
|
||||
}
|
||||
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
let bounds = self.uiscreen.nativeBounds();
|
||||
let bounds = self
|
||||
.ui_screen
|
||||
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
|
||||
(bounds.origin.x as f64, bounds.origin.y as f64).into()
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.uiscreen.nativeScale() as f64
|
||||
self.ui_screen
|
||||
.get_on_main(|ui_screen, _| ui_screen.nativeScale()) as f64
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
Some(refresh_rate_millihertz(&self.uiscreen))
|
||||
Some(
|
||||
self.ui_screen
|
||||
.get_on_main(|ui_screen, _| refresh_rate_millihertz(ui_screen)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
// Use Ord impl of RootVideoMode
|
||||
let modes: BTreeSet<_> = self
|
||||
.uiscreen
|
||||
.availableModes()
|
||||
.into_iter()
|
||||
.map(|mode| RootVideoMode {
|
||||
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
|
||||
})
|
||||
.collect();
|
||||
self.ui_screen.get_on_main(|ui_screen, mtm| {
|
||||
// Use Ord impl of RootVideoMode
|
||||
|
||||
modes.into_iter().map(|mode| mode.video_mode)
|
||||
let modes: BTreeSet<_> = ui_screen
|
||||
.availableModes()
|
||||
.into_iter()
|
||||
.map(|mode| RootVideoMode {
|
||||
video_mode: VideoMode::new(ui_screen.clone(), mode, mtm),
|
||||
})
|
||||
.collect();
|
||||
|
||||
modes.into_iter().map(|mode| mode.video_mode)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Id<UIScreen> {
|
||||
self.ui_screen.get(mtm)
|
||||
}
|
||||
|
||||
pub fn preferred_video_mode(&self) -> VideoMode {
|
||||
self.ui_screen.get_on_main(|ui_screen, mtm| {
|
||||
VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,20 +260,6 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
|
||||
refresh_rate_millihertz as u32 * 1000
|
||||
}
|
||||
|
||||
// MonitorHandleExtIOS
|
||||
impl Inner {
|
||||
pub(crate) fn ui_screen(&self) -> &Id<UIScreen> {
|
||||
&self.uiscreen
|
||||
}
|
||||
|
||||
pub fn preferred_video_mode(&self) -> VideoMode {
|
||||
VideoMode::new(
|
||||
self.uiscreen.clone(),
|
||||
self.uiscreen.preferredMode().unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
|
||||
UIScreen::screens(mtm)
|
||||
.into_iter()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
@@ -13,6 +12,7 @@ mod event;
|
||||
mod responder;
|
||||
mod screen;
|
||||
mod screen_mode;
|
||||
mod status_bar_style;
|
||||
mod touch;
|
||||
mod trait_collection;
|
||||
mod view;
|
||||
@@ -26,6 +26,7 @@ pub(crate) use self::event::UIEvent;
|
||||
pub(crate) use self::responder::UIResponder;
|
||||
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
|
||||
pub(crate) use self::screen_mode::UIScreenMode;
|
||||
pub(crate) use self::status_bar_style::UIStatusBarStyle;
|
||||
pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType};
|
||||
pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection};
|
||||
#[allow(unused_imports)]
|
||||
|
||||
27
src/platform_impl/ios/uikit/status_bar_style.rs
Normal file
27
src/platform_impl/ios/uikit/status_bar_style.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::platform::ios::StatusBarStyle;
|
||||
use icrate::Foundation::NSInteger;
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UIStatusBarStyle {
|
||||
#[default]
|
||||
Default = 0,
|
||||
LightContent = 1,
|
||||
DarkContent = 3,
|
||||
}
|
||||
|
||||
impl From<StatusBarStyle> for UIStatusBarStyle {
|
||||
fn from(value: StatusBarStyle) -> Self {
|
||||
match value {
|
||||
StatusBarStyle::Default => Self::Default,
|
||||
StatusBarStyle::LightContent => Self::LightContent,
|
||||
StatusBarStyle::DarkContent => Self::DarkContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for UIStatusBarStyle {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
@@ -8,10 +8,11 @@ use objc2::rc::Id;
|
||||
use objc2::runtime::AnyClass;
|
||||
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::app_state::{self, EventWrapper};
|
||||
use super::uikit::{
|
||||
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask,
|
||||
UIResponder, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView, UIViewController,
|
||||
UIWindow,
|
||||
UIResponder, UIStatusBarStyle, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView,
|
||||
UIViewController, UIWindow,
|
||||
};
|
||||
use super::window::WindowId;
|
||||
use crate::{
|
||||
@@ -19,8 +20,6 @@ use crate::{
|
||||
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
|
||||
platform::ios::ValidOrientations,
|
||||
platform_impl::platform::{
|
||||
app_state,
|
||||
event_loop::{EventProxy, EventWrapper},
|
||||
ffi::{UIRectEdge, UIUserInterfaceIdiom},
|
||||
window::PlatformSpecificWindowBuilderAttributes,
|
||||
DeviceId, Fullscreen,
|
||||
@@ -41,17 +40,21 @@ declare_class!(
|
||||
unsafe impl WinitView {
|
||||
#[method(drawRect:)]
|
||||
fn draw_rect(&self, rect: CGRect) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let window = self.window().unwrap();
|
||||
unsafe {
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(
|
||||
RootWindowId(window.id()),
|
||||
)));
|
||||
}
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
}),
|
||||
);
|
||||
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
|
||||
}
|
||||
|
||||
#[method(layoutSubviews)]
|
||||
fn layout_subviews(&self) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
|
||||
|
||||
let window = self.window().unwrap();
|
||||
@@ -74,16 +77,18 @@ declare_class!(
|
||||
self.setFrame(window_bounds);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Resized(size),
|
||||
}));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[method(setContentScaleFactor:)]
|
||||
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let _: () =
|
||||
unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] };
|
||||
|
||||
@@ -111,25 +116,26 @@ declare_class!(
|
||||
let screen_space = screen.coordinateSpace();
|
||||
let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space);
|
||||
let size = crate::dpi::LogicalSize {
|
||||
width: screen_frame.size.width as _,
|
||||
height: screen_frame.size.height as _,
|
||||
width: screen_frame.size.width as f64,
|
||||
height: screen_frame.size.height as f64,
|
||||
};
|
||||
let window_id = RootWindowId(window.id());
|
||||
unsafe {
|
||||
app_state::handle_nonuser_events(
|
||||
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
|
||||
app_state::handle_nonuser_events(
|
||||
mtm,
|
||||
std::iter::once(EventWrapper::ScaleFactorChanged(
|
||||
app_state::ScaleFactorChanged {
|
||||
window,
|
||||
scale_factor,
|
||||
suggested_size: size,
|
||||
}))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
||||
},
|
||||
))),
|
||||
);
|
||||
}
|
||||
suggested_size: size.to_physical(scale_factor),
|
||||
},
|
||||
))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
||||
},
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
#[method(touchesBegan:withEvent:)]
|
||||
@@ -254,14 +260,14 @@ impl WinitView {
|
||||
}),
|
||||
}));
|
||||
}
|
||||
unsafe {
|
||||
app_state::handle_nonuser_events(touch_events);
|
||||
}
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_events(mtm, touch_events);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ViewControllerState {
|
||||
prefers_status_bar_hidden: Cell<bool>,
|
||||
preferred_status_bar_style: Cell<UIStatusBarStyle>,
|
||||
prefers_home_indicator_auto_hidden: Cell<bool>,
|
||||
supported_orientations: Cell<UIInterfaceOrientationMask>,
|
||||
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
|
||||
@@ -292,6 +298,7 @@ declare_class!(
|
||||
&mut this.state,
|
||||
Box::new(ViewControllerState {
|
||||
prefers_status_bar_hidden: Cell::new(false),
|
||||
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
|
||||
prefers_home_indicator_auto_hidden: Cell::new(false),
|
||||
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
|
||||
preferred_screen_edges_deferring_system_gestures: Cell::new(
|
||||
@@ -315,6 +322,11 @@ declare_class!(
|
||||
self.state.prefers_status_bar_hidden.get()
|
||||
}
|
||||
|
||||
#[method(preferredStatusBarStyle)]
|
||||
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
|
||||
self.state.preferred_status_bar_style.get()
|
||||
}
|
||||
|
||||
#[method(prefersHomeIndicatorAutoHidden)]
|
||||
fn prefers_home_indicator_auto_hidden(&self) -> bool {
|
||||
self.state.prefers_home_indicator_auto_hidden.get()
|
||||
@@ -340,6 +352,11 @@ impl WinitViewController {
|
||||
self.setNeedsStatusBarAppearanceUpdate();
|
||||
}
|
||||
|
||||
pub(crate) fn set_preferred_status_bar_style(&self, val: UIStatusBarStyle) {
|
||||
self.state.preferred_status_bar_style.set(val);
|
||||
self.setNeedsStatusBarAppearanceUpdate();
|
||||
}
|
||||
|
||||
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
|
||||
self.state.prefers_home_indicator_auto_hidden.set(val);
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
@@ -398,6 +415,8 @@ impl WinitViewController {
|
||||
|
||||
this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
|
||||
|
||||
this.set_preferred_status_bar_style(platform_attributes.preferred_status_bar_style.into());
|
||||
|
||||
this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations);
|
||||
|
||||
this.set_prefers_home_indicator_auto_hidden(
|
||||
@@ -430,23 +449,27 @@ declare_class!(
|
||||
unsafe impl WinitUIWindow {
|
||||
#[method(becomeKeyWindow)]
|
||||
fn become_key_window(&self) {
|
||||
unsafe {
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(self.id()),
|
||||
event: WindowEvent::Focused(true),
|
||||
}));
|
||||
}
|
||||
}),
|
||||
);
|
||||
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
|
||||
}
|
||||
|
||||
#[method(resignKeyWindow)]
|
||||
fn resign_key_window(&self) {
|
||||
unsafe {
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(self.id()),
|
||||
event: WindowEvent::Focused(false),
|
||||
}));
|
||||
}
|
||||
}),
|
||||
);
|
||||
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
|
||||
}
|
||||
}
|
||||
@@ -454,7 +477,7 @@ declare_class!(
|
||||
|
||||
impl WinitUIWindow {
|
||||
pub(crate) fn new(
|
||||
_mtm: MainThreadMarker,
|
||||
mtm: MainThreadMarker,
|
||||
window_attributes: &WindowAttributes,
|
||||
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
@@ -464,15 +487,15 @@ impl WinitUIWindow {
|
||||
|
||||
this.setRootViewController(Some(view_controller));
|
||||
|
||||
match window_attributes.fullscreen.clone().map(Into::into) {
|
||||
match window_attributes.fullscreen.0.clone().map(Into::into) {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
let monitor = video_mode.monitor();
|
||||
let screen = monitor.ui_screen();
|
||||
screen.setCurrentMode(Some(&video_mode.screen_mode.0));
|
||||
let screen = monitor.ui_screen(mtm);
|
||||
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
||||
this.setScreen(screen);
|
||||
}
|
||||
Some(Fullscreen::Borderless(Some(ref monitor))) => {
|
||||
let screen = monitor.ui_screen();
|
||||
let screen = monitor.ui_screen(mtm);
|
||||
this.setScreen(screen);
|
||||
}
|
||||
_ => (),
|
||||
@@ -499,27 +522,31 @@ declare_class!(
|
||||
unsafe impl WinitApplicationDelegate {
|
||||
#[method(application:didFinishLaunchingWithOptions:)]
|
||||
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
|
||||
unsafe {
|
||||
app_state::did_finish_launching();
|
||||
}
|
||||
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
|
||||
true
|
||||
}
|
||||
|
||||
#[method(applicationDidBecomeActive:)]
|
||||
fn did_become_active(&self, _application: &UIApplication) {
|
||||
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
|
||||
}
|
||||
|
||||
#[method(applicationWillResignActive:)]
|
||||
fn will_resign_active(&self, _application: &UIApplication) {
|
||||
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
|
||||
}
|
||||
|
||||
#[method(applicationWillEnterForeground:)]
|
||||
fn will_enter_foreground(&self, _application: &UIApplication) {}
|
||||
fn will_enter_foreground(&self, application: &UIApplication) {
|
||||
self.send_occluded_event_for_all_windows(application, false);
|
||||
}
|
||||
|
||||
#[method(applicationDidEnterBackground:)]
|
||||
fn did_enter_background(&self, _application: &UIApplication) {}
|
||||
fn did_enter_background(&self, application: &UIApplication) {
|
||||
self.send_occluded_event_for_all_windows(application, true);
|
||||
}
|
||||
|
||||
#[method(applicationWillTerminate:)]
|
||||
fn will_terminate(&self, application: &UIApplication) {
|
||||
@@ -538,10 +565,37 @@ declare_class!(
|
||||
}));
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
app_state::handle_nonuser_events(events);
|
||||
app_state::terminated();
|
||||
}
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_events(mtm, events);
|
||||
app_state::terminated(mtm);
|
||||
}
|
||||
|
||||
#[method(applicationDidReceiveMemoryWarning:)]
|
||||
fn did_receive_memory_warning(&self, _application: &UIApplication) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning))
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitApplicationDelegate {
|
||||
fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) {
|
||||
let mut events = Vec::new();
|
||||
for window in application.windows().iter() {
|
||||
if window.is_kind_of::<WinitUIWindow>() {
|
||||
// SAFETY: We just checked that the window is a `winit` window
|
||||
let window = unsafe {
|
||||
let ptr: *const UIWindow = window;
|
||||
let ptr: *const WinitUIWindow = ptr.cast();
|
||||
&*ptr
|
||||
};
|
||||
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Occluded(occluded),
|
||||
}));
|
||||
}
|
||||
}
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_events(mtm, events);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, Main
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{class, msg_send};
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
|
||||
|
||||
use super::app_state::EventWrapper;
|
||||
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
|
||||
use super::view::{WinitUIWindow, WinitView, WinitViewController};
|
||||
use crate::{
|
||||
@@ -15,11 +15,9 @@ use crate::{
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::{Event, WindowEvent},
|
||||
icon::Icon,
|
||||
platform::ios::{ScreenEdge, ValidOrientations},
|
||||
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
|
||||
platform_impl::platform::{
|
||||
app_state,
|
||||
event_loop::{EventProxy, EventWrapper},
|
||||
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
|
||||
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
|
||||
},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
@@ -43,6 +41,10 @@ impl Inner {
|
||||
debug!("`Window::set_transparent` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_blur(&self, _blur: bool) {
|
||||
debug!("`Window::set_blur` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
self.window.setHidden(!visible)
|
||||
}
|
||||
@@ -53,20 +55,19 @@ impl Inner {
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
unsafe {
|
||||
if self.gl_or_metal_backed {
|
||||
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
|
||||
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
|
||||
// raw or gl/metal for drawing this work is completely avoided.
|
||||
//
|
||||
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
|
||||
// testing.
|
||||
//
|
||||
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
|
||||
app_state::queue_gl_or_metal_redraw(self.window.clone());
|
||||
} else {
|
||||
self.view.setNeedsDisplay();
|
||||
}
|
||||
if self.gl_or_metal_backed {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
|
||||
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
|
||||
// raw or gl/metal for drawing this work is completely avoided.
|
||||
//
|
||||
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
|
||||
// testing.
|
||||
//
|
||||
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
|
||||
app_state::queue_gl_or_metal_redraw(mtm, self.window.clone());
|
||||
} else {
|
||||
self.view.setNeedsDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +197,9 @@ impl Inner {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show_window_menu(&self, _position: Position) {}
|
||||
|
||||
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
@@ -219,14 +223,17 @@ impl Inner {
|
||||
}
|
||||
|
||||
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let uiscreen = match &monitor {
|
||||
Some(Fullscreen::Exclusive(video_mode)) => {
|
||||
let uiscreen = video_mode.monitor.ui_screen();
|
||||
uiscreen.setCurrentMode(Some(&video_mode.screen_mode.0));
|
||||
let uiscreen = video_mode.monitor.ui_screen(mtm);
|
||||
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
||||
uiscreen.clone()
|
||||
}
|
||||
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen().clone(),
|
||||
Some(Fullscreen::Borderless(None)) => self.current_monitor_inner().ui_screen().clone(),
|
||||
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(),
|
||||
Some(Fullscreen::Borderless(None)) => {
|
||||
self.current_monitor_inner().ui_screen(mtm).clone()
|
||||
}
|
||||
None => {
|
||||
warn!("`Window::set_fullscreen(None)` ignored on iOS");
|
||||
return;
|
||||
@@ -249,8 +256,9 @@ impl Inner {
|
||||
}
|
||||
|
||||
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let monitor = self.current_monitor_inner();
|
||||
let uiscreen = monitor.ui_screen();
|
||||
let uiscreen = monitor.ui_screen(mtm);
|
||||
let screen_space_bounds = self.screen_frame();
|
||||
let screen_bounds = uiscreen.bounds();
|
||||
|
||||
@@ -323,16 +331,47 @@ impl Inner {
|
||||
self.window.id()
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let mut window_handle = UiKitWindowHandle::empty();
|
||||
#[cfg(feature = "rwh_04")]
|
||||
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
|
||||
let mut window_handle = rwh_04::UiKitHandle::empty();
|
||||
window_handle.ui_window = Id::as_ptr(&self.window) as _;
|
||||
window_handle.ui_view = Id::as_ptr(&self.view) as _;
|
||||
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
|
||||
RawWindowHandle::UiKit(window_handle)
|
||||
rwh_04::RawWindowHandle::UiKit(window_handle)
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
|
||||
#[cfg(feature = "rwh_05")]
|
||||
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
||||
let mut window_handle = rwh_05::UiKitWindowHandle::empty();
|
||||
window_handle.ui_window = Id::as_ptr(&self.window) as _;
|
||||
window_handle.ui_view = Id::as_ptr(&self.view) as _;
|
||||
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
|
||||
rwh_05::RawWindowHandle::UiKit(window_handle)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
|
||||
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
let mut window_handle = rwh_06::UiKitWindowHandle::new({
|
||||
let ui_view = Id::as_ptr(&self.view) as _;
|
||||
std::ptr::NonNull::new(ui_view).expect("Id<T> should never be null")
|
||||
});
|
||||
window_handle.ui_view_controller =
|
||||
std::ptr::NonNull::new(Id::as_ptr(&self.view_controller) as _);
|
||||
Ok(rwh_06::RawWindowHandle::UiKit(window_handle))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
||||
Ok(rwh_06::RawDisplayHandle::UiKit(
|
||||
rwh_06::UiKitDisplayHandle::new(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn theme(&self) -> Option<Theme> {
|
||||
@@ -340,6 +379,8 @@ impl Inner {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_content_protected(&self, _protected: bool) {}
|
||||
|
||||
pub fn has_focus(&self) -> bool {
|
||||
self.window.isKeyWindow()
|
||||
}
|
||||
@@ -365,11 +406,11 @@ pub struct Window {
|
||||
|
||||
impl Window {
|
||||
pub(crate) fn new<T>(
|
||||
_event_loop: &EventLoopWindowTarget<T>,
|
||||
event_loop: &EventLoopWindowTarget<T>,
|
||||
window_attributes: WindowAttributes,
|
||||
platform_attributes: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, RootOsError> {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let mtm = event_loop.mtm;
|
||||
|
||||
if window_attributes.min_inner_size.is_some() {
|
||||
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
|
||||
@@ -381,10 +422,10 @@ impl Window {
|
||||
// TODO: transparency, visible
|
||||
|
||||
let main_screen = UIScreen::main(mtm);
|
||||
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
|
||||
let fullscreen = window_attributes.fullscreen.0.clone().map(Into::into);
|
||||
let screen = match fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(),
|
||||
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(),
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
|
||||
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
|
||||
Some(Fullscreen::Borderless(None)) | None => &main_screen,
|
||||
};
|
||||
|
||||
@@ -424,7 +465,7 @@ impl Window {
|
||||
&view_controller,
|
||||
);
|
||||
|
||||
unsafe { app_state::set_key_window(&window) };
|
||||
app_state::set_key_window(mtm, &window);
|
||||
|
||||
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
|
||||
// event on window creation if the DPI factor != 1.0
|
||||
@@ -436,25 +477,26 @@ impl Window {
|
||||
let screen_space = screen.coordinateSpace();
|
||||
let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
|
||||
let size = crate::dpi::LogicalSize {
|
||||
width: screen_frame.size.width as _,
|
||||
height: screen_frame.size.height as _,
|
||||
width: screen_frame.size.width as f64,
|
||||
height: screen_frame.size.height as f64,
|
||||
};
|
||||
let window_id = RootWindowId(window.id());
|
||||
unsafe {
|
||||
app_state::handle_nonuser_events(
|
||||
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
|
||||
app_state::handle_nonuser_events(
|
||||
mtm,
|
||||
std::iter::once(EventWrapper::ScaleFactorChanged(
|
||||
app_state::ScaleFactorChanged {
|
||||
window: window.clone(),
|
||||
scale_factor,
|
||||
suggested_size: size,
|
||||
}))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
||||
},
|
||||
))),
|
||||
);
|
||||
}
|
||||
suggested_size: size.to_physical(scale_factor),
|
||||
},
|
||||
))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
||||
},
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
let inner = Inner {
|
||||
@@ -509,6 +551,11 @@ impl Inner {
|
||||
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||
self.view_controller.set_prefers_status_bar_hidden(hidden);
|
||||
}
|
||||
|
||||
pub fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
|
||||
self.view_controller
|
||||
.set_preferred_status_bar_style(status_bar_style.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
@@ -617,5 +664,6 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub valid_orientations: ValidOrientations,
|
||||
pub prefers_home_indicator_hidden: bool,
|
||||
pub prefers_status_bar_hidden: bool,
|
||||
pub preferred_status_bar_style: StatusBarStyle,
|
||||
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
|
||||
}
|
||||
|
||||
@@ -1,887 +0,0 @@
|
||||
//! Convert XKB keys to Winit keys.
|
||||
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
|
||||
|
||||
/// Map the raw X11-style keycode to the `KeyCode` enum.
|
||||
///
|
||||
/// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses.
|
||||
pub fn raw_keycode_to_keycode(keycode: u32) -> KeyCode {
|
||||
scancode_to_keycode(keycode.saturating_sub(8))
|
||||
}
|
||||
|
||||
/// Map the linux scancode to Keycode.
|
||||
///
|
||||
/// Both X11 and Wayland use keys with `+ 8` offset to linux scancode.
|
||||
pub fn scancode_to_keycode(scancode: u32) -> KeyCode {
|
||||
// The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as
|
||||
// libxkbcommon's documentation seems to suggest that the keycode values we're interested in
|
||||
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
|
||||
// I can only hope they agree on what the keycodes mean.
|
||||
//
|
||||
// Some of the keycodes are likely superfluous for our purposes, and some are ones which are
|
||||
// difficult to test the correctness of, or discover the purpose of. Because of this, they've
|
||||
// either been commented out here, or not included at all.
|
||||
match scancode {
|
||||
0 => KeyCode::Unidentified(NativeKeyCode::Xkb(0)),
|
||||
1 => KeyCode::Escape,
|
||||
2 => KeyCode::Digit1,
|
||||
3 => KeyCode::Digit2,
|
||||
4 => KeyCode::Digit3,
|
||||
5 => KeyCode::Digit4,
|
||||
6 => KeyCode::Digit5,
|
||||
7 => KeyCode::Digit6,
|
||||
8 => KeyCode::Digit7,
|
||||
9 => KeyCode::Digit8,
|
||||
10 => KeyCode::Digit9,
|
||||
11 => KeyCode::Digit0,
|
||||
12 => KeyCode::Minus,
|
||||
13 => KeyCode::Equal,
|
||||
14 => KeyCode::Backspace,
|
||||
15 => KeyCode::Tab,
|
||||
16 => KeyCode::KeyQ,
|
||||
17 => KeyCode::KeyW,
|
||||
18 => KeyCode::KeyE,
|
||||
19 => KeyCode::KeyR,
|
||||
20 => KeyCode::KeyT,
|
||||
21 => KeyCode::KeyY,
|
||||
22 => KeyCode::KeyU,
|
||||
23 => KeyCode::KeyI,
|
||||
24 => KeyCode::KeyO,
|
||||
25 => KeyCode::KeyP,
|
||||
26 => KeyCode::BracketLeft,
|
||||
27 => KeyCode::BracketRight,
|
||||
28 => KeyCode::Enter,
|
||||
29 => KeyCode::ControlLeft,
|
||||
30 => KeyCode::KeyA,
|
||||
31 => KeyCode::KeyS,
|
||||
32 => KeyCode::KeyD,
|
||||
33 => KeyCode::KeyF,
|
||||
34 => KeyCode::KeyG,
|
||||
35 => KeyCode::KeyH,
|
||||
36 => KeyCode::KeyJ,
|
||||
37 => KeyCode::KeyK,
|
||||
38 => KeyCode::KeyL,
|
||||
39 => KeyCode::Semicolon,
|
||||
40 => KeyCode::Quote,
|
||||
41 => KeyCode::Backquote,
|
||||
42 => KeyCode::ShiftLeft,
|
||||
43 => KeyCode::Backslash,
|
||||
44 => KeyCode::KeyZ,
|
||||
45 => KeyCode::KeyX,
|
||||
46 => KeyCode::KeyC,
|
||||
47 => KeyCode::KeyV,
|
||||
48 => KeyCode::KeyB,
|
||||
49 => KeyCode::KeyN,
|
||||
50 => KeyCode::KeyM,
|
||||
51 => KeyCode::Comma,
|
||||
52 => KeyCode::Period,
|
||||
53 => KeyCode::Slash,
|
||||
54 => KeyCode::ShiftRight,
|
||||
55 => KeyCode::NumpadMultiply,
|
||||
56 => KeyCode::AltLeft,
|
||||
57 => KeyCode::Space,
|
||||
58 => KeyCode::CapsLock,
|
||||
59 => KeyCode::F1,
|
||||
60 => KeyCode::F2,
|
||||
61 => KeyCode::F3,
|
||||
62 => KeyCode::F4,
|
||||
63 => KeyCode::F5,
|
||||
64 => KeyCode::F6,
|
||||
65 => KeyCode::F7,
|
||||
66 => KeyCode::F8,
|
||||
67 => KeyCode::F9,
|
||||
68 => KeyCode::F10,
|
||||
69 => KeyCode::NumLock,
|
||||
70 => KeyCode::ScrollLock,
|
||||
71 => KeyCode::Numpad7,
|
||||
72 => KeyCode::Numpad8,
|
||||
73 => KeyCode::Numpad9,
|
||||
74 => KeyCode::NumpadSubtract,
|
||||
75 => KeyCode::Numpad4,
|
||||
76 => KeyCode::Numpad5,
|
||||
77 => KeyCode::Numpad6,
|
||||
78 => KeyCode::NumpadAdd,
|
||||
79 => KeyCode::Numpad1,
|
||||
80 => KeyCode::Numpad2,
|
||||
81 => KeyCode::Numpad3,
|
||||
82 => KeyCode::Numpad0,
|
||||
83 => KeyCode::NumpadDecimal,
|
||||
85 => KeyCode::Lang5,
|
||||
86 => KeyCode::IntlBackslash,
|
||||
87 => KeyCode::F11,
|
||||
88 => KeyCode::F12,
|
||||
89 => KeyCode::IntlRo,
|
||||
90 => KeyCode::Lang3,
|
||||
91 => KeyCode::Lang4,
|
||||
92 => KeyCode::Convert,
|
||||
93 => KeyCode::KanaMode,
|
||||
94 => KeyCode::NonConvert,
|
||||
// 95 => KeyCode::KPJPCOMMA,
|
||||
96 => KeyCode::NumpadEnter,
|
||||
97 => KeyCode::ControlRight,
|
||||
98 => KeyCode::NumpadDivide,
|
||||
99 => KeyCode::PrintScreen,
|
||||
100 => KeyCode::AltRight,
|
||||
// 101 => KeyCode::LINEFEED,
|
||||
102 => KeyCode::Home,
|
||||
103 => KeyCode::ArrowUp,
|
||||
104 => KeyCode::PageUp,
|
||||
105 => KeyCode::ArrowLeft,
|
||||
106 => KeyCode::ArrowRight,
|
||||
107 => KeyCode::End,
|
||||
108 => KeyCode::ArrowDown,
|
||||
109 => KeyCode::PageDown,
|
||||
110 => KeyCode::Insert,
|
||||
111 => KeyCode::Delete,
|
||||
// 112 => KeyCode::MACRO,
|
||||
113 => KeyCode::AudioVolumeMute,
|
||||
114 => KeyCode::AudioVolumeDown,
|
||||
115 => KeyCode::AudioVolumeUp,
|
||||
// 116 => KeyCode::POWER,
|
||||
117 => KeyCode::NumpadEqual,
|
||||
// 118 => KeyCode::KPPLUSMINUS,
|
||||
119 => KeyCode::Pause,
|
||||
// 120 => KeyCode::SCALE,
|
||||
121 => KeyCode::NumpadComma,
|
||||
122 => KeyCode::Lang1,
|
||||
123 => KeyCode::Lang2,
|
||||
124 => KeyCode::IntlYen,
|
||||
125 => KeyCode::SuperLeft,
|
||||
126 => KeyCode::SuperRight,
|
||||
127 => KeyCode::ContextMenu,
|
||||
// 128 => KeyCode::STOP,
|
||||
// 129 => KeyCode::AGAIN,
|
||||
// 130 => KeyCode::PROPS,
|
||||
// 131 => KeyCode::UNDO,
|
||||
// 132 => KeyCode::FRONT,
|
||||
// 133 => KeyCode::COPY,
|
||||
// 134 => KeyCode::OPEN,
|
||||
// 135 => KeyCode::PASTE,
|
||||
// 136 => KeyCode::FIND,
|
||||
// 137 => KeyCode::CUT,
|
||||
// 138 => KeyCode::HELP,
|
||||
// 139 => KeyCode::MENU,
|
||||
// 140 => KeyCode::CALC,
|
||||
// 141 => KeyCode::SETUP,
|
||||
// 142 => KeyCode::SLEEP,
|
||||
// 143 => KeyCode::WAKEUP,
|
||||
// 144 => KeyCode::FILE,
|
||||
// 145 => KeyCode::SENDFILE,
|
||||
// 146 => KeyCode::DELETEFILE,
|
||||
// 147 => KeyCode::XFER,
|
||||
// 148 => KeyCode::PROG1,
|
||||
// 149 => KeyCode::PROG2,
|
||||
// 150 => KeyCode::WWW,
|
||||
// 151 => KeyCode::MSDOS,
|
||||
// 152 => KeyCode::COFFEE,
|
||||
// 153 => KeyCode::ROTATE_DISPLAY,
|
||||
// 154 => KeyCode::CYCLEWINDOWS,
|
||||
// 155 => KeyCode::MAIL,
|
||||
// 156 => KeyCode::BOOKMARKS,
|
||||
// 157 => KeyCode::COMPUTER,
|
||||
// 158 => KeyCode::BACK,
|
||||
// 159 => KeyCode::FORWARD,
|
||||
// 160 => KeyCode::CLOSECD,
|
||||
// 161 => KeyCode::EJECTCD,
|
||||
// 162 => KeyCode::EJECTCLOSECD,
|
||||
163 => KeyCode::MediaTrackNext,
|
||||
164 => KeyCode::MediaPlayPause,
|
||||
165 => KeyCode::MediaTrackPrevious,
|
||||
166 => KeyCode::MediaStop,
|
||||
// 167 => KeyCode::RECORD,
|
||||
// 168 => KeyCode::REWIND,
|
||||
// 169 => KeyCode::PHONE,
|
||||
// 170 => KeyCode::ISO,
|
||||
// 171 => KeyCode::CONFIG,
|
||||
// 172 => KeyCode::HOMEPAGE,
|
||||
// 173 => KeyCode::REFRESH,
|
||||
// 174 => KeyCode::EXIT,
|
||||
// 175 => KeyCode::MOVE,
|
||||
// 176 => KeyCode::EDIT,
|
||||
// 177 => KeyCode::SCROLLUP,
|
||||
// 178 => KeyCode::SCROLLDOWN,
|
||||
// 179 => KeyCode::KPLEFTPAREN,
|
||||
// 180 => KeyCode::KPRIGHTPAREN,
|
||||
// 181 => KeyCode::NEW,
|
||||
// 182 => KeyCode::REDO,
|
||||
183 => KeyCode::F13,
|
||||
184 => KeyCode::F14,
|
||||
185 => KeyCode::F15,
|
||||
186 => KeyCode::F16,
|
||||
187 => KeyCode::F17,
|
||||
188 => KeyCode::F18,
|
||||
189 => KeyCode::F19,
|
||||
190 => KeyCode::F20,
|
||||
191 => KeyCode::F21,
|
||||
192 => KeyCode::F22,
|
||||
193 => KeyCode::F23,
|
||||
194 => KeyCode::F24,
|
||||
// 200 => KeyCode::PLAYCD,
|
||||
// 201 => KeyCode::PAUSECD,
|
||||
// 202 => KeyCode::PROG3,
|
||||
// 203 => KeyCode::PROG4,
|
||||
// 204 => KeyCode::DASHBOARD,
|
||||
// 205 => KeyCode::SUSPEND,
|
||||
// 206 => KeyCode::CLOSE,
|
||||
// 207 => KeyCode::PLAY,
|
||||
// 208 => KeyCode::FASTFORWARD,
|
||||
// 209 => KeyCode::BASSBOOST,
|
||||
// 210 => KeyCode::PRINT,
|
||||
// 211 => KeyCode::HP,
|
||||
// 212 => KeyCode::CAMERA,
|
||||
// 213 => KeyCode::SOUND,
|
||||
// 214 => KeyCode::QUESTION,
|
||||
// 215 => KeyCode::EMAIL,
|
||||
// 216 => KeyCode::CHAT,
|
||||
// 217 => KeyCode::SEARCH,
|
||||
// 218 => KeyCode::CONNECT,
|
||||
// 219 => KeyCode::FINANCE,
|
||||
// 220 => KeyCode::SPORT,
|
||||
// 221 => KeyCode::SHOP,
|
||||
// 222 => KeyCode::ALTERASE,
|
||||
// 223 => KeyCode::CANCEL,
|
||||
// 224 => KeyCode::BRIGHTNESSDOW,
|
||||
// 225 => KeyCode::BRIGHTNESSU,
|
||||
// 226 => KeyCode::MEDIA,
|
||||
// 227 => KeyCode::SWITCHVIDEOMODE,
|
||||
// 228 => KeyCode::KBDILLUMTOGGLE,
|
||||
// 229 => KeyCode::KBDILLUMDOWN,
|
||||
// 230 => KeyCode::KBDILLUMUP,
|
||||
// 231 => KeyCode::SEND,
|
||||
// 232 => KeyCode::REPLY,
|
||||
// 233 => KeyCode::FORWARDMAIL,
|
||||
// 234 => KeyCode::SAVE,
|
||||
// 235 => KeyCode::DOCUMENTS,
|
||||
// 236 => KeyCode::BATTERY,
|
||||
// 237 => KeyCode::BLUETOOTH,
|
||||
// 238 => KeyCode::WLAN,
|
||||
// 239 => KeyCode::UWB,
|
||||
240 => KeyCode::Unidentified(NativeKeyCode::Unidentified),
|
||||
// 241 => KeyCode::VIDEO_NEXT,
|
||||
// 242 => KeyCode::VIDEO_PREV,
|
||||
// 243 => KeyCode::BRIGHTNESS_CYCLE,
|
||||
// 244 => KeyCode::BRIGHTNESS_AUTO,
|
||||
// 245 => KeyCode::DISPLAY_OFF,
|
||||
// 246 => KeyCode::WWAN,
|
||||
// 247 => KeyCode::RFKILL,
|
||||
// 248 => KeyCode::KEY_MICMUTE,
|
||||
_ => KeyCode::Unidentified(NativeKeyCode::Xkb(scancode)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keycode_to_scancode(keycode: KeyCode) -> Option<u32> {
|
||||
match keycode {
|
||||
KeyCode::Unidentified(NativeKeyCode::Unidentified) => Some(240),
|
||||
KeyCode::Unidentified(NativeKeyCode::Xkb(raw)) => Some(raw),
|
||||
KeyCode::Escape => Some(1),
|
||||
KeyCode::Digit1 => Some(2),
|
||||
KeyCode::Digit2 => Some(3),
|
||||
KeyCode::Digit3 => Some(4),
|
||||
KeyCode::Digit4 => Some(5),
|
||||
KeyCode::Digit5 => Some(6),
|
||||
KeyCode::Digit6 => Some(7),
|
||||
KeyCode::Digit7 => Some(8),
|
||||
KeyCode::Digit8 => Some(9),
|
||||
KeyCode::Digit9 => Some(10),
|
||||
KeyCode::Digit0 => Some(11),
|
||||
KeyCode::Minus => Some(12),
|
||||
KeyCode::Equal => Some(13),
|
||||
KeyCode::Backspace => Some(14),
|
||||
KeyCode::Tab => Some(15),
|
||||
KeyCode::KeyQ => Some(16),
|
||||
KeyCode::KeyW => Some(17),
|
||||
KeyCode::KeyE => Some(18),
|
||||
KeyCode::KeyR => Some(19),
|
||||
KeyCode::KeyT => Some(20),
|
||||
KeyCode::KeyY => Some(21),
|
||||
KeyCode::KeyU => Some(22),
|
||||
KeyCode::KeyI => Some(23),
|
||||
KeyCode::KeyO => Some(24),
|
||||
KeyCode::KeyP => Some(25),
|
||||
KeyCode::BracketLeft => Some(26),
|
||||
KeyCode::BracketRight => Some(27),
|
||||
KeyCode::Enter => Some(28),
|
||||
KeyCode::ControlLeft => Some(29),
|
||||
KeyCode::KeyA => Some(30),
|
||||
KeyCode::KeyS => Some(31),
|
||||
KeyCode::KeyD => Some(32),
|
||||
KeyCode::KeyF => Some(33),
|
||||
KeyCode::KeyG => Some(34),
|
||||
KeyCode::KeyH => Some(35),
|
||||
KeyCode::KeyJ => Some(36),
|
||||
KeyCode::KeyK => Some(37),
|
||||
KeyCode::KeyL => Some(38),
|
||||
KeyCode::Semicolon => Some(39),
|
||||
KeyCode::Quote => Some(40),
|
||||
KeyCode::Backquote => Some(41),
|
||||
KeyCode::ShiftLeft => Some(42),
|
||||
KeyCode::Backslash => Some(43),
|
||||
KeyCode::KeyZ => Some(44),
|
||||
KeyCode::KeyX => Some(45),
|
||||
KeyCode::KeyC => Some(46),
|
||||
KeyCode::KeyV => Some(47),
|
||||
KeyCode::KeyB => Some(48),
|
||||
KeyCode::KeyN => Some(49),
|
||||
KeyCode::KeyM => Some(50),
|
||||
KeyCode::Comma => Some(51),
|
||||
KeyCode::Period => Some(52),
|
||||
KeyCode::Slash => Some(53),
|
||||
KeyCode::ShiftRight => Some(54),
|
||||
KeyCode::NumpadMultiply => Some(55),
|
||||
KeyCode::AltLeft => Some(56),
|
||||
KeyCode::Space => Some(57),
|
||||
KeyCode::CapsLock => Some(58),
|
||||
KeyCode::F1 => Some(59),
|
||||
KeyCode::F2 => Some(60),
|
||||
KeyCode::F3 => Some(61),
|
||||
KeyCode::F4 => Some(62),
|
||||
KeyCode::F5 => Some(63),
|
||||
KeyCode::F6 => Some(64),
|
||||
KeyCode::F7 => Some(65),
|
||||
KeyCode::F8 => Some(66),
|
||||
KeyCode::F9 => Some(67),
|
||||
KeyCode::F10 => Some(68),
|
||||
KeyCode::NumLock => Some(69),
|
||||
KeyCode::ScrollLock => Some(70),
|
||||
KeyCode::Numpad7 => Some(71),
|
||||
KeyCode::Numpad8 => Some(72),
|
||||
KeyCode::Numpad9 => Some(73),
|
||||
KeyCode::NumpadSubtract => Some(74),
|
||||
KeyCode::Numpad4 => Some(75),
|
||||
KeyCode::Numpad5 => Some(76),
|
||||
KeyCode::Numpad6 => Some(77),
|
||||
KeyCode::NumpadAdd => Some(78),
|
||||
KeyCode::Numpad1 => Some(79),
|
||||
KeyCode::Numpad2 => Some(80),
|
||||
KeyCode::Numpad3 => Some(81),
|
||||
KeyCode::Numpad0 => Some(82),
|
||||
KeyCode::NumpadDecimal => Some(83),
|
||||
KeyCode::Lang5 => Some(85),
|
||||
KeyCode::IntlBackslash => Some(86),
|
||||
KeyCode::F11 => Some(87),
|
||||
KeyCode::F12 => Some(88),
|
||||
KeyCode::IntlRo => Some(89),
|
||||
KeyCode::Lang3 => Some(90),
|
||||
KeyCode::Lang4 => Some(91),
|
||||
KeyCode::Convert => Some(92),
|
||||
KeyCode::KanaMode => Some(93),
|
||||
KeyCode::NonConvert => Some(94),
|
||||
KeyCode::NumpadEnter => Some(96),
|
||||
KeyCode::ControlRight => Some(97),
|
||||
KeyCode::NumpadDivide => Some(98),
|
||||
KeyCode::PrintScreen => Some(99),
|
||||
KeyCode::AltRight => Some(100),
|
||||
KeyCode::Home => Some(102),
|
||||
KeyCode::ArrowUp => Some(103),
|
||||
KeyCode::PageUp => Some(104),
|
||||
KeyCode::ArrowLeft => Some(105),
|
||||
KeyCode::ArrowRight => Some(106),
|
||||
KeyCode::End => Some(107),
|
||||
KeyCode::ArrowDown => Some(108),
|
||||
KeyCode::PageDown => Some(109),
|
||||
KeyCode::Insert => Some(110),
|
||||
KeyCode::Delete => Some(111),
|
||||
KeyCode::AudioVolumeMute => Some(113),
|
||||
KeyCode::AudioVolumeDown => Some(114),
|
||||
KeyCode::AudioVolumeUp => Some(115),
|
||||
KeyCode::NumpadEqual => Some(117),
|
||||
KeyCode::Pause => Some(119),
|
||||
KeyCode::NumpadComma => Some(121),
|
||||
KeyCode::Lang1 => Some(122),
|
||||
KeyCode::Lang2 => Some(123),
|
||||
KeyCode::IntlYen => Some(124),
|
||||
KeyCode::SuperLeft => Some(125),
|
||||
KeyCode::SuperRight => Some(126),
|
||||
KeyCode::ContextMenu => Some(127),
|
||||
KeyCode::MediaTrackNext => Some(163),
|
||||
KeyCode::MediaPlayPause => Some(164),
|
||||
KeyCode::MediaTrackPrevious => Some(165),
|
||||
KeyCode::MediaStop => Some(166),
|
||||
KeyCode::F13 => Some(183),
|
||||
KeyCode::F14 => Some(184),
|
||||
KeyCode::F15 => Some(185),
|
||||
KeyCode::F16 => Some(186),
|
||||
KeyCode::F17 => Some(187),
|
||||
KeyCode::F18 => Some(188),
|
||||
KeyCode::F19 => Some(189),
|
||||
KeyCode::F20 => Some(190),
|
||||
KeyCode::F21 => Some(191),
|
||||
KeyCode::F22 => Some(192),
|
||||
KeyCode::F23 => Some(193),
|
||||
KeyCode::F24 => Some(194),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keysym_to_key(keysym: u32) -> Key {
|
||||
use xkbcommon_dl::keysyms;
|
||||
match keysym {
|
||||
// TTY function keys
|
||||
keysyms::BackSpace => Key::Backspace,
|
||||
keysyms::Tab => Key::Tab,
|
||||
// keysyms::Linefeed => Key::Linefeed,
|
||||
keysyms::Clear => Key::Clear,
|
||||
keysyms::Return => Key::Enter,
|
||||
keysyms::Pause => Key::Pause,
|
||||
keysyms::Scroll_Lock => Key::ScrollLock,
|
||||
keysyms::Sys_Req => Key::PrintScreen,
|
||||
keysyms::Escape => Key::Escape,
|
||||
keysyms::Delete => Key::Delete,
|
||||
|
||||
// IME keys
|
||||
keysyms::Multi_key => Key::Compose,
|
||||
keysyms::Codeinput => Key::CodeInput,
|
||||
keysyms::SingleCandidate => Key::SingleCandidate,
|
||||
keysyms::MultipleCandidate => Key::AllCandidates,
|
||||
keysyms::PreviousCandidate => Key::PreviousCandidate,
|
||||
|
||||
// Japanese keys
|
||||
keysyms::Kanji => Key::KanjiMode,
|
||||
keysyms::Muhenkan => Key::NonConvert,
|
||||
keysyms::Henkan_Mode => Key::Convert,
|
||||
keysyms::Romaji => Key::Romaji,
|
||||
keysyms::Hiragana => Key::Hiragana,
|
||||
keysyms::Hiragana_Katakana => Key::HiraganaKatakana,
|
||||
keysyms::Zenkaku => Key::Zenkaku,
|
||||
keysyms::Hankaku => Key::Hankaku,
|
||||
keysyms::Zenkaku_Hankaku => Key::ZenkakuHankaku,
|
||||
// keysyms::Touroku => Key::Touroku,
|
||||
// keysyms::Massyo => Key::Massyo,
|
||||
keysyms::Kana_Lock => Key::KanaMode,
|
||||
keysyms::Kana_Shift => Key::KanaMode,
|
||||
keysyms::Eisu_Shift => Key::Alphanumeric,
|
||||
keysyms::Eisu_toggle => Key::Alphanumeric,
|
||||
// NOTE: The next three items are aliases for values we've already mapped.
|
||||
// keysyms::Kanji_Bangou => Key::CodeInput,
|
||||
// keysyms::Zen_Koho => Key::AllCandidates,
|
||||
// keysyms::Mae_Koho => Key::PreviousCandidate,
|
||||
|
||||
// Cursor control & motion
|
||||
keysyms::Home => Key::Home,
|
||||
keysyms::Left => Key::ArrowLeft,
|
||||
keysyms::Up => Key::ArrowUp,
|
||||
keysyms::Right => Key::ArrowRight,
|
||||
keysyms::Down => Key::ArrowDown,
|
||||
// keysyms::Prior => Key::PageUp,
|
||||
keysyms::Page_Up => Key::PageUp,
|
||||
// keysyms::Next => Key::PageDown,
|
||||
keysyms::Page_Down => Key::PageDown,
|
||||
keysyms::End => Key::End,
|
||||
// keysyms::Begin => Key::Begin,
|
||||
|
||||
// Misc. functions
|
||||
keysyms::Select => Key::Select,
|
||||
keysyms::Print => Key::PrintScreen,
|
||||
keysyms::Execute => Key::Execute,
|
||||
keysyms::Insert => Key::Insert,
|
||||
keysyms::Undo => Key::Undo,
|
||||
keysyms::Redo => Key::Redo,
|
||||
keysyms::Menu => Key::ContextMenu,
|
||||
keysyms::Find => Key::Find,
|
||||
keysyms::Cancel => Key::Cancel,
|
||||
keysyms::Help => Key::Help,
|
||||
keysyms::Break => Key::Pause,
|
||||
keysyms::Mode_switch => Key::ModeChange,
|
||||
// keysyms::script_switch => Key::ModeChange,
|
||||
keysyms::Num_Lock => Key::NumLock,
|
||||
|
||||
// Keypad keys
|
||||
// keysyms::KP_Space => Key::Character(" "),
|
||||
keysyms::KP_Tab => Key::Tab,
|
||||
keysyms::KP_Enter => Key::Enter,
|
||||
keysyms::KP_F1 => Key::F1,
|
||||
keysyms::KP_F2 => Key::F2,
|
||||
keysyms::KP_F3 => Key::F3,
|
||||
keysyms::KP_F4 => Key::F4,
|
||||
keysyms::KP_Home => Key::Home,
|
||||
keysyms::KP_Left => Key::ArrowLeft,
|
||||
keysyms::KP_Up => Key::ArrowLeft,
|
||||
keysyms::KP_Right => Key::ArrowRight,
|
||||
keysyms::KP_Down => Key::ArrowDown,
|
||||
// keysyms::KP_Prior => Key::PageUp,
|
||||
keysyms::KP_Page_Up => Key::PageUp,
|
||||
// keysyms::KP_Next => Key::PageDown,
|
||||
keysyms::KP_Page_Down => Key::PageDown,
|
||||
keysyms::KP_End => Key::End,
|
||||
// This is the key labeled "5" on the numpad when NumLock is off.
|
||||
// keysyms::KP_Begin => Key::Begin,
|
||||
keysyms::KP_Insert => Key::Insert,
|
||||
keysyms::KP_Delete => Key::Delete,
|
||||
// keysyms::KP_Equal => Key::Equal,
|
||||
// keysyms::KP_Multiply => Key::Multiply,
|
||||
// keysyms::KP_Add => Key::Add,
|
||||
// keysyms::KP_Separator => Key::Separator,
|
||||
// keysyms::KP_Subtract => Key::Subtract,
|
||||
// keysyms::KP_Decimal => Key::Decimal,
|
||||
// keysyms::KP_Divide => Key::Divide,
|
||||
|
||||
// keysyms::KP_0 => Key::Character("0"),
|
||||
// keysyms::KP_1 => Key::Character("1"),
|
||||
// keysyms::KP_2 => Key::Character("2"),
|
||||
// keysyms::KP_3 => Key::Character("3"),
|
||||
// keysyms::KP_4 => Key::Character("4"),
|
||||
// keysyms::KP_5 => Key::Character("5"),
|
||||
// keysyms::KP_6 => Key::Character("6"),
|
||||
// keysyms::KP_7 => Key::Character("7"),
|
||||
// keysyms::KP_8 => Key::Character("8"),
|
||||
// keysyms::KP_9 => Key::Character("9"),
|
||||
|
||||
// Function keys
|
||||
keysyms::F1 => Key::F1,
|
||||
keysyms::F2 => Key::F2,
|
||||
keysyms::F3 => Key::F3,
|
||||
keysyms::F4 => Key::F4,
|
||||
keysyms::F5 => Key::F5,
|
||||
keysyms::F6 => Key::F6,
|
||||
keysyms::F7 => Key::F7,
|
||||
keysyms::F8 => Key::F8,
|
||||
keysyms::F9 => Key::F9,
|
||||
keysyms::F10 => Key::F10,
|
||||
keysyms::F11 => Key::F11,
|
||||
keysyms::F12 => Key::F12,
|
||||
keysyms::F13 => Key::F13,
|
||||
keysyms::F14 => Key::F14,
|
||||
keysyms::F15 => Key::F15,
|
||||
keysyms::F16 => Key::F16,
|
||||
keysyms::F17 => Key::F17,
|
||||
keysyms::F18 => Key::F18,
|
||||
keysyms::F19 => Key::F19,
|
||||
keysyms::F20 => Key::F20,
|
||||
keysyms::F21 => Key::F21,
|
||||
keysyms::F22 => Key::F22,
|
||||
keysyms::F23 => Key::F23,
|
||||
keysyms::F24 => Key::F24,
|
||||
keysyms::F25 => Key::F25,
|
||||
keysyms::F26 => Key::F26,
|
||||
keysyms::F27 => Key::F27,
|
||||
keysyms::F28 => Key::F28,
|
||||
keysyms::F29 => Key::F29,
|
||||
keysyms::F30 => Key::F30,
|
||||
keysyms::F31 => Key::F31,
|
||||
keysyms::F32 => Key::F32,
|
||||
keysyms::F33 => Key::F33,
|
||||
keysyms::F34 => Key::F34,
|
||||
keysyms::F35 => Key::F35,
|
||||
|
||||
// Modifiers
|
||||
keysyms::Shift_L => Key::Shift,
|
||||
keysyms::Shift_R => Key::Shift,
|
||||
keysyms::Control_L => Key::Control,
|
||||
keysyms::Control_R => Key::Control,
|
||||
keysyms::Caps_Lock => Key::CapsLock,
|
||||
// keysyms::Shift_Lock => Key::ShiftLock,
|
||||
|
||||
// keysyms::Meta_L => Key::Meta,
|
||||
// keysyms::Meta_R => Key::Meta,
|
||||
keysyms::Alt_L => Key::Alt,
|
||||
keysyms::Alt_R => Key::Alt,
|
||||
keysyms::Super_L => Key::Super,
|
||||
keysyms::Super_R => Key::Super,
|
||||
keysyms::Hyper_L => Key::Hyper,
|
||||
keysyms::Hyper_R => Key::Hyper,
|
||||
|
||||
// XKB function and modifier keys
|
||||
// keysyms::ISO_Lock => Key::IsoLock,
|
||||
// keysyms::ISO_Level2_Latch => Key::IsoLevel2Latch,
|
||||
keysyms::ISO_Level3_Shift => Key::AltGraph,
|
||||
keysyms::ISO_Level3_Latch => Key::AltGraph,
|
||||
keysyms::ISO_Level3_Lock => Key::AltGraph,
|
||||
// keysyms::ISO_Level5_Shift => Key::IsoLevel5Shift,
|
||||
// keysyms::ISO_Level5_Latch => Key::IsoLevel5Latch,
|
||||
// keysyms::ISO_Level5_Lock => Key::IsoLevel5Lock,
|
||||
// keysyms::ISO_Group_Shift => Key::IsoGroupShift,
|
||||
// keysyms::ISO_Group_Latch => Key::IsoGroupLatch,
|
||||
// keysyms::ISO_Group_Lock => Key::IsoGroupLock,
|
||||
keysyms::ISO_Next_Group => Key::GroupNext,
|
||||
// keysyms::ISO_Next_Group_Lock => Key::GroupNextLock,
|
||||
keysyms::ISO_Prev_Group => Key::GroupPrevious,
|
||||
// keysyms::ISO_Prev_Group_Lock => Key::GroupPreviousLock,
|
||||
keysyms::ISO_First_Group => Key::GroupFirst,
|
||||
// keysyms::ISO_First_Group_Lock => Key::GroupFirstLock,
|
||||
keysyms::ISO_Last_Group => Key::GroupLast,
|
||||
// keysyms::ISO_Last_Group_Lock => Key::GroupLastLock,
|
||||
//
|
||||
keysyms::ISO_Left_Tab => Key::Tab,
|
||||
// keysyms::ISO_Move_Line_Up => Key::IsoMoveLineUp,
|
||||
// keysyms::ISO_Move_Line_Down => Key::IsoMoveLineDown,
|
||||
// keysyms::ISO_Partial_Line_Up => Key::IsoPartialLineUp,
|
||||
// keysyms::ISO_Partial_Line_Down => Key::IsoPartialLineDown,
|
||||
// keysyms::ISO_Partial_Space_Left => Key::IsoPartialSpaceLeft,
|
||||
// keysyms::ISO_Partial_Space_Right => Key::IsoPartialSpaceRight,
|
||||
// keysyms::ISO_Set_Margin_Left => Key::IsoSetMarginLeft,
|
||||
// keysyms::ISO_Set_Margin_Right => Key::IsoSetMarginRight,
|
||||
// keysyms::ISO_Release_Margin_Left => Key::IsoReleaseMarginLeft,
|
||||
// keysyms::ISO_Release_Margin_Right => Key::IsoReleaseMarginRight,
|
||||
// keysyms::ISO_Release_Both_Margins => Key::IsoReleaseBothMargins,
|
||||
// keysyms::ISO_Fast_Cursor_Left => Key::IsoFastCursorLeft,
|
||||
// keysyms::ISO_Fast_Cursor_Right => Key::IsoFastCursorRight,
|
||||
// keysyms::ISO_Fast_Cursor_Up => Key::IsoFastCursorUp,
|
||||
// keysyms::ISO_Fast_Cursor_Down => Key::IsoFastCursorDown,
|
||||
// keysyms::ISO_Continuous_Underline => Key::IsoContinuousUnderline,
|
||||
// keysyms::ISO_Discontinuous_Underline => Key::IsoDiscontinuousUnderline,
|
||||
// keysyms::ISO_Emphasize => Key::IsoEmphasize,
|
||||
// keysyms::ISO_Center_Object => Key::IsoCenterObject,
|
||||
keysyms::ISO_Enter => Key::Enter,
|
||||
|
||||
// dead_grave..dead_currency
|
||||
|
||||
// dead_lowline..dead_longsolidusoverlay
|
||||
|
||||
// dead_a..dead_capital_schwa
|
||||
|
||||
// dead_greek
|
||||
|
||||
// First_Virtual_Screen..Terminate_Server
|
||||
|
||||
// AccessX_Enable..AudibleBell_Enable
|
||||
|
||||
// Pointer_Left..Pointer_Drag5
|
||||
|
||||
// Pointer_EnableKeys..Pointer_DfltBtnPrev
|
||||
|
||||
// ch..C_H
|
||||
|
||||
// 3270 terminal keys
|
||||
// keysyms::3270_Duplicate => Key::Duplicate,
|
||||
// keysyms::3270_FieldMark => Key::FieldMark,
|
||||
// keysyms::3270_Right2 => Key::Right2,
|
||||
// keysyms::3270_Left2 => Key::Left2,
|
||||
// keysyms::3270_BackTab => Key::BackTab,
|
||||
keysyms::_3270_EraseEOF => Key::EraseEof,
|
||||
// keysyms::3270_EraseInput => Key::EraseInput,
|
||||
// keysyms::3270_Reset => Key::Reset,
|
||||
// keysyms::3270_Quit => Key::Quit,
|
||||
// keysyms::3270_PA1 => Key::Pa1,
|
||||
// keysyms::3270_PA2 => Key::Pa2,
|
||||
// keysyms::3270_PA3 => Key::Pa3,
|
||||
// keysyms::3270_Test => Key::Test,
|
||||
keysyms::_3270_Attn => Key::Attn,
|
||||
// keysyms::3270_CursorBlink => Key::CursorBlink,
|
||||
// keysyms::3270_AltCursor => Key::AltCursor,
|
||||
// keysyms::3270_KeyClick => Key::KeyClick,
|
||||
// keysyms::3270_Jump => Key::Jump,
|
||||
// keysyms::3270_Ident => Key::Ident,
|
||||
// keysyms::3270_Rule => Key::Rule,
|
||||
// keysyms::3270_Copy => Key::Copy,
|
||||
keysyms::_3270_Play => Key::Play,
|
||||
// keysyms::3270_Setup => Key::Setup,
|
||||
// keysyms::3270_Record => Key::Record,
|
||||
// keysyms::3270_ChangeScreen => Key::ChangeScreen,
|
||||
// keysyms::3270_DeleteWord => Key::DeleteWord,
|
||||
keysyms::_3270_ExSelect => Key::ExSel,
|
||||
keysyms::_3270_CursorSelect => Key::CrSel,
|
||||
keysyms::_3270_PrintScreen => Key::PrintScreen,
|
||||
keysyms::_3270_Enter => Key::Enter,
|
||||
|
||||
keysyms::space => Key::Space,
|
||||
// exclam..Sinh_kunddaliya
|
||||
|
||||
// XFree86
|
||||
// keysyms::XF86_ModeLock => Key::ModeLock,
|
||||
|
||||
// XFree86 - Backlight controls
|
||||
keysyms::XF86_MonBrightnessUp => Key::BrightnessUp,
|
||||
keysyms::XF86_MonBrightnessDown => Key::BrightnessDown,
|
||||
// keysyms::XF86_KbdLightOnOff => Key::LightOnOff,
|
||||
// keysyms::XF86_KbdBrightnessUp => Key::KeyboardBrightnessUp,
|
||||
// keysyms::XF86_KbdBrightnessDown => Key::KeyboardBrightnessDown,
|
||||
|
||||
// XFree86 - "Internet"
|
||||
keysyms::XF86_Standby => Key::Standby,
|
||||
keysyms::XF86_AudioLowerVolume => Key::AudioVolumeDown,
|
||||
keysyms::XF86_AudioRaiseVolume => Key::AudioVolumeUp,
|
||||
keysyms::XF86_AudioPlay => Key::MediaPlay,
|
||||
keysyms::XF86_AudioStop => Key::MediaStop,
|
||||
keysyms::XF86_AudioPrev => Key::MediaTrackPrevious,
|
||||
keysyms::XF86_AudioNext => Key::MediaTrackNext,
|
||||
keysyms::XF86_HomePage => Key::BrowserHome,
|
||||
keysyms::XF86_Mail => Key::LaunchMail,
|
||||
// keysyms::XF86_Start => Key::Start,
|
||||
keysyms::XF86_Search => Key::BrowserSearch,
|
||||
keysyms::XF86_AudioRecord => Key::MediaRecord,
|
||||
|
||||
// XFree86 - PDA
|
||||
keysyms::XF86_Calculator => Key::LaunchApplication2,
|
||||
// keysyms::XF86_Memo => Key::Memo,
|
||||
// keysyms::XF86_ToDoList => Key::ToDoList,
|
||||
keysyms::XF86_Calendar => Key::LaunchCalendar,
|
||||
keysyms::XF86_PowerDown => Key::Power,
|
||||
// keysyms::XF86_ContrastAdjust => Key::AdjustContrast,
|
||||
// keysyms::XF86_RockerUp => Key::RockerUp,
|
||||
// keysyms::XF86_RockerDown => Key::RockerDown,
|
||||
// keysyms::XF86_RockerEnter => Key::RockerEnter,
|
||||
|
||||
// XFree86 - More "Internet"
|
||||
keysyms::XF86_Back => Key::BrowserBack,
|
||||
keysyms::XF86_Forward => Key::BrowserForward,
|
||||
// keysyms::XF86_Stop => Key::Stop,
|
||||
keysyms::XF86_Refresh => Key::BrowserRefresh,
|
||||
keysyms::XF86_PowerOff => Key::Power,
|
||||
keysyms::XF86_WakeUp => Key::WakeUp,
|
||||
keysyms::XF86_Eject => Key::Eject,
|
||||
keysyms::XF86_ScreenSaver => Key::LaunchScreenSaver,
|
||||
keysyms::XF86_WWW => Key::LaunchWebBrowser,
|
||||
keysyms::XF86_Sleep => Key::Standby,
|
||||
keysyms::XF86_Favorites => Key::BrowserFavorites,
|
||||
keysyms::XF86_AudioPause => Key::MediaPause,
|
||||
// keysyms::XF86_AudioMedia => Key::AudioMedia,
|
||||
keysyms::XF86_MyComputer => Key::LaunchApplication1,
|
||||
// keysyms::XF86_VendorHome => Key::VendorHome,
|
||||
// keysyms::XF86_LightBulb => Key::LightBulb,
|
||||
// keysyms::XF86_Shop => Key::BrowserShop,
|
||||
// keysyms::XF86_History => Key::BrowserHistory,
|
||||
// keysyms::XF86_OpenURL => Key::OpenUrl,
|
||||
// keysyms::XF86_AddFavorite => Key::AddFavorite,
|
||||
// keysyms::XF86_HotLinks => Key::HotLinks,
|
||||
// keysyms::XF86_BrightnessAdjust => Key::BrightnessAdjust,
|
||||
// keysyms::XF86_Finance => Key::BrowserFinance,
|
||||
// keysyms::XF86_Community => Key::BrowserCommunity,
|
||||
keysyms::XF86_AudioRewind => Key::MediaRewind,
|
||||
// keysyms::XF86_BackForward => Key::???,
|
||||
// XF86_Launch0..XF86_LaunchF
|
||||
|
||||
// XF86_ApplicationLeft..XF86_CD
|
||||
keysyms::XF86_Calculater => Key::LaunchApplication2, // Nice typo, libxkbcommon :)
|
||||
// XF86_Clear
|
||||
keysyms::XF86_Close => Key::Close,
|
||||
keysyms::XF86_Copy => Key::Copy,
|
||||
keysyms::XF86_Cut => Key::Cut,
|
||||
// XF86_Display..XF86_Documents
|
||||
keysyms::XF86_Excel => Key::LaunchSpreadsheet,
|
||||
// XF86_Explorer..XF86iTouch
|
||||
keysyms::XF86_LogOff => Key::LogOff,
|
||||
// XF86_Market..XF86_MenuPB
|
||||
keysyms::XF86_MySites => Key::BrowserFavorites,
|
||||
keysyms::XF86_New => Key::New,
|
||||
// XF86_News..XF86_OfficeHome
|
||||
keysyms::XF86_Open => Key::Open,
|
||||
// XF86_Option
|
||||
keysyms::XF86_Paste => Key::Paste,
|
||||
keysyms::XF86_Phone => Key::LaunchPhone,
|
||||
// XF86_Q
|
||||
keysyms::XF86_Reply => Key::MailReply,
|
||||
keysyms::XF86_Reload => Key::BrowserRefresh,
|
||||
// XF86_RotateWindows..XF86_RotationKB
|
||||
keysyms::XF86_Save => Key::Save,
|
||||
// XF86_ScrollUp..XF86_ScrollClick
|
||||
keysyms::XF86_Send => Key::MailSend,
|
||||
keysyms::XF86_Spell => Key::SpellCheck,
|
||||
keysyms::XF86_SplitScreen => Key::SplitScreenToggle,
|
||||
// XF86_Support..XF86_User2KB
|
||||
keysyms::XF86_Video => Key::LaunchMediaPlayer,
|
||||
// XF86_WheelButton
|
||||
keysyms::XF86_Word => Key::LaunchWordProcessor,
|
||||
// XF86_Xfer
|
||||
keysyms::XF86_ZoomIn => Key::ZoomIn,
|
||||
keysyms::XF86_ZoomOut => Key::ZoomOut,
|
||||
|
||||
// XF86_Away..XF86_Messenger
|
||||
keysyms::XF86_WebCam => Key::LaunchWebCam,
|
||||
keysyms::XF86_MailForward => Key::MailForward,
|
||||
// XF86_Pictures
|
||||
keysyms::XF86_Music => Key::LaunchMusicPlayer,
|
||||
|
||||
// XF86_Battery..XF86_UWB
|
||||
//
|
||||
keysyms::XF86_AudioForward => Key::MediaFastForward,
|
||||
// XF86_AudioRepeat
|
||||
keysyms::XF86_AudioRandomPlay => Key::RandomToggle,
|
||||
keysyms::XF86_Subtitle => Key::Subtitle,
|
||||
keysyms::XF86_AudioCycleTrack => Key::MediaAudioTrack,
|
||||
// XF86_CycleAngle..XF86_Blue
|
||||
//
|
||||
keysyms::XF86_Suspend => Key::Standby,
|
||||
keysyms::XF86_Hibernate => Key::Hibernate,
|
||||
// XF86_TouchpadToggle..XF86_TouchpadOff
|
||||
//
|
||||
keysyms::XF86_AudioMute => Key::AudioVolumeMute,
|
||||
|
||||
// XF86_Switch_VT_1..XF86_Switch_VT_12
|
||||
|
||||
// XF86_Ungrab..XF86_ClearGrab
|
||||
keysyms::XF86_Next_VMode => Key::VideoModeNext,
|
||||
// keysyms::XF86_Prev_VMode => Key::VideoModePrevious,
|
||||
// XF86_LogWindowTree..XF86_LogGrabInfo
|
||||
|
||||
// SunFA_Grave..SunFA_Cedilla
|
||||
|
||||
// keysyms::SunF36 => Key::F36 | Key::F11,
|
||||
// keysyms::SunF37 => Key::F37 | Key::F12,
|
||||
|
||||
// keysyms::SunSys_Req => Key::PrintScreen,
|
||||
// The next couple of xkb (until SunStop) are already handled.
|
||||
// SunPrint_Screen..SunPageDown
|
||||
|
||||
// SunUndo..SunFront
|
||||
keysyms::SUN_Copy => Key::Copy,
|
||||
keysyms::SUN_Open => Key::Open,
|
||||
keysyms::SUN_Paste => Key::Paste,
|
||||
keysyms::SUN_Cut => Key::Cut,
|
||||
|
||||
// SunPowerSwitch
|
||||
keysyms::SUN_AudioLowerVolume => Key::AudioVolumeDown,
|
||||
keysyms::SUN_AudioMute => Key::AudioVolumeMute,
|
||||
keysyms::SUN_AudioRaiseVolume => Key::AudioVolumeUp,
|
||||
// SUN_VideoDegauss
|
||||
keysyms::SUN_VideoLowerBrightness => Key::BrightnessDown,
|
||||
keysyms::SUN_VideoRaiseBrightness => Key::BrightnessUp,
|
||||
// SunPowerSwitchShift
|
||||
//
|
||||
0 => Key::Unidentified(NativeKey::Unidentified),
|
||||
_ => Key::Unidentified(NativeKey::Xkb(keysym)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keysym_location(keysym: u32) -> KeyLocation {
|
||||
use xkbcommon_dl::keysyms;
|
||||
match keysym {
|
||||
keysyms::Shift_L
|
||||
| keysyms::Control_L
|
||||
| keysyms::Meta_L
|
||||
| keysyms::Alt_L
|
||||
| keysyms::Super_L
|
||||
| keysyms::Hyper_L => KeyLocation::Left,
|
||||
keysyms::Shift_R
|
||||
| keysyms::Control_R
|
||||
| keysyms::Meta_R
|
||||
| keysyms::Alt_R
|
||||
| keysyms::Super_R
|
||||
| keysyms::Hyper_R => KeyLocation::Right,
|
||||
keysyms::KP_0
|
||||
| keysyms::KP_1
|
||||
| keysyms::KP_2
|
||||
| keysyms::KP_3
|
||||
| keysyms::KP_4
|
||||
| keysyms::KP_5
|
||||
| keysyms::KP_6
|
||||
| keysyms::KP_7
|
||||
| keysyms::KP_8
|
||||
| keysyms::KP_9
|
||||
| keysyms::KP_Space
|
||||
| keysyms::KP_Tab
|
||||
| keysyms::KP_Enter
|
||||
| keysyms::KP_F1
|
||||
| keysyms::KP_F2
|
||||
| keysyms::KP_F3
|
||||
| keysyms::KP_F4
|
||||
| keysyms::KP_Home
|
||||
| keysyms::KP_Left
|
||||
| keysyms::KP_Up
|
||||
| keysyms::KP_Right
|
||||
| keysyms::KP_Down
|
||||
| keysyms::KP_Page_Up
|
||||
| keysyms::KP_Page_Down
|
||||
| keysyms::KP_End
|
||||
| keysyms::KP_Begin
|
||||
| keysyms::KP_Insert
|
||||
| keysyms::KP_Delete
|
||||
| keysyms::KP_Equal
|
||||
| keysyms::KP_Multiply
|
||||
| keysyms::KP_Add
|
||||
| keysyms::KP_Separator
|
||||
| keysyms::KP_Subtract
|
||||
| keysyms::KP_Decimal
|
||||
| keysyms::KP_Divide => KeyLocation::Numpad,
|
||||
_ => KeyLocation::Standard,
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
pub mod keymap;
|
||||
pub mod xkb_state;
|
||||
pub mod xkb;
|
||||
|
||||
124
src/platform_impl/linux/common/xkb/compose.rs
Normal file
124
src/platform_impl/linux/common/xkb/compose.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
//! XKB compose handling.
|
||||
|
||||
use std::env;
|
||||
use std::ffi::CString;
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use super::XkbContext;
|
||||
use super::XKBCH;
|
||||
use smol_str::SmolStr;
|
||||
use xkbcommon_dl::{
|
||||
xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags,
|
||||
xkb_compose_status, xkb_compose_table, xkb_keysym_t,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbComposeTable {
|
||||
table: NonNull<xkb_compose_table>,
|
||||
}
|
||||
|
||||
impl XkbComposeTable {
|
||||
pub fn new(context: &XkbContext) -> Option<Self> {
|
||||
let locale = env::var_os("LC_ALL")
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LC_CTYPE"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LANG"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.unwrap_or_else(|| "C".into());
|
||||
let locale = CString::new(locale.into_vec()).unwrap();
|
||||
|
||||
let table = unsafe {
|
||||
(XKBCH.xkb_compose_table_new_from_locale)(
|
||||
context.as_ptr(),
|
||||
locale.as_ptr(),
|
||||
xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
let table = NonNull::new(table)?;
|
||||
Some(Self { table })
|
||||
}
|
||||
|
||||
/// Create new state with the given compose table.
|
||||
pub fn new_state(&self) -> Option<XkbComposeState> {
|
||||
let state = unsafe {
|
||||
(XKBCH.xkb_compose_state_new)(
|
||||
self.table.as_ptr(),
|
||||
xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
let state = NonNull::new(state)?;
|
||||
Some(XkbComposeState { state })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for XkbComposeTable {
|
||||
type Target = NonNull<xkb_compose_table>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.table
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbComposeTable {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBCH.xkb_compose_table_unref)(self.table.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbComposeState {
|
||||
state: NonNull<xkb_compose_state>,
|
||||
}
|
||||
|
||||
impl XkbComposeState {
|
||||
pub fn get_string(&mut self, scratch_buffer: &mut Vec<u8>) -> Option<SmolStr> {
|
||||
super::make_string_with(scratch_buffer, |ptr, len| unsafe {
|
||||
(XKBCH.xkb_compose_state_get_utf8)(self.state.as_ptr(), ptr, len)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn feed(&mut self, keysym: xkb_keysym_t) -> ComposeStatus {
|
||||
let feed_result = unsafe { (XKBCH.xkb_compose_state_feed)(self.state.as_ptr(), keysym) };
|
||||
match feed_result {
|
||||
xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED => ComposeStatus::Ignored,
|
||||
xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED => {
|
||||
ComposeStatus::Accepted(self.status())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
unsafe {
|
||||
(XKBCH.xkb_compose_state_reset)(self.state.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn status(&mut self) -> xkb_compose_status {
|
||||
unsafe { (XKBCH.xkb_compose_state_get_status)(self.state.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbComposeState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBCH.xkb_compose_state_unref)(self.state.as_ptr());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ComposeStatus {
|
||||
Accepted(xkb_compose_status),
|
||||
Ignored,
|
||||
None,
|
||||
}
|
||||
1051
src/platform_impl/linux/common/xkb/keymap.rs
Normal file
1051
src/platform_impl/linux/common/xkb/keymap.rs
Normal file
File diff suppressed because it is too large
Load Diff
465
src/platform_impl/linux/common/xkb/mod.rs
Normal file
465
src/platform_impl/linux/common/xkb/mod.rs
Normal file
@@ -0,0 +1,465 @@
|
||||
use std::ops::Deref;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use log::warn;
|
||||
use once_cell::sync::Lazy;
|
||||
use smol_str::SmolStr;
|
||||
#[cfg(wayland_platform)]
|
||||
use std::os::unix::io::OwnedFd;
|
||||
use xkbcommon_dl::{
|
||||
self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle,
|
||||
xkbcommon_handle, XkbCommon, XkbCommonCompose,
|
||||
};
|
||||
#[cfg(x11_platform)]
|
||||
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
|
||||
|
||||
use crate::event::ElementState;
|
||||
use crate::event::KeyEvent;
|
||||
use crate::keyboard::{Key, KeyLocation};
|
||||
use crate::platform_impl::KeyEventExtra;
|
||||
|
||||
mod compose;
|
||||
mod keymap;
|
||||
mod state;
|
||||
|
||||
use compose::{ComposeStatus, XkbComposeState, XkbComposeTable};
|
||||
use keymap::XkbKeymap;
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub use keymap::raw_keycode_to_physicalkey;
|
||||
pub use keymap::{physicalkey_to_scancode, scancode_to_keycode};
|
||||
pub use state::XkbState;
|
||||
|
||||
// TODO: Wire this up without using a static `AtomicBool`.
|
||||
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle);
|
||||
static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle);
|
||||
#[cfg(feature = "x11")]
|
||||
static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle);
|
||||
|
||||
#[inline(always)]
|
||||
pub fn reset_dead_keys() {
|
||||
RESET_DEAD_KEYS.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// libxkbcommon is not available
|
||||
XKBNotFound,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Context {
|
||||
// NOTE: field order matters.
|
||||
#[cfg(x11_platform)]
|
||||
pub core_keyboard_id: i32,
|
||||
state: Option<XkbState>,
|
||||
keymap: Option<XkbKeymap>,
|
||||
compose_state1: Option<XkbComposeState>,
|
||||
compose_state2: Option<XkbComposeState>,
|
||||
_compose_table: Option<XkbComposeTable>,
|
||||
context: XkbContext,
|
||||
scratch_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
if xkb::xkbcommon_option().is_none() {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
|
||||
let context = XkbContext::new()?;
|
||||
let mut compose_table = XkbComposeTable::new(&context);
|
||||
let mut compose_state1 = compose_table.as_ref().and_then(|table| table.new_state());
|
||||
let mut compose_state2 = compose_table.as_ref().and_then(|table| table.new_state());
|
||||
|
||||
// Disable compose if anything compose related failed to initialize.
|
||||
if compose_table.is_none() || compose_state1.is_none() || compose_state2.is_none() {
|
||||
compose_state2 = None;
|
||||
compose_state1 = None;
|
||||
compose_table = None;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
state: None,
|
||||
keymap: None,
|
||||
compose_state1,
|
||||
compose_state2,
|
||||
#[cfg(x11_platform)]
|
||||
core_keyboard_id: 0,
|
||||
_compose_table: compose_table,
|
||||
context,
|
||||
scratch_buffer: Vec::with_capacity(8),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub fn from_x11_xkb(xcb: *mut xcb_connection_t) -> Result<Self, Error> {
|
||||
let result = unsafe {
|
||||
(XKBXH.xkb_x11_setup_xkb_extension)(
|
||||
xcb,
|
||||
1,
|
||||
2,
|
||||
xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if result != 1 {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
|
||||
let mut this = Self::new()?;
|
||||
this.core_keyboard_id = unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(xcb) };
|
||||
this.set_keymap_from_x11(xcb);
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
pub fn state_mut(&mut self) -> Option<&mut XkbState> {
|
||||
self.state.as_mut()
|
||||
}
|
||||
|
||||
pub fn keymap_mut(&mut self) -> Option<&mut XkbKeymap> {
|
||||
self.keymap.as_mut()
|
||||
}
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) {
|
||||
let keymap = XkbKeymap::from_fd(&self.context, fd, size);
|
||||
let state = keymap.as_ref().and_then(XkbState::new_wayland);
|
||||
if keymap.is_none() || state.is_none() {
|
||||
warn!("failed to update xkb keymap");
|
||||
}
|
||||
self.state = state;
|
||||
self.keymap = keymap;
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) {
|
||||
let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id);
|
||||
let state = keymap
|
||||
.as_ref()
|
||||
.and_then(|keymap| XkbState::new_x11(xcb, keymap));
|
||||
if keymap.is_none() || state.is_none() {
|
||||
warn!("failed to update xkb keymap");
|
||||
}
|
||||
self.state = state;
|
||||
self.keymap = keymap;
|
||||
}
|
||||
|
||||
/// Key builder context with the user provided xkb state.
|
||||
pub fn key_context(&mut self) -> Option<KeyContext<'_>> {
|
||||
let state = self.state.as_mut()?;
|
||||
let keymap = self.keymap.as_mut()?;
|
||||
let compose_state1 = self.compose_state1.as_mut();
|
||||
let compose_state2 = self.compose_state2.as_mut();
|
||||
let scratch_buffer = &mut self.scratch_buffer;
|
||||
Some(KeyContext {
|
||||
state,
|
||||
keymap,
|
||||
compose_state1,
|
||||
compose_state2,
|
||||
scratch_buffer,
|
||||
})
|
||||
}
|
||||
|
||||
/// Key builder context with the user provided xkb state.
|
||||
///
|
||||
/// Should be used when the original context must not be altered.
|
||||
#[cfg(x11_platform)]
|
||||
pub fn key_context_with_state<'a>(
|
||||
&'a mut self,
|
||||
state: &'a mut XkbState,
|
||||
) -> Option<KeyContext<'a>> {
|
||||
let keymap = self.keymap.as_mut()?;
|
||||
let compose_state1 = self.compose_state1.as_mut();
|
||||
let compose_state2 = self.compose_state2.as_mut();
|
||||
let scratch_buffer = &mut self.scratch_buffer;
|
||||
Some(KeyContext {
|
||||
state,
|
||||
keymap,
|
||||
compose_state1,
|
||||
compose_state2,
|
||||
scratch_buffer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KeyContext<'a> {
|
||||
pub state: &'a mut XkbState,
|
||||
pub keymap: &'a mut XkbKeymap,
|
||||
compose_state1: Option<&'a mut XkbComposeState>,
|
||||
compose_state2: Option<&'a mut XkbComposeState>,
|
||||
scratch_buffer: &'a mut Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> KeyContext<'a> {
|
||||
pub fn process_key_event(
|
||||
&mut self,
|
||||
keycode: u32,
|
||||
state: ElementState,
|
||||
repeat: bool,
|
||||
) -> KeyEvent {
|
||||
let mut event =
|
||||
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
|
||||
let physical_key = keymap::raw_keycode_to_physicalkey(keycode);
|
||||
let (logical_key, location) = event.key();
|
||||
let text = event.text();
|
||||
let (key_without_modifiers, _) = event.key_without_modifiers();
|
||||
let text_with_all_modifiers = event.text_with_all_modifiers();
|
||||
|
||||
let platform_specific = KeyEventExtra {
|
||||
text_with_all_modifiers,
|
||||
key_without_modifiers,
|
||||
};
|
||||
|
||||
KeyEvent {
|
||||
physical_key,
|
||||
logical_key,
|
||||
text,
|
||||
location,
|
||||
state,
|
||||
repeat,
|
||||
platform_specific,
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
|
||||
self.scratch_buffer.clear();
|
||||
self.scratch_buffer.reserve(8);
|
||||
loop {
|
||||
let bytes_written = unsafe {
|
||||
(XKBH.xkb_keysym_to_utf8)(
|
||||
keysym,
|
||||
self.scratch_buffer.as_mut_ptr().cast(),
|
||||
self.scratch_buffer.capacity(),
|
||||
)
|
||||
};
|
||||
if bytes_written == 0 {
|
||||
return None;
|
||||
} else if bytes_written == -1 {
|
||||
self.scratch_buffer.reserve(8);
|
||||
} else {
|
||||
unsafe {
|
||||
self.scratch_buffer
|
||||
.set_len(bytes_written.try_into().unwrap())
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the null-terminator
|
||||
self.scratch_buffer.pop();
|
||||
byte_slice_to_smol_str(self.scratch_buffer)
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyEventResults<'a, 'b> {
|
||||
context: &'a mut KeyContext<'b>,
|
||||
keycode: u32,
|
||||
keysym: u32,
|
||||
compose: ComposeStatus,
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventResults<'a, 'b> {
|
||||
fn new(context: &'a mut KeyContext<'b>, keycode: u32, compose: bool) -> Self {
|
||||
let keysym = context.state.get_one_sym_raw(keycode);
|
||||
|
||||
let compose = if let Some(state) = context.compose_state1.as_mut().filter(|_| compose) {
|
||||
if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) {
|
||||
state.reset();
|
||||
context.compose_state2.as_mut().unwrap().reset();
|
||||
}
|
||||
state.feed(keysym)
|
||||
} else {
|
||||
ComposeStatus::None
|
||||
};
|
||||
|
||||
KeyEventResults {
|
||||
context,
|
||||
keycode,
|
||||
keysym,
|
||||
compose,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(&mut self) -> (Key, KeyLocation) {
|
||||
let (key, location) = match self.keysym_to_key(self.keysym) {
|
||||
Ok(known) => return known,
|
||||
Err(undefined) => undefined,
|
||||
};
|
||||
|
||||
if let ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSING) = self.compose {
|
||||
let compose_state = self.context.compose_state2.as_mut().unwrap();
|
||||
// When pressing a dead key twice, the non-combining variant of that character will
|
||||
// be produced. Since this function only concerns itself with a single keypress, we
|
||||
// simulate this double press here by feeding the keysym to the compose state
|
||||
// twice.
|
||||
|
||||
compose_state.feed(self.keysym);
|
||||
if matches!(compose_state.feed(self.keysym), ComposeStatus::Accepted(_)) {
|
||||
// Extracting only a single `char` here *should* be fine, assuming that no
|
||||
// dead key's non-combining variant ever occupies more than one `char`.
|
||||
let text = compose_state.get_string(self.context.scratch_buffer);
|
||||
let key = Key::Dead(text.and_then(|s| s.chars().next()));
|
||||
(key, location)
|
||||
} else {
|
||||
(key, location)
|
||||
}
|
||||
} else {
|
||||
let key = self
|
||||
.composed_text()
|
||||
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
|
||||
.map(Key::Character)
|
||||
.unwrap_or(key);
|
||||
(key, location)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) {
|
||||
// This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it.
|
||||
let layout = self.context.state.layout(self.keycode);
|
||||
let keysym = self
|
||||
.context
|
||||
.keymap
|
||||
.first_keysym_by_level(layout, self.keycode);
|
||||
|
||||
match self.keysym_to_key(keysym) {
|
||||
Ok((key, location)) => (key, location),
|
||||
Err((key, location)) => {
|
||||
let key = self
|
||||
.context
|
||||
.keysym_to_utf8_raw(keysym)
|
||||
.map(Key::Character)
|
||||
.unwrap_or(key);
|
||||
(key, location)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
|
||||
let location = keymap::keysym_location(keysym);
|
||||
let key = keymap::keysym_to_key(keysym);
|
||||
if matches!(key, Key::Unidentified(_)) {
|
||||
Err((key, location))
|
||||
} else {
|
||||
Ok((key, location))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(&mut self) -> Option<SmolStr> {
|
||||
self.composed_text()
|
||||
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
|
||||
}
|
||||
|
||||
// The current behaviour makes it so composing a character overrides attempts to input a
|
||||
// control character with the `Ctrl` key. We can potentially add a configuration option
|
||||
// if someone specifically wants the oppsite behaviour.
|
||||
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
|
||||
match self.composed_text() {
|
||||
Ok(text) => text,
|
||||
Err(_) => self
|
||||
.context
|
||||
.state
|
||||
.get_utf8_raw(self.keycode, self.context.scratch_buffer),
|
||||
}
|
||||
}
|
||||
|
||||
fn composed_text(&mut self) -> Result<Option<SmolStr>, ()> {
|
||||
match self.compose {
|
||||
ComposeStatus::Accepted(status) => match status {
|
||||
xkb_compose_status::XKB_COMPOSE_COMPOSED => {
|
||||
let state = self.context.compose_state1.as_mut().unwrap();
|
||||
Ok(state.get_string(self.context.scratch_buffer))
|
||||
}
|
||||
xkb_compose_status::XKB_COMPOSE_COMPOSING
|
||||
| xkb_compose_status::XKB_COMPOSE_CANCELLED => Ok(None),
|
||||
xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()),
|
||||
},
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbContext {
|
||||
context: NonNull<xkb_context>,
|
||||
}
|
||||
|
||||
impl XkbContext {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let context = unsafe { (XKBH.xkb_context_new)(xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
|
||||
|
||||
let context = match NonNull::new(context) {
|
||||
Some(context) => context,
|
||||
None => return Err(Error::XKBNotFound),
|
||||
};
|
||||
|
||||
Ok(Self { context })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbContext {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBH.xkb_context_unref)(self.context.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for XkbContext {
|
||||
type Target = NonNull<xkb_context>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.context
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and
|
||||
/// `xkb_state_key_get_utf8`.
|
||||
fn make_string_with<F>(scratch_buffer: &mut Vec<u8>, mut f: F) -> Option<SmolStr>
|
||||
where
|
||||
F: FnMut(*mut c_char, usize) -> i32,
|
||||
{
|
||||
let size = f(ptr::null_mut(), 0);
|
||||
if size == 0 {
|
||||
return None;
|
||||
}
|
||||
let size = usize::try_from(size).unwrap();
|
||||
scratch_buffer.clear();
|
||||
// The allocated buffer must include space for the null-terminator.
|
||||
scratch_buffer.reserve(size + 1);
|
||||
unsafe {
|
||||
let written = f(
|
||||
scratch_buffer.as_mut_ptr().cast(),
|
||||
scratch_buffer.capacity(),
|
||||
);
|
||||
if usize::try_from(written).unwrap() != size {
|
||||
// This will likely never happen.
|
||||
return None;
|
||||
}
|
||||
scratch_buffer.set_len(size);
|
||||
};
|
||||
|
||||
byte_slice_to_smol_str(scratch_buffer)
|
||||
}
|
||||
|
||||
// NOTE: This is track_caller so we can have more informative line numbers when logging
|
||||
#[track_caller]
|
||||
fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
|
||||
std::str::from_utf8(bytes)
|
||||
.map(SmolStr::new)
|
||||
.map_err(|e| {
|
||||
log::warn!(
|
||||
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
|
||||
bytes
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
189
src/platform_impl/linux/common/xkb/state.rs
Normal file
189
src/platform_impl/linux/common/xkb/state.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
//! XKB state.
|
||||
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use smol_str::SmolStr;
|
||||
#[cfg(x11_platform)]
|
||||
use x11_dl::xlib_xcb::xcb_connection_t;
|
||||
use xkbcommon_dl::{
|
||||
self as xkb, xkb_keycode_t, xkb_keysym_t, xkb_layout_index_t, xkb_state, xkb_state_component,
|
||||
};
|
||||
|
||||
use crate::platform_impl::common::xkb::keymap::XkbKeymap;
|
||||
#[cfg(x11_platform)]
|
||||
use crate::platform_impl::common::xkb::XKBXH;
|
||||
use crate::platform_impl::common::xkb::{make_string_with, XKBH};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbState {
|
||||
state: NonNull<xkb_state>,
|
||||
modifiers: ModifiersState,
|
||||
}
|
||||
|
||||
impl XkbState {
|
||||
#[cfg(wayland_platform)]
|
||||
pub fn new_wayland(keymap: &XkbKeymap) -> Option<Self> {
|
||||
let state = NonNull::new(unsafe { (XKBH.xkb_state_new)(keymap.as_ptr()) })?;
|
||||
Some(Self::new_inner(state))
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn new_x11(xcb: *mut xcb_connection_t, keymap: &XkbKeymap) -> Option<Self> {
|
||||
let state = unsafe {
|
||||
(XKBXH.xkb_x11_state_new_from_device)(keymap.as_ptr(), xcb, keymap._core_keyboard_id)
|
||||
};
|
||||
let state = NonNull::new(state)?;
|
||||
Some(Self::new_inner(state))
|
||||
}
|
||||
|
||||
fn new_inner(state: NonNull<xkb_state>) -> Self {
|
||||
let modifiers = ModifiersState::default();
|
||||
let mut this = Self { state, modifiers };
|
||||
this.reload_modifiers();
|
||||
this
|
||||
}
|
||||
|
||||
pub fn get_one_sym_raw(&mut self, keycode: xkb_keycode_t) -> xkb_keysym_t {
|
||||
unsafe { (XKBH.xkb_state_key_get_one_sym)(self.state.as_ptr(), keycode) }
|
||||
}
|
||||
|
||||
pub fn layout(&mut self, key: xkb_keycode_t) -> xkb_layout_index_t {
|
||||
unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) }
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_serialize_mods)(
|
||||
self.state.as_ptr(),
|
||||
xkb_state_component::XKB_STATE_MODS_DEPRESSED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_serialize_mods)(
|
||||
self.state.as_ptr(),
|
||||
xkb_state_component::XKB_STATE_MODS_LATCHED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_serialize_mods)(
|
||||
self.state.as_ptr(),
|
||||
xkb_state_component::XKB_STATE_MODS_LOCKED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_utf8_raw(
|
||||
&mut self,
|
||||
keycode: xkb_keycode_t,
|
||||
scratch_buffer: &mut Vec<u8>,
|
||||
) -> Option<SmolStr> {
|
||||
make_string_with(scratch_buffer, |ptr, len| unsafe {
|
||||
(XKBH.xkb_state_key_get_utf8)(self.state.as_ptr(), keycode, ptr, len)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn modifiers(&self) -> ModifiersState {
|
||||
self.modifiers
|
||||
}
|
||||
|
||||
pub fn update_modifiers(
|
||||
&mut self,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
depressed_group: u32,
|
||||
latched_group: u32,
|
||||
locked_group: u32,
|
||||
) {
|
||||
let mask = unsafe {
|
||||
(XKBH.xkb_state_update_mask)(
|
||||
self.state.as_ptr(),
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
depressed_group,
|
||||
latched_group,
|
||||
locked_group,
|
||||
)
|
||||
};
|
||||
|
||||
if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) {
|
||||
// Effective value of mods have changed, we need to update our state.
|
||||
self.reload_modifiers();
|
||||
}
|
||||
}
|
||||
|
||||
/// Reload the modifiers.
|
||||
fn reload_modifiers(&mut self) {
|
||||
self.modifiers.ctrl = self.mod_name_is_active(xkb::XKB_MOD_NAME_CTRL);
|
||||
self.modifiers.alt = self.mod_name_is_active(xkb::XKB_MOD_NAME_ALT);
|
||||
self.modifiers.shift = self.mod_name_is_active(xkb::XKB_MOD_NAME_SHIFT);
|
||||
self.modifiers.caps_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_CAPS);
|
||||
self.modifiers.logo = self.mod_name_is_active(xkb::XKB_MOD_NAME_LOGO);
|
||||
self.modifiers.num_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_NUM);
|
||||
}
|
||||
|
||||
/// Check if the modifier is active within xkb.
|
||||
fn mod_name_is_active(&mut self, name: &[u8]) -> bool {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_mod_name_is_active)(
|
||||
self.state.as_ptr(),
|
||||
name.as_ptr() as *const c_char,
|
||||
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
|
||||
) > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_unref)(self.state.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the current state of the keyboard modifiers
|
||||
///
|
||||
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
|
||||
///
|
||||
/// For some modifiers, this means that the key is currently pressed, others are toggled
|
||||
/// (like caps lock).
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ModifiersState {
|
||||
/// The "control" key
|
||||
pub ctrl: bool,
|
||||
/// The "alt" key
|
||||
pub alt: bool,
|
||||
/// The "shift" key
|
||||
pub shift: bool,
|
||||
/// The "Caps lock" key
|
||||
pub caps_lock: bool,
|
||||
/// The "logo" key
|
||||
///
|
||||
/// Also known as the "windows" key on most keyboards
|
||||
pub logo: bool,
|
||||
/// The "Num lock" key
|
||||
pub num_lock: bool,
|
||||
}
|
||||
|
||||
impl From<ModifiersState> for crate::keyboard::ModifiersState {
|
||||
fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState {
|
||||
let mut to_mods = crate::keyboard::ModifiersState::empty();
|
||||
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift);
|
||||
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl);
|
||||
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt);
|
||||
to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo);
|
||||
to_mods
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ use xkbcommon_dl::{
|
||||
XkbCommonCompose,
|
||||
};
|
||||
#[cfg(feature = "wayland")]
|
||||
use {memmap2::MmapOptions, wayland_backend::io_lifetimes::OwnedFd};
|
||||
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
|
||||
#[cfg(feature = "x11")]
|
||||
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::platform_impl::common::keymap;
|
||||
use crate::platform_impl::KeyEventExtra;
|
||||
use crate::{
|
||||
event::ElementState,
|
||||
keyboard::{Key, KeyCode, KeyLocation},
|
||||
keyboard::{Key, KeyLocation, PhysicalKey},
|
||||
};
|
||||
|
||||
// TODO: Wire this up without using a static `AtomicBool`.
|
||||
@@ -250,37 +250,43 @@ impl KbdState {
|
||||
.unwrap_or_else(|| "C".into());
|
||||
let locale = CString::new(locale.into_vec()).unwrap();
|
||||
|
||||
let compose_table = (XKBCH.xkb_compose_table_new_from_locale)(
|
||||
self.xkb_context,
|
||||
locale.as_ptr(),
|
||||
ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
|
||||
);
|
||||
let compose_table = unsafe {
|
||||
(XKBCH.xkb_compose_table_new_from_locale)(
|
||||
self.xkb_context,
|
||||
locale.as_ptr(),
|
||||
ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
if compose_table.is_null() {
|
||||
// init of compose table failed, continue without compose
|
||||
return;
|
||||
}
|
||||
|
||||
let compose_state = (XKBCH.xkb_compose_state_new)(
|
||||
compose_table,
|
||||
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
|
||||
);
|
||||
let compose_state = unsafe {
|
||||
(XKBCH.xkb_compose_state_new)(
|
||||
compose_table,
|
||||
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
if compose_state.is_null() {
|
||||
// init of compose state failed, continue without compose
|
||||
(XKBCH.xkb_compose_table_unref)(compose_table);
|
||||
unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) };
|
||||
return;
|
||||
}
|
||||
|
||||
let compose_state_2 = (XKBCH.xkb_compose_state_new)(
|
||||
compose_table,
|
||||
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
|
||||
);
|
||||
let compose_state_2 = unsafe {
|
||||
(XKBCH.xkb_compose_state_new)(
|
||||
compose_table,
|
||||
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
if compose_state_2.is_null() {
|
||||
// init of compose state failed, continue without compose
|
||||
(XKBCH.xkb_compose_table_unref)(compose_table);
|
||||
(XKBCH.xkb_compose_state_unref)(compose_state);
|
||||
unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) };
|
||||
unsafe { (XKBCH.xkb_compose_state_unref)(compose_state) };
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -296,62 +302,71 @@ impl KbdState {
|
||||
}
|
||||
|
||||
unsafe fn de_init(&mut self) {
|
||||
(XKBH.xkb_state_unref)(self.xkb_state);
|
||||
unsafe { (XKBH.xkb_state_unref)(self.xkb_state) };
|
||||
self.xkb_state = ptr::null_mut();
|
||||
(XKBH.xkb_keymap_unref)(self.xkb_keymap);
|
||||
unsafe { (XKBH.xkb_keymap_unref)(self.xkb_keymap) };
|
||||
self.xkb_keymap = ptr::null_mut();
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub unsafe fn init_with_x11_keymap(&mut self) {
|
||||
if !self.xkb_keymap.is_null() {
|
||||
self.de_init();
|
||||
unsafe { self.de_init() };
|
||||
}
|
||||
|
||||
// TODO: Support keyboards other than the "virtual core keyboard device".
|
||||
self.core_keyboard_id = (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection);
|
||||
let keymap = (XKBXH.xkb_x11_keymap_new_from_device)(
|
||||
self.xkb_context,
|
||||
self.xcb_connection,
|
||||
self.core_keyboard_id,
|
||||
xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
);
|
||||
self.core_keyboard_id =
|
||||
unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection) };
|
||||
let keymap = unsafe {
|
||||
(XKBXH.xkb_x11_keymap_new_from_device)(
|
||||
self.xkb_context,
|
||||
self.xcb_connection,
|
||||
self.core_keyboard_id,
|
||||
xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
if keymap.is_null() {
|
||||
panic!("Failed to get keymap from X11 server.");
|
||||
}
|
||||
|
||||
let state = (XKBXH.xkb_x11_state_new_from_device)(
|
||||
keymap,
|
||||
self.xcb_connection,
|
||||
self.core_keyboard_id,
|
||||
);
|
||||
self.post_init(state, keymap);
|
||||
let state = unsafe {
|
||||
(XKBXH.xkb_x11_state_new_from_device)(
|
||||
keymap,
|
||||
self.xcb_connection,
|
||||
self.core_keyboard_id,
|
||||
)
|
||||
};
|
||||
unsafe { self.post_init(state, keymap) };
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub unsafe fn init_with_fd(&mut self, fd: OwnedFd, size: usize) {
|
||||
if !self.xkb_keymap.is_null() {
|
||||
self.de_init();
|
||||
unsafe { self.de_init() };
|
||||
}
|
||||
|
||||
let map = MmapOptions::new()
|
||||
.len(size)
|
||||
.map_copy_read_only(&fd)
|
||||
.unwrap();
|
||||
let map = unsafe {
|
||||
MmapOptions::new()
|
||||
.len(size)
|
||||
.map_copy_read_only(&fd)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let keymap = (XKBH.xkb_keymap_new_from_string)(
|
||||
self.xkb_context,
|
||||
map.as_ptr() as *const _,
|
||||
ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
|
||||
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
);
|
||||
let keymap = unsafe {
|
||||
(XKBH.xkb_keymap_new_from_string)(
|
||||
self.xkb_context,
|
||||
map.as_ptr() as *const _,
|
||||
ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
|
||||
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
if keymap.is_null() {
|
||||
panic!("Received invalid keymap from compositor.");
|
||||
}
|
||||
|
||||
let state = (XKBH.xkb_state_new)(keymap);
|
||||
self.post_init(state, keymap);
|
||||
let state = unsafe { (XKBH.xkb_state_new)(keymap) };
|
||||
unsafe { self.post_init(state, keymap) };
|
||||
}
|
||||
|
||||
pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool {
|
||||
@@ -376,7 +391,7 @@ impl KbdState {
|
||||
) -> KeyEvent {
|
||||
let mut event =
|
||||
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
|
||||
let physical_key = event.keycode();
|
||||
let physical_key = event.physical_key();
|
||||
let (logical_key, location) = event.key();
|
||||
let text = event.text();
|
||||
let (key_without_modifiers, _) = event.key_without_modifiers();
|
||||
@@ -483,8 +498,8 @@ impl<'a> KeyEventResults<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn keycode(&mut self) -> KeyCode {
|
||||
keymap::raw_keycode_to_keycode(self.keycode)
|
||||
fn physical_key(&self) -> PhysicalKey {
|
||||
keymap::raw_keycode_to_physicalkey(self.keycode)
|
||||
}
|
||||
|
||||
pub fn key(&mut self) -> (Key, KeyLocation) {
|
||||
@@ -538,6 +553,7 @@ impl<'a> KeyEventResults<'a> {
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
self.keysym_to_key(keysym)
|
||||
.unwrap_or_else(|(key, location)| {
|
||||
(
|
||||
@@ -550,7 +566,7 @@ impl<'a> KeyEventResults<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn keysym_to_key(&mut self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
|
||||
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
|
||||
let location = super::keymap::keysym_location(keysym);
|
||||
let key = super::keymap::keysym_to_key(keysym);
|
||||
if matches!(key, Key::Unidentified(_)) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#[cfg(all(not(x11_platform), not(wayland_platform)))]
|
||||
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
|
||||
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{collections::VecDeque, env, fmt};
|
||||
@@ -11,7 +12,6 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
use once_cell::sync::Lazy;
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||
use smol_str::SmolStr;
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
@@ -19,16 +19,16 @@ use crate::platform::x11::XlibErrorHook;
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::{Event, KeyEvent},
|
||||
event::KeyEvent,
|
||||
event_loop::{
|
||||
AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
|
||||
EventLoopWindowTarget as RootELW,
|
||||
},
|
||||
icon::Icon,
|
||||
keyboard::{Key, KeyCode},
|
||||
keyboard::{Key, PhysicalKey},
|
||||
platform::{
|
||||
modifier_supplement::KeyEventExtModifierSupplement, pump_events::PumpStatus,
|
||||
scancode::KeyCodeExtScancode,
|
||||
scancode::PhysicalKeyExtScancode,
|
||||
},
|
||||
window::{
|
||||
ActivationToken, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme,
|
||||
@@ -178,9 +178,9 @@ pub enum DeviceId {
|
||||
impl DeviceId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
#[cfg(wayland_platform)]
|
||||
return DeviceId::Wayland(wayland::DeviceId::dummy());
|
||||
return DeviceId::Wayland(unsafe { wayland::DeviceId::dummy() });
|
||||
#[cfg(all(not(wayland_platform), x11_platform))]
|
||||
return DeviceId::X(x11::DeviceId::dummy());
|
||||
return DeviceId::X(unsafe { x11::DeviceId::dummy() });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ impl VideoMode {
|
||||
|
||||
#[inline]
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
x11_or_wayland!(match self; VideoMode(m) => m.monitor())
|
||||
x11_or_wayland!(match self; VideoMode(m) => m.monitor(); as MonitorHandle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,12 +316,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
match self {
|
||||
#[cfg(wayland_platform)]
|
||||
Self::Wayland(window) => window.id(),
|
||||
#[cfg(x11_platform)]
|
||||
Self::X(window) => window.id(),
|
||||
}
|
||||
x11_or_wayland!(match self; Window(w) => w.id())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -334,6 +329,11 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_transparent(transparent));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_blur(&self, blur: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_blur(blur));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
|
||||
@@ -444,6 +444,11 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show_window_menu(&self, position: Position) {
|
||||
x11_or_wayland!(match self; Window(w) => w.show_window_menu(position))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
|
||||
@@ -500,23 +505,13 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_level(&self, _level: WindowLevel) {
|
||||
match self {
|
||||
#[cfg(x11_platform)]
|
||||
Window::X(ref w) => w.set_window_level(_level),
|
||||
#[cfg(wayland_platform)]
|
||||
Window::Wayland(_) => (),
|
||||
}
|
||||
pub fn set_window_level(&self, level: WindowLevel) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_window_level(level))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
|
||||
match self {
|
||||
#[cfg(x11_platform)]
|
||||
Window::X(ref w) => w.set_window_icon(_window_icon),
|
||||
#[cfg(wayland_platform)]
|
||||
Window::Wayland(_) => (),
|
||||
}
|
||||
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_window_icon(window_icon.map(|icon| icon.inner)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -526,7 +521,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn reset_dead_keys(&self) {
|
||||
common::xkb_state::reset_dead_keys()
|
||||
common::xkb::reset_dead_keys()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -541,12 +536,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn focus_window(&self) {
|
||||
match self {
|
||||
#[cfg(x11_platform)]
|
||||
Window::X(ref w) => w.focus_window(),
|
||||
#[cfg(wayland_platform)]
|
||||
Window::Wayland(_) => (),
|
||||
}
|
||||
x11_or_wayland!(match self; Window(w) => w.focus_window())
|
||||
}
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type))
|
||||
@@ -564,18 +554,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||
match self {
|
||||
#[cfg(x11_platform)]
|
||||
Window::X(ref window) => {
|
||||
let current_monitor = MonitorHandle::X(window.current_monitor());
|
||||
Some(current_monitor)
|
||||
}
|
||||
#[cfg(wayland_platform)]
|
||||
Window::Wayland(ref window) => {
|
||||
let current_monitor = MonitorHandle::Wayland(window.current_monitor()?);
|
||||
Some(current_monitor)
|
||||
}
|
||||
}
|
||||
Some(x11_or_wayland!(match self; Window(w) => w.current_monitor()?; as MonitorHandle))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -598,25 +577,39 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
match self {
|
||||
#[cfg(x11_platform)]
|
||||
Window::X(ref window) => {
|
||||
let primary_monitor = MonitorHandle::X(window.primary_monitor());
|
||||
Some(primary_monitor)
|
||||
}
|
||||
#[cfg(wayland_platform)]
|
||||
Window::Wayland(ref window) => window.primary_monitor(),
|
||||
}
|
||||
Some(x11_or_wayland!(match self; Window(w) => w.primary_monitor()?; as MonitorHandle))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_04")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
x11_or_wayland!(match self; Window(window) => window.raw_window_handle())
|
||||
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
|
||||
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_04())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
x11_or_wayland!(match self; Window(window) => window.raw_display_handle())
|
||||
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
||||
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_05())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
|
||||
x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_05())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_06())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
||||
x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_06())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -629,6 +622,10 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(window) => window.theme())
|
||||
}
|
||||
|
||||
pub fn set_content_protected(&self, protected: bool) {
|
||||
x11_or_wayland!(match self; Window(window) => window.set_content_protected(protected))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_focus(&self) -> bool {
|
||||
x11_or_wayland!(match self; Window(window) => window.has_focus())
|
||||
@@ -660,13 +657,13 @@ impl KeyEventExtModifierSupplement for KeyEvent {
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyCodeExtScancode for KeyCode {
|
||||
fn from_scancode(scancode: u32) -> KeyCode {
|
||||
common::keymap::scancode_to_keycode(scancode)
|
||||
impl PhysicalKeyExtScancode for PhysicalKey {
|
||||
fn from_scancode(scancode: u32) -> PhysicalKey {
|
||||
common::xkb::scancode_to_keycode(scancode)
|
||||
}
|
||||
|
||||
fn to_scancode(self) -> Option<u32> {
|
||||
common::keymap::keycode_to_scancode(self)
|
||||
common::xkb::physicalkey_to_scancode(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,26 +681,31 @@ unsafe extern "C" fn x_error_callback(
|
||||
if let Ok(ref xconn) = *xconn_lock {
|
||||
// Call all the hooks.
|
||||
let mut error_handled = false;
|
||||
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
|
||||
for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() {
|
||||
error_handled |= hook(display as *mut _, event as *mut _);
|
||||
}
|
||||
|
||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||
// which do not require initialization.
|
||||
let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
|
||||
(xconn.xlib.XGetErrorText)(
|
||||
display,
|
||||
(*event).error_code as c_int,
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
buf.len() as c_int,
|
||||
);
|
||||
let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy();
|
||||
let mut buf: [MaybeUninit<c_char>; 1024] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||
unsafe {
|
||||
(xconn.xlib.XGetErrorText)(
|
||||
display,
|
||||
(*event).error_code as c_int,
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
buf.len() as c_int,
|
||||
)
|
||||
};
|
||||
let description =
|
||||
unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy();
|
||||
|
||||
let error = XError {
|
||||
description: description.into_owned(),
|
||||
error_code: (*event).error_code,
|
||||
request_code: (*event).request_code,
|
||||
minor_code: (*event).minor_code,
|
||||
let error = unsafe {
|
||||
XError {
|
||||
description: description.into_owned(),
|
||||
error_code: (*event).error_code,
|
||||
request_code: (*event).request_code,
|
||||
minor_code: (*event).minor_code,
|
||||
}
|
||||
};
|
||||
|
||||
// Don't log error.
|
||||
@@ -750,22 +752,48 @@ impl<T: 'static> EventLoop<T> {
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: Wayland first because of X11 could be present under wayland as well.
|
||||
#[cfg(wayland_platform)]
|
||||
if attributes.forced_backend == Some(Backend::Wayland)
|
||||
|| env::var("WAYLAND_DISPLAY").is_ok()
|
||||
{
|
||||
return EventLoop::new_wayland_any_thread().map_err(Into::into);
|
||||
}
|
||||
// NOTE: Wayland first because of X11 could be present under Wayland as well. Empty
|
||||
// variables are also treated as not set.
|
||||
let backend = match (
|
||||
attributes.forced_backend,
|
||||
env::var("WAYLAND_DISPLAY")
|
||||
.ok()
|
||||
.filter(|var| !var.is_empty())
|
||||
.or_else(|| env::var("WAYLAND_SOCKET").ok())
|
||||
.filter(|var| !var.is_empty())
|
||||
.is_some(),
|
||||
env::var("DISPLAY")
|
||||
.map(|var| !var.is_empty())
|
||||
.unwrap_or(false),
|
||||
) {
|
||||
// User is forcing a backend.
|
||||
(Some(backend), _, _) => backend,
|
||||
// Wayland is present.
|
||||
#[cfg(wayland_platform)]
|
||||
(None, true, _) => Backend::Wayland,
|
||||
// X11 is present.
|
||||
#[cfg(x11_platform)]
|
||||
(None, _, true) => Backend::X,
|
||||
// No backend is present.
|
||||
(_, wayland_display, x11_display) => {
|
||||
let msg = if wayland_display && !cfg!(wayland_platform) {
|
||||
"DISPLAY is not set; note: enable the `winit/wayland` feature to support Wayland"
|
||||
} else if x11_display && !cfg!(x11_platform) {
|
||||
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the `winit/x11` feature to support X11"
|
||||
} else {
|
||||
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
|
||||
};
|
||||
return Err(EventLoopError::Os(os_error!(OsError::Misc(msg))));
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
if attributes.forced_backend == Some(Backend::X) || env::var("DISPLAY").is_ok() {
|
||||
return Ok(EventLoop::new_x11_any_thread().unwrap());
|
||||
// Create the display based on the backend.
|
||||
match backend {
|
||||
#[cfg(wayland_platform)]
|
||||
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
|
||||
#[cfg(x11_platform)]
|
||||
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
|
||||
}
|
||||
|
||||
Err(EventLoopError::Os(os_error!(OsError::Misc(
|
||||
"neither WAYLAND_DISPLAY nor DISPLAY is set."
|
||||
))))
|
||||
}
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
@@ -774,10 +802,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
|
||||
fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> {
|
||||
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
|
||||
Ok(xconn) => xconn.clone(),
|
||||
Err(err) => return Err(err.clone()),
|
||||
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
|
||||
};
|
||||
|
||||
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
|
||||
@@ -789,21 +817,21 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(crate::event::Event<T>, &RootELW<T>),
|
||||
{
|
||||
self.run_ondemand(callback)
|
||||
self.run_on_demand(callback)
|
||||
}
|
||||
|
||||
pub fn run_ondemand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
|
||||
pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(crate::event::Event<T>, &RootELW<T>),
|
||||
{
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_ondemand(callback))
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback))
|
||||
}
|
||||
|
||||
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(crate::event::Event<T>, &RootELW<T>),
|
||||
{
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
|
||||
}
|
||||
@@ -813,6 +841,18 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsFd for EventLoop<T> {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRawFd for EventLoop<T> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopProxy<T> {
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event))
|
||||
@@ -843,61 +883,69 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
#[cfg(wayland_platform)]
|
||||
EventLoopWindowTarget::Wayland(ref evlp) => evlp
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::Wayland)
|
||||
.collect(),
|
||||
#[cfg(x11_platform)]
|
||||
EventLoopWindowTarget::X(ref evlp) => evlp
|
||||
.x_connection()
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::X)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
match *self {
|
||||
#[cfg(wayland_platform)]
|
||||
EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(),
|
||||
#[cfg(x11_platform)]
|
||||
EventLoopWindowTarget::X(ref evlp) => {
|
||||
let primary_monitor = MonitorHandle::X(evlp.x_connection().primary_monitor());
|
||||
Some(primary_monitor)
|
||||
evlp.available_monitors().map(MonitorHandle::X).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
|
||||
match *self {
|
||||
#[cfg(wayland_platform)]
|
||||
EventLoopWindowTarget::Wayland(_) => (),
|
||||
#[cfg(x11_platform)]
|
||||
EventLoopWindowTarget::X(ref evlp) => evlp.set_listen_device_events(_allowed),
|
||||
}
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
Some(
|
||||
x11_or_wayland!(match self; EventLoopWindowTarget(evlp) => evlp.primary_monitor()?; as MonitorHandle),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle())
|
||||
#[inline]
|
||||
pub fn listen_device_events(&self, allowed: DeviceEvents) {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.listen_device_events(allowed))
|
||||
}
|
||||
}
|
||||
|
||||
fn sticky_exit_callback<T, F>(
|
||||
evt: Event<T>,
|
||||
target: &RootELW<T>,
|
||||
control_flow: &mut ControlFlow,
|
||||
callback: &mut F,
|
||||
) where
|
||||
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
// make ControlFlow::ExitWithCode sticky by providing a dummy
|
||||
// control flow reference if it is already ExitWithCode.
|
||||
if let ControlFlow::ExitWithCode(code) = *control_flow {
|
||||
callback(evt, target, &mut ControlFlow::ExitWithCode(code))
|
||||
} else {
|
||||
callback(evt, target, control_flow)
|
||||
#[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())
|
||||
}
|
||||
|
||||
#[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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
//! The event-loop routines.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::io::Result as IOResult;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
|
||||
|
||||
use sctk::reexports::calloop;
|
||||
use sctk::reexports::calloop::Error as CalloopError;
|
||||
use sctk::reexports::calloop_wayland_source::WaylandSource;
|
||||
use sctk::reexports::client::globals;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource};
|
||||
use sctk::reexports::client::{Connection, QueueHandle};
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::error::{EventLoopError, OsError as RootOsError};
|
||||
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
|
||||
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
|
||||
use crate::event_loop::{
|
||||
ControlFlow, DeviceEvents, EventLoopWindowTarget as RootEventLoopWindowTarget,
|
||||
};
|
||||
use crate::platform::pump_events::PumpStatus;
|
||||
use crate::platform_impl::platform::min_timeout;
|
||||
use crate::platform_impl::platform::sticky_exit_callback;
|
||||
use crate::platform_impl::{EventLoopWindowTarget as PlatformEventLoopWindowTarget, OsError};
|
||||
|
||||
mod proxy;
|
||||
@@ -33,18 +33,15 @@ use sink::EventSink;
|
||||
|
||||
use super::state::{WindowCompositorUpdate, WinitState};
|
||||
use super::window::state::FrameCallbackState;
|
||||
use super::{DeviceId, WaylandError, WindowId};
|
||||
use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
|
||||
|
||||
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
|
||||
|
||||
/// The Wayland event loop.
|
||||
pub struct EventLoop<T: 'static> {
|
||||
/// Has `run` or `run_ondemand` 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,
|
||||
|
||||
/// The application's latest control_flow state
|
||||
control_flow: ControlFlow,
|
||||
|
||||
buffer_sink: EventSink,
|
||||
compositor_updates: Vec<WindowCompositorUpdate>,
|
||||
window_ids: Vec<WindowId>,
|
||||
@@ -104,7 +101,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
)?;
|
||||
|
||||
// Register Wayland source.
|
||||
let wayland_source = map_err!(WaylandSource::new(event_queue), WaylandError::Wire)?;
|
||||
let wayland_source = WaylandSource::new(connection.clone(), event_queue);
|
||||
let wayland_dispatcher =
|
||||
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state: &mut WinitState| {
|
||||
let result = queue.dispatch_pending(winit_state);
|
||||
@@ -166,13 +163,14 @@ impl<T: 'static> EventLoop<T> {
|
||||
wayland_dispatcher: wayland_dispatcher.clone(),
|
||||
event_loop_awakener,
|
||||
queue_handle,
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
exit: Cell::new(None),
|
||||
state: RefCell::new(winit_state),
|
||||
_marker: PhantomData,
|
||||
};
|
||||
|
||||
let event_loop = Self {
|
||||
loop_running: false,
|
||||
control_flow: ControlFlow::default(),
|
||||
compositor_updates: Vec::new(),
|
||||
buffer_sink: EventSink::default(),
|
||||
window_ids: Vec::new(),
|
||||
@@ -190,9 +188,9 @@ impl<T: 'static> EventLoop<T> {
|
||||
Ok(event_loop)
|
||||
}
|
||||
|
||||
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
|
||||
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
|
||||
{
|
||||
if self.loop_running {
|
||||
return Err(EventLoopError::AlreadyRunning);
|
||||
@@ -213,7 +211,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
};
|
||||
|
||||
// Applications aren't allowed to carry windows between separate
|
||||
// `run_ondemand` calls but if they have only just dropped their
|
||||
// `run_on_demand` calls but if they have only just dropped their
|
||||
// windows we need to make sure those last requests are sent to the
|
||||
// compositor.
|
||||
let _ = self.roundtrip().map_err(EventLoopError::Os);
|
||||
@@ -223,35 +221,24 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
|
||||
{
|
||||
if !self.loop_running {
|
||||
self.loop_running = true;
|
||||
|
||||
// Reset the internal state for the loop as we start running to
|
||||
// ensure consistent behaviour in case the loop runs and exits more
|
||||
// than once.
|
||||
self.control_flow = ControlFlow::Poll;
|
||||
|
||||
// Run the initial loop iteration.
|
||||
self.single_iteration(&mut callback, StartCause::Init);
|
||||
}
|
||||
|
||||
// Consider the possibility that the `StartCause::Init` iteration could
|
||||
// request to Exit.
|
||||
if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) {
|
||||
if !self.exiting() {
|
||||
self.poll_events_with_timeout(timeout, &mut callback);
|
||||
}
|
||||
if let ControlFlow::ExitWithCode(code) = self.control_flow {
|
||||
if let Some(code) = self.exit_code() {
|
||||
self.loop_running = false;
|
||||
|
||||
let mut dummy = self.control_flow;
|
||||
sticky_exit_callback(
|
||||
Event::LoopExiting,
|
||||
self.window_target(),
|
||||
&mut dummy,
|
||||
&mut callback,
|
||||
);
|
||||
callback(Event::LoopExiting, self.window_target());
|
||||
|
||||
PumpStatus::Exit(code)
|
||||
} else {
|
||||
@@ -261,62 +248,18 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
|
||||
where
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
|
||||
{
|
||||
let cause = loop {
|
||||
let start = Instant::now();
|
||||
|
||||
// TODO(rib): remove this workaround and instead make sure that the calloop
|
||||
// WaylandSource correctly implements the cooperative prepare_read protocol
|
||||
// that support multithreaded wayland clients that may all read from the
|
||||
// same socket.
|
||||
//
|
||||
// During the run of the user callback, some other code monitoring and reading the
|
||||
// Wayland socket may have been run (mesa for example does this with vsync), if that
|
||||
// is the case, some events may have been enqueued in our event queue.
|
||||
//
|
||||
// If some messages are there, the event loop needs to behave as if it was instantly
|
||||
// woken up by messages arriving from the Wayland socket, to avoid delaying the
|
||||
// dispatch of these events until we're woken up again.
|
||||
let instant_wakeup = {
|
||||
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
|
||||
let queue = wayland_source.queue();
|
||||
let state = match &mut self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => {
|
||||
window_target.state.get_mut()
|
||||
}
|
||||
#[cfg(x11_platform)]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match queue.dispatch_pending(state) {
|
||||
Ok(dispatched) => {
|
||||
state.dispatched_events |= !state.events_sink.is_empty()
|
||||
|| !state.window_compositor_updates.is_empty();
|
||||
dispatched > 0
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Error dispatching wayland queue: {}", error);
|
||||
self.control_flow = ControlFlow::ExitWithCode(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
timeout = if instant_wakeup {
|
||||
Some(Duration::ZERO)
|
||||
} else {
|
||||
let control_flow_timeout = match self.control_flow {
|
||||
timeout = {
|
||||
let control_flow_timeout = match self.control_flow() {
|
||||
ControlFlow::Wait => None,
|
||||
ControlFlow::Poll => Some(Duration::ZERO),
|
||||
ControlFlow::WaitUntil(wait_deadline) => {
|
||||
Some(wait_deadline.saturating_duration_since(start))
|
||||
}
|
||||
// This function shouldn't have to handle any requests to exit
|
||||
// the application (there should be no need to poll for events
|
||||
// if the application has requested to exit) so we consider
|
||||
// it a bug in the backend if we ever see `ExitWithCode` here.
|
||||
ControlFlow::ExitWithCode(_code) => unreachable!(),
|
||||
};
|
||||
min_timeout(control_flow_timeout, timeout)
|
||||
};
|
||||
@@ -324,7 +267,13 @@ impl<T: 'static> EventLoop<T> {
|
||||
// NOTE Ideally we should flush as the last thing we do before polling
|
||||
// to wait for events, and this should be done by the calloop
|
||||
// WaylandSource but we currently need to flush writes manually.
|
||||
let _ = self.connection.flush();
|
||||
//
|
||||
// Checking for flush error is essential to perform an exit with error, since
|
||||
// once we have a protocol error, we could get stuck retrying...
|
||||
if self.connection.flush().is_err() {
|
||||
self.set_exit_code(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(error) = self.loop_dispatch(timeout) {
|
||||
// NOTE We exit on errors from dispatches, since if we've got protocol error
|
||||
@@ -334,13 +283,13 @@ impl<T: 'static> EventLoop<T> {
|
||||
// with an API to do that via some event.
|
||||
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
|
||||
let exit_code = error.raw_os_error().unwrap_or(1);
|
||||
self.control_flow = ControlFlow::ExitWithCode(exit_code);
|
||||
self.set_exit_code(exit_code);
|
||||
return;
|
||||
}
|
||||
|
||||
// NB: `StartCause::Init` is handled as a special case and doesn't need
|
||||
// to be considered here
|
||||
let cause = match self.control_flow {
|
||||
let cause = match self.control_flow() {
|
||||
ControlFlow::Poll => StartCause::Poll,
|
||||
ControlFlow::Wait => StartCause::WaitCancelled {
|
||||
start,
|
||||
@@ -359,11 +308,6 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// This function shouldn't have to handle any requests to exit
|
||||
// the application (there should be no need to poll for events
|
||||
// if the application has requested to exit) so we consider
|
||||
// it a bug in the backend if we ever see `ExitWithCode` here.
|
||||
ControlFlow::ExitWithCode(_code) => unreachable!(),
|
||||
};
|
||||
|
||||
// Reduce spurious wake-ups.
|
||||
@@ -378,14 +322,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
self.single_iteration(&mut callback, cause);
|
||||
}
|
||||
|
||||
fn single_iteration<F>(&mut self, mut callback: &mut F, cause: StartCause)
|
||||
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
|
||||
where
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
|
||||
{
|
||||
// NOTE currently just indented to simplify the diff
|
||||
|
||||
let mut control_flow = self.control_flow;
|
||||
|
||||
// We retain these grow-only scratch buffers as part of the EventLoop
|
||||
// for the sake of avoiding lots of reallocs. We take them here to avoid
|
||||
// trying to mutably borrow `self` more than once and we swap them back
|
||||
@@ -394,33 +336,18 @@ impl<T: 'static> EventLoop<T> {
|
||||
let mut buffer_sink = std::mem::take(&mut self.buffer_sink);
|
||||
let mut window_ids = std::mem::take(&mut self.window_ids);
|
||||
|
||||
sticky_exit_callback(
|
||||
Event::NewEvents(cause),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
callback(Event::NewEvents(cause), &self.window_target);
|
||||
|
||||
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
|
||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
||||
if cause == StartCause::Init {
|
||||
sticky_exit_callback(
|
||||
Event::Resumed,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
callback(Event::Resumed, &self.window_target);
|
||||
}
|
||||
|
||||
// Handle pending user events. We don't need back buffer, since we can't dispatch
|
||||
// user events indirectly via callback to the user.
|
||||
for user_event in self.pending_user_events.borrow_mut().drain(..) {
|
||||
sticky_exit_callback(
|
||||
Event::UserEvent(user_event),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
callback(Event::UserEvent(user_event), &self.window_target);
|
||||
}
|
||||
|
||||
// Drain the pending compositor updates.
|
||||
@@ -428,22 +355,20 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
for mut compositor_update in compositor_updates.drain(..) {
|
||||
let window_id = compositor_update.window_id;
|
||||
if let Some(scale_factor) = compositor_update.scale_factor {
|
||||
let physical_size = self.with_state(|state| {
|
||||
if compositor_update.scale_changed {
|
||||
let (physical_size, scale_factor) = self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
// Set the new scale factor.
|
||||
window.set_scale_factor(scale_factor);
|
||||
let window_size = compositor_update.size.unwrap_or(window.inner_size());
|
||||
logical_to_physical_rounded(window_size, scale_factor)
|
||||
let window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
let scale_factor = window.scale_factor();
|
||||
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
|
||||
(size, scale_factor)
|
||||
});
|
||||
|
||||
// Stash the old window size.
|
||||
let old_physical_size = physical_size;
|
||||
|
||||
let new_inner_size = Arc::new(Mutex::new(physical_size));
|
||||
sticky_exit_callback(
|
||||
callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
@@ -454,36 +379,36 @@ impl<T: 'static> EventLoop<T> {
|
||||
},
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
let new_logical_size = physical_size.to_logical(scale_factor);
|
||||
|
||||
// Resize the window when user altered the size.
|
||||
if old_physical_size != physical_size {
|
||||
self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
window.resize(new_logical_size);
|
||||
});
|
||||
}
|
||||
|
||||
// Make it queue resize.
|
||||
compositor_update.size = Some(new_logical_size);
|
||||
let new_logical_size: LogicalSize<f64> =
|
||||
physical_size.to_logical(scale_factor);
|
||||
window.request_inner_size(new_logical_size.into());
|
||||
});
|
||||
|
||||
// Make it queue resize.
|
||||
compositor_update.resized = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(size) = compositor_update.size.take() {
|
||||
// NOTE: Rescale changed the physical size which winit operates in, thus we should
|
||||
// resize.
|
||||
if compositor_update.resized || compositor_update.scale_changed {
|
||||
let physical_size = self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
let physical_size = logical_to_physical_rounded(size, scale_factor);
|
||||
|
||||
// TODO could probably bring back size reporting optimization.
|
||||
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
|
||||
|
||||
// Mark the window as needed a redraw.
|
||||
state
|
||||
@@ -494,29 +419,25 @@ impl<T: 'static> EventLoop<T> {
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
|
||||
physical_size
|
||||
size
|
||||
});
|
||||
|
||||
sticky_exit_callback(
|
||||
callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
|
||||
if compositor_update.close_window {
|
||||
sticky_exit_callback(
|
||||
callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::CloseRequested,
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -527,7 +448,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
});
|
||||
for event in buffer_sink.drain() {
|
||||
let event = event.map_nonuser_event().unwrap();
|
||||
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
|
||||
callback(event, &self.window_target);
|
||||
}
|
||||
|
||||
// Handle non-synthetic events.
|
||||
@@ -536,7 +457,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
});
|
||||
for event in buffer_sink.drain() {
|
||||
let event = event.map_nonuser_event().unwrap();
|
||||
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
|
||||
callback(event, &self.window_target);
|
||||
}
|
||||
|
||||
// Collect the window ids
|
||||
@@ -544,46 +465,47 @@ impl<T: 'static> EventLoop<T> {
|
||||
window_ids.extend(state.window_requests.get_mut().keys());
|
||||
});
|
||||
|
||||
for window_id in window_ids.drain(..) {
|
||||
let request_redraw = self.with_state(|state| {
|
||||
for window_id in window_ids.iter() {
|
||||
let event = self.with_state(|state| {
|
||||
let window_requests = state.window_requests.get_mut();
|
||||
if window_requests.get(&window_id).unwrap().take_closed() {
|
||||
mem::drop(window_requests.remove(&window_id));
|
||||
mem::drop(state.windows.get_mut().remove(&window_id));
|
||||
false
|
||||
} else {
|
||||
let mut window = state
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
if window.frame_callback_state() == FrameCallbackState::Requested {
|
||||
false
|
||||
} else {
|
||||
// Reset the frame callbacks state.
|
||||
window.frame_callback_reset();
|
||||
let mut redraw_requested = window_requests
|
||||
.get(&window_id)
|
||||
.unwrap()
|
||||
.take_redraw_requested();
|
||||
|
||||
// Redraw the frame while at it.
|
||||
redraw_requested |= window.refresh_frame();
|
||||
|
||||
redraw_requested
|
||||
}
|
||||
if window_requests.get(window_id).unwrap().take_closed() {
|
||||
mem::drop(window_requests.remove(window_id));
|
||||
mem::drop(state.windows.get_mut().remove(window_id));
|
||||
return Some(WindowEvent::Destroyed);
|
||||
}
|
||||
|
||||
let mut window = state
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(window_id)
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
if window.frame_callback_state() == FrameCallbackState::Requested {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Reset the frame callbacks state.
|
||||
window.frame_callback_reset();
|
||||
let mut redraw_requested = window_requests
|
||||
.get(window_id)
|
||||
.unwrap()
|
||||
.take_redraw_requested();
|
||||
|
||||
// Redraw the frame while at it.
|
||||
redraw_requested |= window.refresh_frame();
|
||||
|
||||
redraw_requested.then_some(WindowEvent::RedrawRequested)
|
||||
});
|
||||
|
||||
if request_redraw {
|
||||
sticky_exit_callback(
|
||||
Event::RedrawRequested(crate::window::WindowId(window_id)),
|
||||
if let Some(event) = event {
|
||||
callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event,
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -594,14 +516,44 @@ impl<T: 'static> EventLoop<T> {
|
||||
});
|
||||
|
||||
// This is always the last event we dispatch before poll again
|
||||
sticky_exit_callback(
|
||||
Event::AboutToWait,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
callback(Event::AboutToWait, &self.window_target);
|
||||
|
||||
// Update the window frames and schedule redraws.
|
||||
let mut wake_up = false;
|
||||
for window_id in window_ids.drain(..) {
|
||||
wake_up |= self.with_state(|state| match state.windows.get_mut().get_mut(&window_id) {
|
||||
Some(window) => {
|
||||
let refresh = window.lock().unwrap().refresh_frame();
|
||||
if refresh {
|
||||
state
|
||||
.window_requests
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
refresh
|
||||
}
|
||||
None => false,
|
||||
});
|
||||
}
|
||||
|
||||
// Wakeup event loop if needed.
|
||||
//
|
||||
// If the user draws from the `AboutToWait` this is likely not required, however
|
||||
// we can't do much about it.
|
||||
if wake_up {
|
||||
match &self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => {
|
||||
window_target.event_loop_awakener.ping();
|
||||
}
|
||||
#[cfg(x11_platform)]
|
||||
PlatformEventLoopWindowTarget::X(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
self.control_flow = control_flow;
|
||||
std::mem::swap(&mut self.compositor_updates, &mut compositor_updates);
|
||||
std::mem::swap(&mut self.buffer_sink, &mut buffer_sink);
|
||||
std::mem::swap(&mut self.window_ids, &mut window_ids);
|
||||
@@ -655,6 +607,34 @@ impl<T: 'static> EventLoop<T> {
|
||||
))))
|
||||
})
|
||||
}
|
||||
|
||||
fn control_flow(&self) -> ControlFlow {
|
||||
self.window_target.p.control_flow()
|
||||
}
|
||||
|
||||
fn exiting(&self) -> bool {
|
||||
self.window_target.p.exiting()
|
||||
}
|
||||
|
||||
fn set_exit_code(&self, code: i32) {
|
||||
self.window_target.p.set_exit_code(code)
|
||||
}
|
||||
|
||||
fn exit_code(&self) -> Option<i32> {
|
||||
self.window_target.p.exit_code()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsFd for EventLoop<T> {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.event_loop.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRawFd for EventLoop<T> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.event_loop.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
@@ -664,6 +644,12 @@ pub struct EventLoopWindowTarget<T> {
|
||||
/// The main queue used by the event loop.
|
||||
pub queue_handle: QueueHandle<WinitState>,
|
||||
|
||||
/// The application's latest control_flow state
|
||||
pub(crate) control_flow: Cell<ControlFlow>,
|
||||
|
||||
/// The application's exit state.
|
||||
pub(crate) exit: Cell<Option<i32>>,
|
||||
|
||||
// TODO remove that RefCell once we can pass `&mut` in `Window::new`.
|
||||
/// Winit state.
|
||||
pub state: RefCell<WinitState>,
|
||||
@@ -678,16 +664,58 @@ pub struct EventLoopWindowTarget<T> {
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
let mut display_handle = WaylandDisplayHandle::empty();
|
||||
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)
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
self.exit.get().is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn set_exit_code(&self, code: i32) {
|
||||
self.exit.set(Some(code))
|
||||
}
|
||||
|
||||
pub(crate) fn exit_code(&self) -> Option<i32> {
|
||||
self.exit.get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
let mut display_handle = rwh_05::WaylandDisplayHandle::empty();
|
||||
display_handle.display = self.connection.display().id().as_ptr() as *mut _;
|
||||
RawDisplayHandle::Wayland(display_handle)
|
||||
rwh_05::RawDisplayHandle::Wayland(display_handle)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
Ok(rwh_06::WaylandDisplayHandle::new({
|
||||
let ptr = self.connection.display().id().as_ptr();
|
||||
std::ptr::NonNull::new(ptr as *mut _).expect("wl_display should never be null")
|
||||
})
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
// The default routine does floor, but we need round on Wayland.
|
||||
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
|
||||
let width = size.width as f64 * scale_factor;
|
||||
let height = size.height as f64 * scale_factor;
|
||||
(width.round(), height.round()).into()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use sctk::reexports::client::globals::{BindError, GlobalError};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy};
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
pub use crate::platform_impl::platform::{OsError, WindowId};
|
||||
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
pub use output::{MonitorHandle, VideoMode};
|
||||
@@ -76,3 +77,10 @@ impl DeviceId {
|
||||
fn make_wid(surface: &WlSurface) -> WindowId {
|
||||
WindowId(surface.id().as_ptr() as u64)
|
||||
}
|
||||
|
||||
/// The default routine does floor, but we need round on Wayland.
|
||||
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
|
||||
let width = size.width as f64 * scale_factor;
|
||||
let height = size.height as f64 * scale_factor;
|
||||
(width.round(), height.round()).into()
|
||||
}
|
||||
|
||||
@@ -3,26 +3,23 @@ use sctk::reexports::client::Proxy;
|
||||
|
||||
use sctk::output::OutputData;
|
||||
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::platform_impl::platform::{
|
||||
MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode,
|
||||
};
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::platform_impl::platform::VideoMode as PlatformVideoMode;
|
||||
|
||||
use super::event_loop::EventLoopWindowTarget;
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
|
||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
||||
self.state
|
||||
.borrow()
|
||||
.output_state
|
||||
.outputs()
|
||||
.map(MonitorHandle::new)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<PlatformMonitorHandle> {
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
// There's no primary monitor on Wayland.
|
||||
None
|
||||
}
|
||||
@@ -70,7 +67,18 @@ impl MonitorHandle {
|
||||
#[inline]
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.with_output_info(|info| info.location).into()
|
||||
output_data.with_output_info(|info| {
|
||||
info.logical_position.map_or_else(
|
||||
|| {
|
||||
LogicalPosition::<i32>::from(info.location)
|
||||
.to_physical(info.scale_factor as f64)
|
||||
},
|
||||
|logical_position| {
|
||||
LogicalPosition::<i32>::from(logical_position)
|
||||
.to_physical(info.scale_factor as f64)
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -157,7 +165,7 @@ impl VideoMode {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> PlatformMonitorHandle {
|
||||
PlatformMonitorHandle::Wayland(self.monitor.clone())
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.monitor.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum};
|
||||
use crate::event::{ElementState, WindowEvent};
|
||||
use crate::keyboard::ModifiersState;
|
||||
|
||||
use crate::platform_impl::common::xkb_state::KbdState;
|
||||
use crate::platform_impl::common::xkb::Context;
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::seat::WinitSeatState;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
@@ -43,14 +43,10 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
WlKeymapFormat::NoKeymap => {
|
||||
warn!("non-xkb compatible keymap")
|
||||
}
|
||||
WlKeymapFormat::XkbV1 => unsafe {
|
||||
seat_state
|
||||
.keyboard_state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.xkb_state
|
||||
.init_with_fd(fd, size as usize);
|
||||
},
|
||||
WlKeymapFormat::XkbV1 => {
|
||||
let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
|
||||
context.set_keymap_from_fd(fd, size as usize);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
WEnum::Unknown(value) => {
|
||||
@@ -61,21 +57,32 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
// Mark the window as focused.
|
||||
match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().set_has_focus(true),
|
||||
let was_unfocused = match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => {
|
||||
let mut window = window.lock().unwrap();
|
||||
let was_unfocused = !window.has_focus();
|
||||
window.add_seat_focus(data.seat.id());
|
||||
was_unfocused
|
||||
}
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Drop the repeat, if there were any.
|
||||
seat_state.keyboard_state.as_mut().unwrap().current_repeat = None;
|
||||
|
||||
// The keyboard focus is considered as general focus.
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(true), window_id);
|
||||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
keyboard_state.current_repeat = None;
|
||||
if let Some(token) = keyboard_state.repeat_token.take() {
|
||||
keyboard_state.loop_handle.remove(token);
|
||||
}
|
||||
|
||||
*data.window_id.lock().unwrap() = Some(window_id);
|
||||
|
||||
// The keyboard focus is considered as general focus.
|
||||
if was_unfocused {
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(true), window_id);
|
||||
}
|
||||
|
||||
// HACK: this is just for GNOME not fixing their ordering issue of modifiers.
|
||||
if std::mem::take(&mut seat_state.modifiers_pending) {
|
||||
state.events_sink.push_window_event(
|
||||
@@ -89,34 +96,44 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
|
||||
// NOTE: we should drop the repeat regardless whethere it was for the present
|
||||
// window of for the window which just went gone.
|
||||
seat_state.keyboard_state.as_mut().unwrap().current_repeat = None;
|
||||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
keyboard_state.current_repeat = None;
|
||||
if let Some(token) = keyboard_state.repeat_token.take() {
|
||||
keyboard_state.loop_handle.remove(token);
|
||||
}
|
||||
|
||||
// NOTE: The check whether the window exists is essential as we might get a
|
||||
// nil surface, regardless of what protocol says.
|
||||
match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().set_has_focus(false),
|
||||
let focused = match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => {
|
||||
let mut window = window.lock().unwrap();
|
||||
window.remove_seat_focus(&data.seat.id());
|
||||
window.has_focus()
|
||||
}
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Notify that no modifiers are being pressed.
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
|
||||
window_id,
|
||||
);
|
||||
|
||||
// We don't need to update it above, because the next `Enter` will overwrite
|
||||
// anyway.
|
||||
*data.window_id.lock().unwrap() = None;
|
||||
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(false), window_id);
|
||||
if !focused {
|
||||
// Notify that no modifiers are being pressed.
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
|
||||
window_id,
|
||||
);
|
||||
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(false), window_id);
|
||||
}
|
||||
}
|
||||
WlKeyboardEvent::Key {
|
||||
key,
|
||||
state: key_state,
|
||||
state: WEnum::Value(WlKeyState::Pressed),
|
||||
..
|
||||
} if key_state == WEnum::Value(WlKeyState::Pressed) => {
|
||||
} => {
|
||||
let key = key + 8;
|
||||
|
||||
key_input(
|
||||
@@ -134,7 +151,12 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
RepeatInfo::Disable => return,
|
||||
};
|
||||
|
||||
if !keyboard_state.xkb_state.key_repeats(key) {
|
||||
if !keyboard_state
|
||||
.xkb_context
|
||||
.keymap_mut()
|
||||
.unwrap()
|
||||
.key_repeats(key)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,9 +206,9 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
}
|
||||
WlKeyboardEvent::Key {
|
||||
key,
|
||||
state: key_state,
|
||||
state: WEnum::Value(WlKeyState::Released),
|
||||
..
|
||||
} if key_state == WEnum::Value(WlKeyState::Released) => {
|
||||
} => {
|
||||
let key = key + 8;
|
||||
|
||||
key_input(
|
||||
@@ -200,10 +222,17 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
|
||||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
if keyboard_state.repeat_info != RepeatInfo::Disable
|
||||
&& keyboard_state.xkb_state.key_repeats(key)
|
||||
&& keyboard_state
|
||||
.xkb_context
|
||||
.keymap_mut()
|
||||
.unwrap()
|
||||
.key_repeats(key)
|
||||
&& Some(key) == keyboard_state.current_repeat
|
||||
{
|
||||
keyboard_state.current_repeat = None;
|
||||
if let Some(token) = keyboard_state.repeat_token.take() {
|
||||
keyboard_state.loop_handle.remove(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
WlKeyboardEvent::Modifiers {
|
||||
@@ -213,9 +242,14 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
group,
|
||||
..
|
||||
} => {
|
||||
let xkb_state = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_state;
|
||||
let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
|
||||
let xkb_state = match xkb_context.state_mut() {
|
||||
Some(state) => state,
|
||||
None => return,
|
||||
};
|
||||
|
||||
xkb_state.update_modifiers(mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
||||
seat_state.modifiers = xkb_state.mods_state().into();
|
||||
seat_state.modifiers = xkb_state.modifiers().into();
|
||||
|
||||
// HACK: part of the workaround from `WlKeyboardEvent::Enter`.
|
||||
let window_id = match *data.window_id.lock().unwrap() {
|
||||
@@ -261,7 +295,7 @@ pub struct KeyboardState {
|
||||
pub loop_handle: LoopHandle<'static, WinitState>,
|
||||
|
||||
/// The state of the keyboard.
|
||||
pub xkb_state: KbdState,
|
||||
pub xkb_context: Context,
|
||||
|
||||
/// The information about the repeat rate obtained from the compositor.
|
||||
pub repeat_info: RepeatInfo,
|
||||
@@ -278,7 +312,7 @@ impl KeyboardState {
|
||||
Self {
|
||||
keyboard,
|
||||
loop_handle,
|
||||
xkb_state: KbdState::new().unwrap(),
|
||||
xkb_context: Context::new().unwrap(),
|
||||
repeat_info: RepeatInfo::default(),
|
||||
repeat_token: None,
|
||||
current_repeat: None,
|
||||
@@ -361,16 +395,13 @@ fn key_input(
|
||||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
|
||||
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
|
||||
let event = keyboard_state
|
||||
.xkb_state
|
||||
.process_key_event(keycode, state, repeat);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::KeyboardInput {
|
||||
if let Some(mut key_context) = keyboard_state.xkb_context.key_context() {
|
||||
let event = key_context.process_key_event(keycode, state, repeat);
|
||||
let event = WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
};
|
||||
event_sink.push_window_event(event, window_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
use ahash::AHashMap;
|
||||
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_touch::WlTouch;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
@@ -13,6 +14,7 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
|
||||
use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
|
||||
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
|
||||
|
||||
use crate::event::WindowEvent;
|
||||
use crate::keyboard::ModifiersState;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
|
||||
@@ -29,7 +31,7 @@ use keyboard::{KeyboardData, KeyboardState};
|
||||
use text_input::TextInputData;
|
||||
use touch::TouchPoint;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WinitSeatState {
|
||||
/// The pointer bound on the seat.
|
||||
pointer: Option<Arc<ThemedPointer<WinitPointerData>>>,
|
||||
@@ -38,7 +40,7 @@ pub struct WinitSeatState {
|
||||
touch: Option<WlTouch>,
|
||||
|
||||
/// The mapping from touched points to the surfaces they're present.
|
||||
touch_map: FnvHashMap<i32, TouchPoint>,
|
||||
touch_map: AHashMap<i32, TouchPoint>,
|
||||
|
||||
/// The text input bound on the seat.
|
||||
text_input: Option<Arc<ZwpTextInputV3>>,
|
||||
@@ -58,16 +60,7 @@ pub struct WinitSeatState {
|
||||
|
||||
impl WinitSeatState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pointer: None,
|
||||
touch: None,
|
||||
relative_pointer: None,
|
||||
text_input: None,
|
||||
touch_map: Default::default(),
|
||||
keyboard_state: None,
|
||||
modifiers: ModifiersState::empty(),
|
||||
modifiers_pending: false,
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,12 +90,14 @@ impl SeatHandler for WinitState {
|
||||
SeatCapability::Pointer if seat_state.pointer.is_none() => {
|
||||
let surface = self.compositor_state.create_surface(queue_handle);
|
||||
let surface_id = surface.id();
|
||||
let pointer_data = WinitPointerData::new(seat.clone(), surface);
|
||||
let pointer_data = WinitPointerData::new(seat.clone());
|
||||
let themed_pointer = self
|
||||
.seat_state
|
||||
.get_pointer_with_theme_and_data(
|
||||
queue_handle,
|
||||
&seat,
|
||||
self.shm.wl_shm(),
|
||||
surface,
|
||||
ThemeSpec::System,
|
||||
pointer_data,
|
||||
)
|
||||
@@ -150,6 +145,10 @@ impl SeatHandler for WinitState {
|
||||
) {
|
||||
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
|
||||
|
||||
if let Some(text_input) = seat_state.text_input.take() {
|
||||
text_input.destroy();
|
||||
}
|
||||
|
||||
match capability {
|
||||
SeatCapability::Touch => {
|
||||
if let Some(touch) = seat_state.touch.take() {
|
||||
@@ -167,7 +166,7 @@ impl SeatHandler for WinitState {
|
||||
let pointer_data = pointer.pointer().winit_data();
|
||||
|
||||
// Remove the cursor from the mapping.
|
||||
let surface_id = pointer_data.cursor_surface().id();
|
||||
let surface_id = pointer.surface().id();
|
||||
let _ = self.pointer_surfaces.remove(&surface_id);
|
||||
|
||||
// Remove the inner locks/confines before dropping the pointer.
|
||||
@@ -181,13 +180,10 @@ impl SeatHandler for WinitState {
|
||||
}
|
||||
SeatCapability::Keyboard => {
|
||||
seat_state.keyboard_state = None;
|
||||
self.on_keyboard_destroy(&seat.id());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(text_input) = seat_state.text_input.take() {
|
||||
text_input.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
fn new_seat(
|
||||
@@ -206,6 +202,21 @@ impl SeatHandler for WinitState {
|
||||
seat: WlSeat,
|
||||
) {
|
||||
let _ = self.seats.remove(&seat.id());
|
||||
self.on_keyboard_destroy(&seat.id());
|
||||
}
|
||||
}
|
||||
|
||||
impl WinitState {
|
||||
fn on_keyboard_destroy(&mut self, seat: &ObjectId) {
|
||||
for (window_id, window) in self.windows.get_mut() {
|
||||
let mut window = window.lock().unwrap();
|
||||
let had_focus = window.has_focus();
|
||||
window.remove_seat_focus(seat);
|
||||
if had_focus != window.has_focus() {
|
||||
self.events_sink
|
||||
.push_window_event(WindowEvent::Focused(false), *window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use sctk::reexports::client::delegate_dispatch;
|
||||
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
|
||||
@@ -10,15 +11,17 @@ use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle, Dispatch};
|
||||
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
||||
use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
|
||||
use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1;
|
||||
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1};
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::csd_frame::FrameClick;
|
||||
|
||||
use sctk::compositor::SurfaceData;
|
||||
use sctk::globals::GlobalData;
|
||||
use sctk::seat::pointer::{PointerData, PointerDataExt};
|
||||
use sctk::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler};
|
||||
use sctk::seat::SeatState;
|
||||
use sctk::shell::xdg::frame::FrameClick;
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition};
|
||||
use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent};
|
||||
@@ -67,35 +70,31 @@ impl PointerHandler for WinitState {
|
||||
PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. }
|
||||
if parent_surface != surface =>
|
||||
{
|
||||
if let Some(icon) =
|
||||
window.frame_point_moved(seat, surface, event.position.0, event.position.1)
|
||||
{
|
||||
if let Some(icon) = window.frame_point_moved(
|
||||
seat,
|
||||
surface,
|
||||
Duration::ZERO,
|
||||
event.position.0,
|
||||
event.position.1,
|
||||
) {
|
||||
if let Some(pointer) = seat_state.pointer.as_ref() {
|
||||
let surface = pointer
|
||||
.pointer()
|
||||
.data::<WinitPointerData>()
|
||||
.unwrap()
|
||||
.cursor_surface();
|
||||
let scale_factor =
|
||||
surface.data::<SurfaceData>().unwrap().scale_factor();
|
||||
|
||||
let _ = pointer.set_cursor(
|
||||
connection,
|
||||
icon,
|
||||
self.shm.wl_shm(),
|
||||
surface,
|
||||
scale_factor,
|
||||
);
|
||||
let _ = pointer.set_cursor(connection, icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
PointerEventKind::Leave { .. } if parent_surface != surface => {
|
||||
window.frame_point_left();
|
||||
}
|
||||
ref kind @ PointerEventKind::Press { button, serial, .. }
|
||||
| ref kind @ PointerEventKind::Release { button, serial, .. }
|
||||
if parent_surface != surface =>
|
||||
{
|
||||
ref kind @ PointerEventKind::Press {
|
||||
button,
|
||||
serial,
|
||||
time,
|
||||
}
|
||||
| ref kind @ PointerEventKind::Release {
|
||||
button,
|
||||
serial,
|
||||
time,
|
||||
} if parent_surface != surface => {
|
||||
let click = match wayland_button_to_winit(button) {
|
||||
MouseButton::Left => FrameClick::Normal,
|
||||
MouseButton::Right => FrameClick::Alternate,
|
||||
@@ -109,6 +108,7 @@ impl PointerHandler for WinitState {
|
||||
pressed,
|
||||
seat,
|
||||
serial,
|
||||
Duration::from_millis(time as u64),
|
||||
window_id,
|
||||
&mut self.window_compositor_updates,
|
||||
);
|
||||
@@ -238,9 +238,6 @@ impl PointerHandler for WinitState {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WinitPointerData {
|
||||
/// The surface associated with this pointer, which is used for icons.
|
||||
cursor_surface: WlSurface,
|
||||
|
||||
/// The inner winit data associated with the pointer.
|
||||
inner: Mutex<WinitPointerDataInner>,
|
||||
|
||||
@@ -249,9 +246,8 @@ pub struct WinitPointerData {
|
||||
}
|
||||
|
||||
impl WinitPointerData {
|
||||
pub fn new(seat: WlSeat, surface: WlSurface) -> Self {
|
||||
pub fn new(seat: WlSeat) -> Self {
|
||||
Self {
|
||||
cursor_surface: surface,
|
||||
inner: Mutex::new(WinitPointerDataInner::default()),
|
||||
sctk_data: PointerData::new(seat),
|
||||
}
|
||||
@@ -313,11 +309,6 @@ impl WinitPointerData {
|
||||
self.sctk_data.seat()
|
||||
}
|
||||
|
||||
/// The WlSurface used to set cursor theme.
|
||||
pub fn cursor_surface(&self) -> &WlSurface {
|
||||
&self.cursor_surface
|
||||
}
|
||||
|
||||
/// Active window.
|
||||
pub fn focused_window(&self) -> Option<WindowId> {
|
||||
self.inner.lock().unwrap().surface
|
||||
@@ -325,7 +316,7 @@ impl WinitPointerData {
|
||||
|
||||
/// Last button serial.
|
||||
pub fn latest_button_serial(&self) -> u32 {
|
||||
self.inner.lock().unwrap().latest_button_serial
|
||||
self.sctk_data.latest_button_serial().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Last enter serial.
|
||||
@@ -341,12 +332,6 @@ impl WinitPointerData {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WinitPointerData {
|
||||
fn drop(&mut self) {
|
||||
self.cursor_surface.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerDataExt for WinitPointerData {
|
||||
fn pointer_data(&self) -> &PointerData {
|
||||
&self.sctk_data
|
||||
@@ -486,7 +471,35 @@ impl Dispatch<ZwpConfinedPointerV1, GlobalData, WinitState> for PointerConstrain
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WpCursorShapeDeviceV1, GlobalData, WinitState> for SeatState {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &WpCursorShapeDeviceV1,
|
||||
_: <WpCursorShapeDeviceV1 as Proxy>::Event,
|
||||
_: &GlobalData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
unreachable!("wp_cursor_shape_manager has no events")
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WpCursorShapeManagerV1, GlobalData, WinitState> for SeatState {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &WpCursorShapeManagerV1,
|
||||
_: <WpCursorShapeManagerV1 as Proxy>::Event,
|
||||
_: &GlobalData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
unreachable!("wp_cursor_device_manager has no events")
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState);
|
||||
delegate_dispatch!(WinitState: [ WpCursorShapeManagerV1: GlobalData] => SeatState);
|
||||
delegate_dispatch!(WinitState: [ WpCursorShapeDeviceV1: GlobalData] => SeatState);
|
||||
delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState);
|
||||
delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState);
|
||||
delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState);
|
||||
|
||||
@@ -60,19 +60,34 @@ impl Dispatch<ZwpRelativePointerV1, GlobalData, WinitState> for RelativePointerS
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
if let zwp_relative_pointer_v1::Event::RelativeMotion {
|
||||
dx_unaccel,
|
||||
dy_unaccel,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
state.events_sink.push_device_event(
|
||||
DeviceEvent::MouseMotion {
|
||||
delta: (dx_unaccel, dy_unaccel),
|
||||
},
|
||||
super::DeviceId,
|
||||
);
|
||||
}
|
||||
let (dx_unaccel, dy_unaccel) = match event {
|
||||
zwp_relative_pointer_v1::Event::RelativeMotion {
|
||||
dx_unaccel,
|
||||
dy_unaccel,
|
||||
..
|
||||
} => (dx_unaccel, dy_unaccel),
|
||||
_ => return,
|
||||
};
|
||||
state.events_sink.push_device_event(
|
||||
DeviceEvent::Motion {
|
||||
axis: 0,
|
||||
value: dx_unaccel,
|
||||
},
|
||||
super::DeviceId,
|
||||
);
|
||||
state.events_sink.push_device_event(
|
||||
DeviceEvent::Motion {
|
||||
axis: 1,
|
||||
value: dy_unaccel,
|
||||
},
|
||||
super::DeviceId,
|
||||
);
|
||||
state.events_sink.push_device_event(
|
||||
DeviceEvent::MouseMotion {
|
||||
delta: (dx_unaccel, dy_unaccel),
|
||||
},
|
||||
super::DeviceId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ impl TouchHandler for WinitState {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Cancelled,
|
||||
phase: TouchPhase::Moved,
|
||||
location: touch_point.location.to_physical(scale_factor),
|
||||
force: None,
|
||||
id: id as u64,
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::cell::RefCell;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
use ahash::AHashMap;
|
||||
|
||||
use sctk::reexports::calloop::LoopHandle;
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
@@ -22,20 +22,19 @@ use sctk::shell::WaylandSurface;
|
||||
use sctk::shm::{Shm, ShmHandler};
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::platform_impl::OsError;
|
||||
|
||||
use super::event_loop::sink::EventSink;
|
||||
use super::output::MonitorHandle;
|
||||
use super::seat::{
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::output::MonitorHandle;
|
||||
use crate::platform_impl::wayland::seat::{
|
||||
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
|
||||
WinitPointerDataExt, WinitSeatState,
|
||||
};
|
||||
use super::types::wp_fractional_scaling::FractionalScalingManager;
|
||||
use super::types::wp_viewporter::ViewporterState;
|
||||
use super::types::xdg_activation::XdgActivationState;
|
||||
use super::window::{WindowRequests, WindowState};
|
||||
use super::{WaylandError, WindowId};
|
||||
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
|
||||
use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager;
|
||||
use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState;
|
||||
use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState;
|
||||
use crate::platform_impl::wayland::window::{WindowRequests, WindowState};
|
||||
use crate::platform_impl::wayland::{WaylandError, WindowId};
|
||||
use crate::platform_impl::OsError;
|
||||
|
||||
/// Winit's Wayland state.
|
||||
pub struct WinitState {
|
||||
@@ -49,7 +48,7 @@ pub struct WinitState {
|
||||
pub compositor_state: Arc<CompositorState>,
|
||||
|
||||
/// The state of the subcompositor.
|
||||
pub subcompositor_state: Arc<SubcompositorState>,
|
||||
pub subcompositor_state: Option<Arc<SubcompositorState>>,
|
||||
|
||||
/// The seat state responsible for all sorts of input.
|
||||
pub seat_state: SeatState,
|
||||
@@ -61,10 +60,10 @@ pub struct WinitState {
|
||||
pub xdg_shell: XdgShell,
|
||||
|
||||
/// The currently present windows.
|
||||
pub windows: RefCell<FnvHashMap<WindowId, Arc<Mutex<WindowState>>>>,
|
||||
pub windows: RefCell<AHashMap<WindowId, Arc<Mutex<WindowState>>>>,
|
||||
|
||||
/// The requests from the `Window` to EventLoop, such as close operations and redraw requests.
|
||||
pub window_requests: RefCell<FnvHashMap<WindowId, Arc<WindowRequests>>>,
|
||||
pub window_requests: RefCell<AHashMap<WindowId, Arc<WindowRequests>>>,
|
||||
|
||||
/// The events that were generated directly from the window.
|
||||
pub window_events_sink: Arc<Mutex<EventSink>>,
|
||||
@@ -73,10 +72,10 @@ pub struct WinitState {
|
||||
pub window_compositor_updates: Vec<WindowCompositorUpdate>,
|
||||
|
||||
/// Currently handled seats.
|
||||
pub seats: FnvHashMap<ObjectId, WinitSeatState>,
|
||||
pub seats: AHashMap<ObjectId, WinitSeatState>,
|
||||
|
||||
/// Currently present cursor surfaces.
|
||||
pub pointer_surfaces: FnvHashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
|
||||
pub pointer_surfaces: AHashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
|
||||
|
||||
/// The state of the text input on the client.
|
||||
pub text_input_state: Option<TextInputState>,
|
||||
@@ -103,6 +102,9 @@ pub struct WinitState {
|
||||
/// Fractional scaling manager.
|
||||
pub fractional_scaling_manager: Option<FractionalScalingManager>,
|
||||
|
||||
/// KWin blur manager.
|
||||
pub kwin_blur_manager: Option<KWinBlurManager>,
|
||||
|
||||
/// Loop handle to re-register event sources, such as keyboard repeat.
|
||||
pub loop_handle: LoopHandle<'static, Self>,
|
||||
|
||||
@@ -120,19 +122,24 @@ impl WinitState {
|
||||
let registry_state = RegistryState::new(globals);
|
||||
let compositor_state =
|
||||
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
|
||||
let subcompositor_state = SubcompositorState::bind(
|
||||
let subcompositor_state = match SubcompositorState::bind(
|
||||
compositor_state.wl_compositor().clone(),
|
||||
globals,
|
||||
queue_handle,
|
||||
)
|
||||
.map_err(WaylandError::Bind)?;
|
||||
) {
|
||||
Ok(c) => Some(c),
|
||||
Err(e) => {
|
||||
warn!("Subcompositor protocol not available, ignoring CSD: {e:?}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let output_state = OutputState::new(globals, queue_handle);
|
||||
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
|
||||
|
||||
let seat_state = SeatState::new(globals, queue_handle);
|
||||
|
||||
let mut seats = FnvHashMap::default();
|
||||
let mut seats = AHashMap::default();
|
||||
for seat in seat_state.seats() {
|
||||
seats.insert(seat.id(), WinitSeatState::new());
|
||||
}
|
||||
@@ -147,7 +154,7 @@ impl WinitState {
|
||||
Ok(Self {
|
||||
registry_state,
|
||||
compositor_state: Arc::new(compositor_state),
|
||||
subcompositor_state: Arc::new(subcompositor_state),
|
||||
subcompositor_state: subcompositor_state.map(Arc::new),
|
||||
output_state,
|
||||
seat_state,
|
||||
shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
|
||||
@@ -161,6 +168,7 @@ impl WinitState {
|
||||
window_events_sink: Default::default(),
|
||||
viewporter_state,
|
||||
fractional_scaling_manager,
|
||||
kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(),
|
||||
|
||||
seats,
|
||||
text_input_state: TextInputState::new(globals, queue_handle).ok(),
|
||||
@@ -209,7 +217,7 @@ impl WinitState {
|
||||
|
||||
// Update the scale factor right away.
|
||||
window.lock().unwrap().set_scale_factor(scale_factor);
|
||||
self.window_compositor_updates[pos].scale_factor = Some(scale_factor);
|
||||
self.window_compositor_updates[pos].scale_changed = true;
|
||||
} else if let Some(pointer) = self.pointer_surfaces.get(&surface.id()) {
|
||||
// Get the window, where the pointer resides right now.
|
||||
let focused_window = match pointer.pointer().winit_data().focused_window() {
|
||||
@@ -273,9 +281,7 @@ impl WindowHandler for WinitState {
|
||||
};
|
||||
|
||||
// Populate the configure to the window.
|
||||
//
|
||||
// XXX the size on the window will be updated right before dispatching the size to the user.
|
||||
let new_size = self
|
||||
self.window_compositor_updates[pos].resized |= self
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
@@ -284,7 +290,17 @@ impl WindowHandler for WinitState {
|
||||
.unwrap()
|
||||
.configure(configure, &self.shm, &self.subcompositor_state);
|
||||
|
||||
self.window_compositor_updates[pos].size = Some(new_size);
|
||||
// NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the
|
||||
// users, since it can break a lot of things, thus it'll ask users to redraw instead.
|
||||
self.window_requests
|
||||
.get_mut()
|
||||
.get(&window_id)
|
||||
.unwrap()
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
|
||||
// Manually mark that we've got an event, since configure may not generate a resize.
|
||||
self.dispatched_events = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,6 +336,16 @@ impl OutputHandler for WinitState {
|
||||
}
|
||||
|
||||
impl CompositorHandler for WinitState {
|
||||
fn transform_changed(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_surface::WlSurface,
|
||||
_: wayland_client::protocol::wl_output::Transform,
|
||||
) {
|
||||
// TODO(kchibisov) we need to expose it somehow in winit.
|
||||
}
|
||||
|
||||
fn scale_factor_changed(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
@@ -368,10 +394,10 @@ pub struct WindowCompositorUpdate {
|
||||
pub window_id: WindowId,
|
||||
|
||||
/// New window size.
|
||||
pub size: Option<LogicalSize<u32>>,
|
||||
pub resized: bool,
|
||||
|
||||
/// New scale factor.
|
||||
pub scale_factor: Option<f64>,
|
||||
pub scale_changed: bool,
|
||||
|
||||
/// Close the window.
|
||||
pub close_window: bool,
|
||||
@@ -381,8 +407,8 @@ impl WindowCompositorUpdate {
|
||||
fn new(window_id: WindowId) -> Self {
|
||||
Self {
|
||||
window_id,
|
||||
size: None,
|
||||
scale_factor: None,
|
||||
resized: false,
|
||||
scale_changed: false,
|
||||
close_window: false,
|
||||
}
|
||||
}
|
||||
|
||||
70
src/platform_impl/linux/wayland/types/kwin_blur.rs
Normal file
70
src/platform_impl/linux/wayland/types/kwin_blur.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
//! Handling of KDE-compatible blur.
|
||||
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Dispatch;
|
||||
use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle};
|
||||
use wayland_protocols_plasma::blur::client::{
|
||||
org_kde_kwin_blur::OrgKdeKwinBlur, org_kde_kwin_blur_manager::OrgKdeKwinBlurManager,
|
||||
};
|
||||
|
||||
use sctk::globals::GlobalData;
|
||||
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
|
||||
/// KWin blur manager.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KWinBlurManager {
|
||||
manager: OrgKdeKwinBlurManager,
|
||||
}
|
||||
|
||||
impl KWinBlurManager {
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { manager })
|
||||
}
|
||||
|
||||
pub fn blur(
|
||||
&self,
|
||||
surface: &WlSurface,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> OrgKdeKwinBlur {
|
||||
self.manager.create(surface, queue_handle, ())
|
||||
}
|
||||
|
||||
pub fn unset(&self, surface: &WlSurface) {
|
||||
self.manager.unset(surface)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<OrgKdeKwinBlurManager, GlobalData, WinitState> for KWinBlurManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &OrgKdeKwinBlurManager,
|
||||
_: <OrgKdeKwinBlurManager as Proxy>::Event,
|
||||
_: &GlobalData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
unreachable!("no events defined for org_kde_kwin_blur_manager");
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<OrgKdeKwinBlur, (), WinitState> for KWinBlurManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &OrgKdeKwinBlur,
|
||||
_: <OrgKdeKwinBlur as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
unreachable!("no events defined for org_kde_kwin_blur");
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [OrgKdeKwinBlurManager: GlobalData] => KWinBlurManager);
|
||||
delegate_dispatch!(WinitState: [OrgKdeKwinBlur: ()] => KWinBlurManager);
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Wayland protocol implementation boilerplate.
|
||||
|
||||
pub mod kwin_blur;
|
||||
pub mod wp_fractional_scaling;
|
||||
pub mod wp_viewporter;
|
||||
pub mod xdg_activation;
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use raw_window_handle::{
|
||||
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
|
||||
};
|
||||
|
||||
use sctk::reexports::calloop;
|
||||
use sctk::reexports::client::protocol::wl_display::WlDisplay;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Proxy;
|
||||
@@ -24,12 +19,12 @@ 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,
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
|
||||
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
|
||||
};
|
||||
use crate::window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
WindowAttributes, WindowButtons,
|
||||
WindowAttributes, WindowButtons, WindowLevel,
|
||||
};
|
||||
|
||||
use super::event_loop::sink::EventSink;
|
||||
@@ -57,6 +52,7 @@ pub struct Window {
|
||||
compositor: Arc<CompositorState>,
|
||||
|
||||
/// The wayland display used solely for raw window handle.
|
||||
#[allow(dead_code)]
|
||||
display: WlDisplay,
|
||||
|
||||
/// Xdg activation to request user attention.
|
||||
@@ -100,11 +96,9 @@ impl Window {
|
||||
.map(|activation_state| activation_state.global().clone());
|
||||
let display = event_loop_window_target.connection.display();
|
||||
|
||||
// XXX The initial scale factor must be 1, but it might cause sizing issues on HiDPI.
|
||||
let size: LogicalSize<u32> = attributes
|
||||
let size: Size = attributes
|
||||
.inner_size
|
||||
.map(|size| size.to_logical::<u32>(1.))
|
||||
.unwrap_or((800, 600).into());
|
||||
.unwrap_or(LogicalSize::new(800., 600.).into());
|
||||
|
||||
// We prefer server side decorations, however to not have decorations we ask for client
|
||||
// side decorations instead.
|
||||
@@ -131,6 +125,8 @@ impl Window {
|
||||
// Set transparency hint.
|
||||
window_state.set_transparent(attributes.transparent);
|
||||
|
||||
window_state.set_blur(attributes.blur);
|
||||
|
||||
// Set the decorations hint.
|
||||
window_state.set_decorate(attributes.decorations);
|
||||
|
||||
@@ -142,7 +138,8 @@ impl Window {
|
||||
// Set the window title.
|
||||
window_state.set_title(attributes.title);
|
||||
|
||||
// Set the min and max sizes.
|
||||
// Set the min and max sizes. We must set the hints upon creating a window, so
|
||||
// we use the default `1.` scaling...
|
||||
let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.));
|
||||
let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.));
|
||||
window_state.set_min_inner_size(min_size);
|
||||
@@ -152,7 +149,7 @@ impl Window {
|
||||
window_state.set_resizable(attributes.resizable);
|
||||
|
||||
// Set startup mode.
|
||||
match attributes.fullscreen.map(Into::into) {
|
||||
match attributes.fullscreen.0.map(Into::into) {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
|
||||
}
|
||||
@@ -282,7 +279,7 @@ impl Window {
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state.inner_size().to_physical(scale_factor)
|
||||
super::logical_to_physical_rounded(window_state.inner_size(), scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -310,18 +307,15 @@ impl Window {
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state.outer_size().to_physical(scale_factor)
|
||||
super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state.resize(size.to_logical::<u32>(scale_factor));
|
||||
|
||||
let new_size = window_state.request_inner_size(size);
|
||||
self.request_redraw();
|
||||
|
||||
Some(window_state.inner_size().to_physical(scale_factor))
|
||||
Some(new_size)
|
||||
}
|
||||
|
||||
/// Set the minimum inner size for the window.
|
||||
@@ -332,7 +326,9 @@ impl Window {
|
||||
self.window_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_min_inner_size(min_size)
|
||||
.set_min_inner_size(min_size);
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Set the maximum inner size for the window.
|
||||
@@ -343,7 +339,9 @@ impl Window {
|
||||
self.window_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_max_inner_size(max_size)
|
||||
.set_max_inner_size(max_size);
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -375,6 +373,13 @@ impl Window {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show_window_menu(&self, position: Position) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let position = position.to_logical(scale_factor);
|
||||
self.window_state.lock().unwrap().show_window_menu(position);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
|
||||
self.window_state
|
||||
@@ -385,7 +390,10 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
self.window_state.lock().unwrap().set_resizable(resizable);
|
||||
if self.window_state.lock().unwrap().set_resizable(resizable) {
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -409,6 +417,11 @@ impl Window {
|
||||
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)
|
||||
@@ -419,6 +432,12 @@ impl Window {
|
||||
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.
|
||||
@@ -612,6 +631,9 @@ impl Window {
|
||||
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()
|
||||
@@ -629,23 +651,56 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<PlatformMonitorHandle> {
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
// XXX there's no such concept on Wayland.
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_04")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let mut window_handle = WaylandWindowHandle::empty();
|
||||
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 _;
|
||||
RawWindowHandle::Wayland(window_handle)
|
||||
window_handle.display = self.display.id().as_ptr() as *mut _;
|
||||
rwh_04::RawWindowHandle::Wayland(window_handle)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
let mut display_handle = WaylandDisplayHandle::empty();
|
||||
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 _;
|
||||
RawDisplayHandle::Wayland(display_handle)
|
||||
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]
|
||||
@@ -658,6 +713,8 @@ impl Window {
|
||||
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()
|
||||
|
||||
@@ -1,31 +1,38 @@
|
||||
//! The state of the window, which is shared with the event-loop.
|
||||
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use log::warn;
|
||||
use ahash::HashSet;
|
||||
use log::{info, warn};
|
||||
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
use sctk::reexports::csd_frame::{
|
||||
DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowState as XdgWindowState,
|
||||
};
|
||||
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
|
||||
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::xdg::shell::client::xdg_toplevel::ResizeEdge;
|
||||
use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
|
||||
|
||||
use sctk::compositor::{CompositorState, Region, SurfaceData};
|
||||
use sctk::compositor::{CompositorState, Region};
|
||||
use sctk::seat::pointer::ThemedPointer;
|
||||
use sctk::shell::xdg::frame::{DecorationsFrame, FrameAction, FrameClick};
|
||||
use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
|
||||
use sctk::shell::xdg::XdgSurface;
|
||||
use sctk::shell::WaylandSurface;
|
||||
use sctk::shm::Shm;
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError};
|
||||
use crate::platform_impl::wayland::logical_to_physical_rounded;
|
||||
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
|
||||
use crate::platform_impl::WindowId;
|
||||
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
|
||||
|
||||
@@ -37,7 +44,7 @@ use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState};
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
pub type WinitFrame = sctk_adwaita::AdwaitaFrame<WinitState>;
|
||||
#[cfg(not(feature = "sctk-adwaita"))]
|
||||
pub type WinitFrame = sctk::shell::xdg::frame::fallback_frame::FallbackFrame<WinitState>;
|
||||
pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame<WinitState>;
|
||||
|
||||
// Minimum window inner size.
|
||||
const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
|
||||
@@ -47,9 +54,6 @@ pub struct WindowState {
|
||||
/// The connection to Wayland server.
|
||||
pub connection: Connection,
|
||||
|
||||
/// The underlying SCTK window.
|
||||
pub window: ManuallyDrop<Window>,
|
||||
|
||||
/// The window frame, which is created from the configure request.
|
||||
frame: Option<WinitFrame>,
|
||||
|
||||
@@ -83,8 +87,10 @@ pub struct WindowState {
|
||||
/// Whether the frame is resizable.
|
||||
resizable: bool,
|
||||
|
||||
/// Whether the window has focus.
|
||||
has_focus: bool,
|
||||
// NOTE: we can't use simple counter, since it's racy when seat getting destroyed and new
|
||||
// is created, since add/removed stuff could be delivered a bit out of order.
|
||||
/// Seats that has keyboard focus on that window.
|
||||
seat_focus: HashSet<ObjectId>,
|
||||
|
||||
/// The scale factor of the window.
|
||||
scale_factor: f64,
|
||||
@@ -125,16 +131,25 @@ pub struct WindowState {
|
||||
/// sends `None` for the new size in the configure.
|
||||
stateless_size: LogicalSize<u32>,
|
||||
|
||||
/// Initial window size provided by the user. Removed on the first
|
||||
/// configure.
|
||||
initial_size: Option<Size>,
|
||||
|
||||
/// The state of the frame callback.
|
||||
frame_callback_state: FrameCallbackState,
|
||||
|
||||
viewport: Option<WpViewport>,
|
||||
fractional_scale: Option<WpFractionalScaleV1>,
|
||||
blur: Option<OrgKdeKwinBlur>,
|
||||
blur_manager: Option<KWinBlurManager>,
|
||||
|
||||
/// Whether the client side decorations have pending move operations.
|
||||
///
|
||||
/// The value is the serial of the event triggered moved.
|
||||
has_pending_move: Option<u32>,
|
||||
|
||||
/// The underlying SCTK window.
|
||||
pub window: Window,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
@@ -143,7 +158,7 @@ impl WindowState {
|
||||
connection: Connection,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
winit_state: &WinitState,
|
||||
size: LogicalSize<u32>,
|
||||
initial_size: Size,
|
||||
window: Window,
|
||||
theme: Option<Theme>,
|
||||
) -> Self {
|
||||
@@ -159,6 +174,8 @@ impl WindowState {
|
||||
.map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
|
||||
|
||||
Self {
|
||||
blur: None,
|
||||
blur_manager: winit_state.kwin_blur_manager.clone(),
|
||||
compositor,
|
||||
connection,
|
||||
csd_fails: false,
|
||||
@@ -169,7 +186,7 @@ impl WindowState {
|
||||
fractional_scale,
|
||||
frame: None,
|
||||
frame_callback_state: FrameCallbackState::None,
|
||||
has_focus: false,
|
||||
seat_focus: Default::default(),
|
||||
has_pending_move: None,
|
||||
ime_allowed: false,
|
||||
ime_purpose: ImePurpose::Normal,
|
||||
@@ -182,14 +199,15 @@ impl WindowState {
|
||||
resizable: true,
|
||||
scale_factor: 1.,
|
||||
shm: winit_state.shm.wl_shm().clone(),
|
||||
size,
|
||||
stateless_size: size,
|
||||
size: initial_size.to_logical(1.),
|
||||
stateless_size: initial_size.to_logical(1.),
|
||||
initial_size: Some(initial_size),
|
||||
text_inputs: Vec::new(),
|
||||
theme,
|
||||
title: String::default(),
|
||||
transparent: false,
|
||||
viewport,
|
||||
window: ManuallyDrop::new(window),
|
||||
window,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,15 +256,26 @@ impl WindowState {
|
||||
&mut self,
|
||||
configure: WindowConfigure,
|
||||
shm: &Shm,
|
||||
subcompositor: &Arc<SubcompositorState>,
|
||||
) -> LogicalSize<u32> {
|
||||
if configure.decoration_mode == DecorationMode::Client
|
||||
&& self.frame.is_none()
|
||||
&& !self.csd_fails
|
||||
{
|
||||
subcompositor: &Option<Arc<SubcompositorState>>,
|
||||
) -> bool {
|
||||
// NOTE: when using fractional scaling or wl_compositor@v6 the scaling
|
||||
// should be delivered before the first configure, thus apply it to
|
||||
// properly scale the physical sizes provided by the users.
|
||||
if let Some(initial_size) = self.initial_size.take() {
|
||||
self.size = initial_size.to_logical(self.scale_factor());
|
||||
self.stateless_size = self.size;
|
||||
}
|
||||
|
||||
if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
|
||||
configure.decoration_mode == DecorationMode::Client
|
||||
&& self.frame.is_none()
|
||||
&& !self.csd_fails
|
||||
}) {
|
||||
match WinitFrame::new(
|
||||
&*self.window,
|
||||
&self.window,
|
||||
shm,
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
self.compositor.clone(),
|
||||
subcompositor.clone(),
|
||||
self.queue_handle.clone(),
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
@@ -254,6 +283,7 @@ impl WindowState {
|
||||
) {
|
||||
Ok(mut frame) => {
|
||||
frame.set_title(&self.title);
|
||||
frame.set_scaling_factor(self.scale_factor);
|
||||
// Hide the frame if we were asked to not decorate.
|
||||
frame.set_hidden(!self.decorate);
|
||||
self.frame = Some(frame);
|
||||
@@ -270,37 +300,90 @@ impl WindowState {
|
||||
|
||||
let stateless = Self::is_stateless(&configure);
|
||||
|
||||
let new_size = if let Some(frame) = self.frame.as_mut() {
|
||||
let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
|
||||
// Configure the window states.
|
||||
frame.update_state(configure.state);
|
||||
|
||||
match configure.new_size {
|
||||
(Some(width), Some(height)) => {
|
||||
let (width, height) = frame.subtract_borders(width, height);
|
||||
(
|
||||
width.map(|w| w.get()).unwrap_or(1),
|
||||
height.map(|h| h.get()).unwrap_or(1),
|
||||
)
|
||||
.into()
|
||||
let width = width.map(|w| w.get()).unwrap_or(1);
|
||||
let height = height.map(|h| h.get()).unwrap_or(1);
|
||||
((width, height).into(), false)
|
||||
}
|
||||
(_, _) if stateless => self.stateless_size,
|
||||
_ => self.size,
|
||||
(_, _) if stateless => (self.stateless_size, true),
|
||||
_ => (self.size, true),
|
||||
}
|
||||
} else {
|
||||
match configure.new_size {
|
||||
(Some(width), Some(height)) => (width.get(), height.get()).into(),
|
||||
_ if stateless => self.stateless_size,
|
||||
_ => self.size,
|
||||
(Some(width), Some(height)) => ((width.get(), height.get()).into(), false),
|
||||
_ if stateless => (self.stateless_size, true),
|
||||
_ => (self.size, true),
|
||||
}
|
||||
};
|
||||
|
||||
// XXX Set the configure before doing a resize.
|
||||
// Apply configure bounds only when compositor let the user decide what size to pick.
|
||||
if constrain {
|
||||
let bounds = self.inner_size_bounds(&configure);
|
||||
new_size.width = bounds
|
||||
.0
|
||||
.map(|bound_w| new_size.width.min(bound_w.get()))
|
||||
.unwrap_or(new_size.width);
|
||||
new_size.height = bounds
|
||||
.1
|
||||
.map(|bound_h| new_size.height.min(bound_h.get()))
|
||||
.unwrap_or(new_size.height);
|
||||
}
|
||||
|
||||
let new_state = configure.state;
|
||||
let old_state = self
|
||||
.last_configure
|
||||
.as_ref()
|
||||
.map(|configure| configure.state);
|
||||
|
||||
let state_change_requires_resize = old_state
|
||||
.map(|old_state| {
|
||||
!old_state
|
||||
.symmetric_difference(new_state)
|
||||
.difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
|
||||
.is_empty()
|
||||
})
|
||||
// NOTE: `None` is present for the initial configure, thus we must always resize.
|
||||
.unwrap_or(true);
|
||||
|
||||
// NOTE: Set the configure before doing a resize, since we query it during it.
|
||||
self.last_configure = Some(configure);
|
||||
|
||||
// XXX Update the new size right away.
|
||||
self.resize(new_size);
|
||||
if state_change_requires_resize || new_size != self.inner_size() {
|
||||
self.resize(new_size);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
new_size
|
||||
/// Compute the bounds for the inner size of the surface.
|
||||
fn inner_size_bounds(
|
||||
&self,
|
||||
configure: &WindowConfigure,
|
||||
) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
|
||||
let configure_bounds = match configure.suggested_bounds {
|
||||
Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)),
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
if let Some(frame) = self.frame.as_ref() {
|
||||
let (width, height) = frame.subtract_borders(
|
||||
configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()),
|
||||
configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()),
|
||||
);
|
||||
(
|
||||
configure_bounds.0.and(width),
|
||||
configure_bounds.1.and(height),
|
||||
)
|
||||
} else {
|
||||
configure_bounds
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -336,23 +419,40 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Tells whether the window should be closed.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn frame_click(
|
||||
&mut self,
|
||||
click: FrameClick,
|
||||
pressed: bool,
|
||||
seat: &WlSeat,
|
||||
serial: u32,
|
||||
timestamp: Duration,
|
||||
window_id: WindowId,
|
||||
updates: &mut Vec<WindowCompositorUpdate>,
|
||||
) -> Option<bool> {
|
||||
match self.frame.as_mut()?.on_click(click, pressed)? {
|
||||
match self.frame.as_mut()?.on_click(timestamp, click, pressed)? {
|
||||
FrameAction::Minimize => self.window.set_minimized(),
|
||||
FrameAction::Maximize => self.window.set_maximized(),
|
||||
FrameAction::UnMaximize => self.window.unset_maximized(),
|
||||
FrameAction::Close => WinitState::queue_close(updates, window_id),
|
||||
FrameAction::Move => self.has_pending_move = Some(serial),
|
||||
FrameAction::Resize(edge) => self.window.resize(seat, serial, edge),
|
||||
FrameAction::Resize(edge) => {
|
||||
let edge = match edge {
|
||||
ResizeEdge::None => XdgResizeEdge::None,
|
||||
ResizeEdge::Top => XdgResizeEdge::Top,
|
||||
ResizeEdge::Bottom => XdgResizeEdge::Bottom,
|
||||
ResizeEdge::Left => XdgResizeEdge::Left,
|
||||
ResizeEdge::TopLeft => XdgResizeEdge::TopLeft,
|
||||
ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft,
|
||||
ResizeEdge::Right => XdgResizeEdge::Right,
|
||||
ResizeEdge::TopRight => XdgResizeEdge::TopRight,
|
||||
ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
|
||||
_ => return None,
|
||||
};
|
||||
self.window.resize(seat, serial, edge);
|
||||
}
|
||||
FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
|
||||
_ => (),
|
||||
};
|
||||
|
||||
Some(false)
|
||||
@@ -369,14 +469,15 @@ impl WindowState {
|
||||
&mut self,
|
||||
seat: &WlSeat,
|
||||
surface: &WlSurface,
|
||||
timestamp: Duration,
|
||||
x: f64,
|
||||
y: f64,
|
||||
) -> Option<&str> {
|
||||
) -> Option<CursorIcon> {
|
||||
// Take the serial if we had any, so it doesn't stick around.
|
||||
let serial = self.has_pending_move.take();
|
||||
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
let cursor = frame.click_point_moved(surface, x, y);
|
||||
let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y);
|
||||
// If we have a cursor change, that means that cursor is over the decorations,
|
||||
// so try to apply move.
|
||||
if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
|
||||
@@ -397,10 +498,12 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Set the resizable state on the window.
|
||||
///
|
||||
/// Returns `true` when the state was applied.
|
||||
#[inline]
|
||||
pub fn set_resizable(&mut self, resizable: bool) {
|
||||
pub fn set_resizable(&mut self, resizable: bool) -> bool {
|
||||
if self.resizable == resizable {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
self.resizable = resizable;
|
||||
@@ -416,12 +519,14 @@ impl WindowState {
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
frame.set_resizable(resizable);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Whether the window is focused.
|
||||
/// Whether the window is focused by any seat.
|
||||
#[inline]
|
||||
pub fn has_focus(&self) -> bool {
|
||||
self.has_focus
|
||||
!self.seat_focus.is_empty()
|
||||
}
|
||||
|
||||
/// Whether the IME is allowed.
|
||||
@@ -492,14 +597,12 @@ impl WindowState {
|
||||
/// Refresh the decorations frame if it's present returning whether the client should redraw.
|
||||
pub fn refresh_frame(&mut self) -> bool {
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
let dirty = frame.is_dirty();
|
||||
if dirty {
|
||||
frame.draw();
|
||||
if !frame.is_hidden() && frame.is_dirty() {
|
||||
return frame.draw();
|
||||
}
|
||||
dirty
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Reload the cursor style on the given window.
|
||||
@@ -525,8 +628,22 @@ impl WindowState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to resize the window when the user can do so.
|
||||
pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize<u32> {
|
||||
if self
|
||||
.last_configure
|
||||
.as_ref()
|
||||
.map(Self::is_stateless)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
self.resize(inner_size.to_logical(self.scale_factor()))
|
||||
}
|
||||
|
||||
logical_to_physical_rounded(self.inner_size(), self.scale_factor())
|
||||
}
|
||||
|
||||
/// Resize the window to the new inner size.
|
||||
pub fn resize(&mut self, inner_size: LogicalSize<u32>) {
|
||||
fn resize(&mut self, inner_size: LogicalSize<u32>) {
|
||||
self.size = inner_size;
|
||||
|
||||
// Update the stateless size.
|
||||
@@ -586,20 +703,8 @@ impl WindowState {
|
||||
return;
|
||||
}
|
||||
|
||||
self.apply_on_poiner(|pointer, data| {
|
||||
let surface = data.cursor_surface();
|
||||
let scale_factor = surface.data::<SurfaceData>().unwrap().scale_factor();
|
||||
|
||||
if pointer
|
||||
.set_cursor(
|
||||
&self.connection,
|
||||
cursor_icon.name(),
|
||||
&self.shm,
|
||||
surface,
|
||||
scale_factor,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
self.apply_on_poiner(|pointer, _| {
|
||||
if pointer.set_cursor(&self.connection, cursor_icon).is_err() {
|
||||
warn!("Failed to set cursor to {:?}", cursor_icon);
|
||||
}
|
||||
})
|
||||
@@ -653,9 +758,14 @@ impl WindowState {
|
||||
|
||||
/// Set the cursor grabbing state on the top-level.
|
||||
pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
// Replace the user grabbing mode.
|
||||
if self.cursor_grab_mode.user_grab_mode == mode {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.set_cursor_grab_inner(mode)?;
|
||||
// Update user grab on success.
|
||||
self.cursor_grab_mode.user_grab_mode = mode;
|
||||
self.set_cursor_grab_inner(mode)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reload the hints for minimum and maximum sizes.
|
||||
@@ -703,6 +813,15 @@ impl WindowState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
|
||||
// TODO(kchibisov) handle touch serials.
|
||||
self.apply_on_poiner(|_, data| {
|
||||
let serial = data.latest_button_serial();
|
||||
let seat = data.seat();
|
||||
self.window.show_window_menu(seat, serial, position.into());
|
||||
});
|
||||
}
|
||||
|
||||
/// Set the position of the cursor.
|
||||
pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), ExternalError> {
|
||||
if self.pointer_constraints.is_none() {
|
||||
@@ -774,12 +893,16 @@ impl WindowState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark that the window has focus.
|
||||
///
|
||||
/// Should be used from routine that sends focused event.
|
||||
/// Add seat focus for the window.
|
||||
#[inline]
|
||||
pub fn set_has_focus(&mut self, has_focus: bool) {
|
||||
self.has_focus = has_focus;
|
||||
pub fn add_seat_focus(&mut self, seat: ObjectId) {
|
||||
self.seat_focus.insert(seat);
|
||||
}
|
||||
|
||||
/// Remove seat focus from the window.
|
||||
#[inline]
|
||||
pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
|
||||
self.seat_focus.remove(seat);
|
||||
}
|
||||
|
||||
/// Returns `true` if the requested state was applied.
|
||||
@@ -803,7 +926,7 @@ impl WindowState {
|
||||
|
||||
/// Set the IME position.
|
||||
pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
|
||||
// XXX This won't fly unless user will have a way to request IME window per seat, since
|
||||
// FIXME: This won't fly unless user will have a way to request IME window per seat, since
|
||||
// the ime windows will be overlapping, but winit doesn't expose API to specify for
|
||||
// which seat we're setting IME position.
|
||||
let (x, y) = (position.x as i32, position.y as i32);
|
||||
@@ -834,10 +957,34 @@ impl WindowState {
|
||||
pub fn set_scale_factor(&mut self, scale_factor: f64) {
|
||||
self.scale_factor = scale_factor;
|
||||
|
||||
// XXX when fractional scaling is not used update the buffer scale.
|
||||
// NOTE: When fractional scaling is not used update the buffer scale.
|
||||
if self.fractional_scale.is_none() {
|
||||
let _ = self.window.set_buffer_scale(self.scale_factor as _);
|
||||
}
|
||||
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
frame.set_scaling_factor(scale_factor);
|
||||
}
|
||||
}
|
||||
|
||||
/// Make window background blurred
|
||||
#[inline]
|
||||
pub fn set_blur(&mut self, blurred: bool) {
|
||||
if blurred && self.blur.is_none() {
|
||||
if let Some(blur_manager) = self.blur_manager.as_ref() {
|
||||
let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
|
||||
blur.commit();
|
||||
self.blur = Some(blur);
|
||||
} else {
|
||||
info!("Blur manager unavailable, unable to change blur")
|
||||
}
|
||||
} else if !blurred && self.blur.is_some() {
|
||||
self.blur_manager
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.unset(self.window.wl_surface());
|
||||
self.blur.take().unwrap().release();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the window title to a new value.
|
||||
@@ -895,12 +1042,20 @@ impl WindowState {
|
||||
|
||||
impl Drop for WindowState {
|
||||
fn drop(&mut self) {
|
||||
let surface = self.window.wl_surface().clone();
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.window);
|
||||
if let Some(blur) = self.blur.take() {
|
||||
blur.release();
|
||||
}
|
||||
|
||||
surface.destroy();
|
||||
if let Some(fs) = self.fractional_scale.take() {
|
||||
fs.destroy();
|
||||
}
|
||||
|
||||
if let Some(viewport) = self.viewport.take() {
|
||||
viewport.destroy();
|
||||
}
|
||||
|
||||
// NOTE: the wl_surface used by the window is being cleaned up when
|
||||
// dropping SCTK `Window`.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -935,22 +1090,22 @@ pub enum FrameCallbackState {
|
||||
Received,
|
||||
}
|
||||
|
||||
impl From<ResizeDirection> for ResizeEdge {
|
||||
impl From<ResizeDirection> for XdgResizeEdge {
|
||||
fn from(value: ResizeDirection) -> Self {
|
||||
match value {
|
||||
ResizeDirection::North => ResizeEdge::Top,
|
||||
ResizeDirection::West => ResizeEdge::Left,
|
||||
ResizeDirection::NorthWest => ResizeEdge::TopLeft,
|
||||
ResizeDirection::NorthEast => ResizeEdge::TopRight,
|
||||
ResizeDirection::East => ResizeEdge::Right,
|
||||
ResizeDirection::SouthWest => ResizeEdge::BottomLeft,
|
||||
ResizeDirection::SouthEast => ResizeEdge::BottomRight,
|
||||
ResizeDirection::South => ResizeEdge::Bottom,
|
||||
ResizeDirection::North => XdgResizeEdge::Top,
|
||||
ResizeDirection::West => XdgResizeEdge::Left,
|
||||
ResizeDirection::NorthWest => XdgResizeEdge::TopLeft,
|
||||
ResizeDirection::NorthEast => XdgResizeEdge::TopRight,
|
||||
ResizeDirection::East => XdgResizeEdge::Right,
|
||||
ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft,
|
||||
ResizeDirection::SouthEast => XdgResizeEdge::BottomRight,
|
||||
ResizeDirection::South => XdgResizeEdge::Bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XXX rust doesn't allow from `Option`.
|
||||
// NOTE: Rust doesn't allow `From<Option<Theme>>`.
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
|
||||
match theme {
|
||||
|
||||
@@ -6,7 +6,7 @@ macro_rules! atom_manager {
|
||||
($($name:ident $(:$lit:literal)?),*) => {
|
||||
x11rb::atom_manager! {
|
||||
/// The atoms used by `winit`
|
||||
pub(crate) Atoms: AtomsCookie {
|
||||
pub Atoms: AtomsCookie {
|
||||
$($name $(:$lit)?,)*
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ macro_rules! atom_manager {
|
||||
/// Indices into the `Atoms` struct.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub(crate) enum AtomName {
|
||||
pub enum AtomName {
|
||||
$($name,)*
|
||||
}
|
||||
|
||||
@@ -100,7 +100,8 @@ atom_manager! {
|
||||
_NET_FRAME_EXTENTS,
|
||||
_NET_SUPPORTED,
|
||||
_NET_SUPPORTING_WM_CHECK,
|
||||
_XEMBED
|
||||
_XEMBED,
|
||||
_XSETTINGS_SETTINGS
|
||||
}
|
||||
|
||||
impl Index<AtomName> for Atoms {
|
||||
|
||||
@@ -41,7 +41,7 @@ impl From<io::Error> for DndDataParseError {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Dnd {
|
||||
pub struct Dnd {
|
||||
xconn: Arc<XConnection>,
|
||||
// Populated by XdndEnter event handler
|
||||
pub version: Option<c_long>,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user