mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-27 15:13:13 -04:00
Compare commits
435 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27e17e3f29 | ||
|
|
56fde0791b | ||
|
|
4ef144b519 | ||
|
|
7fe8c206ae | ||
|
|
fb78ecbb03 | ||
|
|
850d5f59a7 | ||
|
|
81b2729765 | ||
|
|
c4afadbfab | ||
|
|
b5252f1366 | ||
|
|
f93a223da9 | ||
|
|
d75a0dada0 | ||
|
|
9bf46af6f7 | ||
|
|
464c37a94e | ||
|
|
557d285170 | ||
|
|
ba856e127a | ||
|
|
0ffd303db6 | ||
|
|
5a74bf0aab | ||
|
|
117ec364f9 | ||
|
|
9f789e56ee | ||
|
|
91558169d2 | ||
|
|
4998cb990f | ||
|
|
98692641c4 | ||
|
|
a630b5333c | ||
|
|
ca7735f10b | ||
|
|
7adb805011 | ||
|
|
a8c7d809b9 | ||
|
|
4d81f4aa62 | ||
|
|
4f29aed5ee | ||
|
|
7864d02077 | ||
|
|
0f2d59cbba | ||
|
|
f0c4adc58c | ||
|
|
8e38112ad3 | ||
|
|
6f3d763650 | ||
|
|
7a11ff8766 | ||
|
|
211ae8f6c7 | ||
|
|
0b6b794f01 | ||
|
|
5e2f421e34 | ||
|
|
41f4265957 | ||
|
|
c535968128 | ||
|
|
85ff2fa093 | ||
|
|
44865be79d | ||
|
|
f3fb2fe3a6 | ||
|
|
fa10ca1764 | ||
|
|
4fda048729 | ||
|
|
983e50971d | ||
|
|
cf9daedb18 | ||
|
|
5218e11e55 | ||
|
|
b8f28efd04 | ||
|
|
eced28824b | ||
|
|
b3c7635bba | ||
|
|
4aac778d2a | ||
|
|
043c8635a9 | ||
|
|
da6220060e | ||
|
|
50f9b84f6b | ||
|
|
56333064ac | ||
|
|
de78ffdfed | ||
|
|
2d56ed1099 | ||
|
|
7b1dd52ad1 | ||
|
|
6a46610632 | ||
|
|
27233f575f | ||
|
|
f2ba68771f | ||
|
|
cc23fbf3be | ||
|
|
c98d880a1c | ||
|
|
165c163f01 | ||
|
|
0a59e4b905 | ||
|
|
d837c88855 | ||
|
|
82eab465e0 | ||
|
|
a9c189a423 | ||
|
|
9d9d21cfdb | ||
|
|
779f52a21f | ||
|
|
7a21858d29 | ||
|
|
a3f7e6566a | ||
|
|
f41897cfa4 | ||
|
|
bd6fef1d80 | ||
|
|
42d256e926 | ||
|
|
03dad26c43 | ||
|
|
1e7ab0cd25 | ||
|
|
c333003514 | ||
|
|
10f21090ce | ||
|
|
5575f51483 | ||
|
|
2ede84ab2f | ||
|
|
f046e778aa | ||
|
|
ffcdf80192 | ||
|
|
b811e9d878 | ||
|
|
d815bc089c | ||
|
|
66283a79bd | ||
|
|
3be30affe4 | ||
|
|
a68f1a664b | ||
|
|
6de5041a94 | ||
|
|
014fb68a26 | ||
|
|
9a03dacbde | ||
|
|
4d9302b33c | ||
|
|
488c036a05 | ||
|
|
a4af50ec13 | ||
|
|
317d62fb93 | ||
|
|
b13b39aa0b | ||
|
|
abea6e64e4 | ||
|
|
d6f7a28499 | ||
|
|
bd98561b38 | ||
|
|
ca6f523924 | ||
|
|
d7fdfb1bca | ||
|
|
120f21a010 | ||
|
|
5904529ba1 | ||
|
|
e7a6034b55 | ||
|
|
eb66c25980 | ||
|
|
0ccb8a9f87 | ||
|
|
abed32eb80 | ||
|
|
08907148ec | ||
|
|
fa0795a50c | ||
|
|
fe1eab07ae | ||
|
|
552c7a6252 | ||
|
|
50c0180af3 | ||
|
|
4f33643509 | ||
|
|
0b21c55b72 | ||
|
|
e1bccb68d8 | ||
|
|
e540062ac0 | ||
|
|
f1e0f6c646 | ||
|
|
3218316420 | ||
|
|
2900ecab93 | ||
|
|
3a84da6951 | ||
|
|
478427b0bd | ||
|
|
b0f26c79ff | ||
|
|
969237f422 | ||
|
|
e542a78deb | ||
|
|
2d4b9938f0 | ||
|
|
8ad016362a | ||
|
|
5f2c7350e9 | ||
|
|
256bbe949e | ||
|
|
1126e9ea2f | ||
|
|
927af44aa4 | ||
|
|
0adc0898f0 | ||
|
|
3b986f5583 | ||
|
|
b1f8d778a1 | ||
|
|
04482d5a2e | ||
|
|
3e50911adb | ||
|
|
f51a470872 | ||
|
|
47b938dbe7 | ||
|
|
e2b883d215 | ||
|
|
38fd3c6a99 | ||
|
|
5190472bee | ||
|
|
eab03dca80 | ||
|
|
59e3dda89f | ||
|
|
c846f67bcb | ||
|
|
03c01e038b | ||
|
|
ed4ebd4242 | ||
|
|
b5921d89f2 | ||
|
|
b5a6a4e616 | ||
|
|
9598eb371c | ||
|
|
634b9baea2 | ||
|
|
cf5e422dc8 | ||
|
|
276597e009 | ||
|
|
c0b737de4a | ||
|
|
79fa4061cb | ||
|
|
056421546a | ||
|
|
b4c5b76155 | ||
|
|
c8b9a86885 | ||
|
|
fe2df61884 | ||
|
|
446482367b | ||
|
|
cbb29ab526 | ||
|
|
a491c2abed | ||
|
|
3142355417 | ||
|
|
3493a20173 | ||
|
|
bf0bde8067 | ||
|
|
519947463f | ||
|
|
8c36ed4900 | ||
|
|
7b2c9d42b4 | ||
|
|
587ade844d | ||
|
|
6756549ac9 | ||
|
|
17666e3171 | ||
|
|
f6ca06cd58 | ||
|
|
e634cc609f | ||
|
|
aa83726c72 | ||
|
|
1800fa1670 | ||
|
|
a5e6d0aaaf | ||
|
|
4fe4ce3d77 | ||
|
|
078c4c0c4f | ||
|
|
c8579a1882 | ||
|
|
ab96fa8395 | ||
|
|
6461cfa9b1 | ||
|
|
6c214e71ae | ||
|
|
ecc884ac91 | ||
|
|
24e2c6914a | ||
|
|
ed4d70fdd4 | ||
|
|
07c25b9703 | ||
|
|
cdbdd974fb | ||
|
|
7e13248be3 | ||
|
|
b15a40cd14 | ||
|
|
8db4a9cc61 | ||
|
|
a4ab7dc64c | ||
|
|
afb731bb52 | ||
|
|
ef37b1d5dd | ||
|
|
2b4e8ef916 | ||
|
|
ae28eea406 | ||
|
|
a0464ae83b | ||
|
|
16d5f46db1 | ||
|
|
5cada36ae8 | ||
|
|
b3dcfa1275 | ||
|
|
f1c5afd84e | ||
|
|
be1baf164c | ||
|
|
39c0862198 | ||
|
|
aa8ebdc795 | ||
|
|
ea68916055 | ||
|
|
e26b831f23 | ||
|
|
8c3e69c08b | ||
|
|
46879429ed | ||
|
|
0c89ea7386 | ||
|
|
6c0e3c3b15 | ||
|
|
675582bd46 | ||
|
|
4d6fe7e35c | ||
|
|
f9912baf09 | ||
|
|
f290619dce | ||
|
|
23011c6b0a | ||
|
|
3a39a6ddb0 | ||
|
|
90cf9a3398 | ||
|
|
05d8fa0b91 | ||
|
|
d7d20507ed | ||
|
|
f6e66a71f8 | ||
|
|
c09160d1a8 | ||
|
|
2230e71093 | ||
|
|
5e6350d142 | ||
|
|
a6998af997 | ||
|
|
5c48ec7977 | ||
|
|
953d9b4268 | ||
|
|
f5dcd2aabe | ||
|
|
77f1c73f06 | ||
|
|
24c226416e | ||
|
|
69382fda9a | ||
|
|
ee245c569d | ||
|
|
5462f27dda | ||
|
|
927deb030f | ||
|
|
5ea81efc74 | ||
|
|
6896de5b73 | ||
|
|
d736763216 | ||
|
|
e316a89847 | ||
|
|
f0d8689039 | ||
|
|
4d5e68c6e2 | ||
|
|
5835c9102e | ||
|
|
e47081e385 | ||
|
|
171d53c042 | ||
|
|
35379f305a | ||
|
|
4d2a0dd2b3 | ||
|
|
4a8b659228 | ||
|
|
f314cd2b9a | ||
|
|
3657506f6e | ||
|
|
edca3ebc41 | ||
|
|
ca46e29203 | ||
|
|
132fbe14d5 | ||
|
|
164bf85b5b | ||
|
|
cfa8f027cc | ||
|
|
2e5db75101 | ||
|
|
19e5bee3d1 | ||
|
|
fc6cf89ac0 | ||
|
|
dbcdb6f1b4 | ||
|
|
d0c6c34eaa | ||
|
|
f2688d94ea | ||
|
|
f6b20852bf | ||
|
|
59b1eb5410 | ||
|
|
f781e13166 | ||
|
|
9f8ac8feb5 | ||
|
|
74958ecc6f | ||
|
|
3a60cbaba5 | ||
|
|
ae4c449670 | ||
|
|
b2896d7408 | ||
|
|
edfb4b03f4 | ||
|
|
c8c1eca3c7 | ||
|
|
d3207a8d76 | ||
|
|
9d347b72d9 | ||
|
|
13bb51c7e0 | ||
|
|
bb4aa22cf9 | ||
|
|
3e9b80d47a | ||
|
|
c913cdab0b | ||
|
|
a5f5ce6a3d | ||
|
|
fb6b1d487b | ||
|
|
7d77ccfad3 | ||
|
|
c23bed20b8 | ||
|
|
8fe2b62adf | ||
|
|
4e3165f3d8 | ||
|
|
da2268ae22 | ||
|
|
eccd9e415d | ||
|
|
32cd1ad9a7 | ||
|
|
6e1b9fa24d | ||
|
|
380eea0072 | ||
|
|
a18658284c | ||
|
|
8ddd10a7f9 | ||
|
|
4f1c5b6129 | ||
|
|
7e819bb2ce | ||
|
|
dfea49f488 | ||
|
|
b674d20edf | ||
|
|
8db3e0e043 | ||
|
|
d37c591378 | ||
|
|
9419e4e1a7 | ||
|
|
241b7a80bb | ||
|
|
e716adcc0a | ||
|
|
aee95114db | ||
|
|
8f4a8efa99 | ||
|
|
1e1f0fd7e9 | ||
|
|
6e008b39e9 | ||
|
|
6c4da19197 | ||
|
|
a61e7bb4f4 | ||
|
|
7fbc2118b6 | ||
|
|
a96491f302 | ||
|
|
d96fd02f33 | ||
|
|
92e9bfe0fc | ||
|
|
3392e9c1de | ||
|
|
038ef5c3ad | ||
|
|
70c54ee0ff | ||
|
|
3bab4ef4fb | ||
|
|
58142680ce | ||
|
|
0ffcfd8a3a | ||
|
|
2e53533cc1 | ||
|
|
1168cd4113 | ||
|
|
9dff801f93 | ||
|
|
f07153b8e0 | ||
|
|
f5304815a1 | ||
|
|
29e1041987 | ||
|
|
15b79b18e1 | ||
|
|
54ff9c3bb5 | ||
|
|
546962c904 | ||
|
|
b7a7f59298 | ||
|
|
586255ac0a | ||
|
|
42ba0a74e0 | ||
|
|
34a37b8747 | ||
|
|
02a0a91a94 | ||
|
|
e3fbfb81d7 | ||
|
|
0909bf3d9d | ||
|
|
3398ebe467 | ||
|
|
21c121f9b3 | ||
|
|
ce0d1dfe1b | ||
|
|
7892e86731 | ||
|
|
7b0104b54c | ||
|
|
facb809f12 | ||
|
|
4e2e764e4a | ||
|
|
a0bc3e5dc8 | ||
|
|
2e97ab3d4f | ||
|
|
ef580b817d | ||
|
|
5ec934b1b0 | ||
|
|
c9c260ca08 | ||
|
|
b6109d4a17 | ||
|
|
eef2848c98 | ||
|
|
73c01fff96 | ||
|
|
88bbdb33da | ||
|
|
652ff7576c | ||
|
|
d741c58ac3 | ||
|
|
944ab60eda | ||
|
|
8e23d1608a | ||
|
|
dc99920612 | ||
|
|
ee3ab33a7c | ||
|
|
5b8f5cb54a | ||
|
|
bf97def398 | ||
|
|
d5fd8682eb | ||
|
|
39a7d5b738 | ||
|
|
2665c12098 | ||
|
|
936da131c2 | ||
|
|
850dd97177 | ||
|
|
1170554dbd | ||
|
|
75ce71f05a | ||
|
|
a0d69c782a | ||
|
|
2e93e48a3b | ||
|
|
82d9bbe559 | ||
|
|
8bdd4d620e | ||
|
|
9d5412ffe1 | ||
|
|
ecb887e5c3 | ||
|
|
7d1287958f | ||
|
|
c0c14aaf00 | ||
|
|
10dc0674bb | ||
|
|
4f59796e8a | ||
|
|
32097d75c7 | ||
|
|
c6c4395c3b | ||
|
|
38e6f9ad84 | ||
|
|
3e6092b8ed | ||
|
|
b4e83a5966 | ||
|
|
db2c97a995 | ||
|
|
1552eb21f7 | ||
|
|
d8ffd4bb26 | ||
|
|
34c15608e0 | ||
|
|
eef6977f45 | ||
|
|
078b46720b | ||
|
|
3b4e064a07 | ||
|
|
39bc139500 | ||
|
|
9522670081 | ||
|
|
9a1ef49dc3 | ||
|
|
3a624e0f52 | ||
|
|
279e3edc54 | ||
|
|
0e74d37ff5 | ||
|
|
2d1382f7d6 | ||
|
|
5d8091fc7f | ||
|
|
d7abe0316e | ||
|
|
5ea20fc905 | ||
|
|
e108fa2fbf | ||
|
|
fff6788c12 | ||
|
|
3e8fa41073 | ||
|
|
2b1c8cea1b | ||
|
|
ab33fb8eda | ||
|
|
b0b64a9a15 | ||
|
|
d5d202d60e | ||
|
|
cb39ab29f4 | ||
|
|
0a3cacd577 | ||
|
|
16fd2baba0 | ||
|
|
7f8771a362 | ||
|
|
337d50779c | ||
|
|
fd477986de | ||
|
|
94664ff687 | ||
|
|
0fc8c37721 | ||
|
|
ec29c81ad2 | ||
|
|
304a585493 | ||
|
|
1682703b5c | ||
|
|
bdd80c8af2 | ||
|
|
7b0c7b6cb2 | ||
|
|
7006c7ceca | ||
|
|
2491f2bbd6 | ||
|
|
babbb715c5 | ||
|
|
be79e8979a | ||
|
|
9ab4c03e89 | ||
|
|
4f47a4e793 | ||
|
|
c15fa6e433 | ||
|
|
24faacf497 | ||
|
|
080556f2c8 | ||
|
|
259e868c05 | ||
|
|
575d978202 | ||
|
|
7dd7dc1fc8 | ||
|
|
df7c496a5d | ||
|
|
0086d7153b | ||
|
|
b79acd8a5a | ||
|
|
c2951e0194 | ||
|
|
8998e36994 | ||
|
|
5a7169c7a6 | ||
|
|
4cd6877e8e | ||
|
|
44aabdddcc | ||
|
|
c4415009c0 | ||
|
|
63a7c02492 | ||
|
|
7b0ef160fc | ||
|
|
962241e2a0 | ||
|
|
3efa6d855d | ||
|
|
9067426dca | ||
|
|
ba10c35240 |
@@ -1,9 +1,9 @@
|
|||||||
[alias]
|
|
||||||
run-wasm = ["run", "--release", "--package", "run-wasm", "--"]
|
|
||||||
|
|
||||||
# Allow rust-analyzer and local `cargo doc` invocations to pick up unreleased changelog entries
|
# Allow rust-analyzer and local `cargo doc` invocations to pick up unreleased changelog entries
|
||||||
#
|
#
|
||||||
# Note that these flags are (intentionally) not included when building from the downloaded crate.
|
# Note that these flags are (intentionally) not included when building from the downloaded crate.
|
||||||
[build]
|
[build]
|
||||||
rustflags = ["--cfg=unreleased_changelogs"]
|
|
||||||
rustdocflags = ["--cfg=unreleased_changelogs"]
|
rustdocflags = ["--cfg=unreleased_changelogs"]
|
||||||
|
rustflags = ["--cfg=unreleased_changelogs"]
|
||||||
|
|
||||||
|
[target.wasm32-unknown-unknown]
|
||||||
|
runner = "wasm-bindgen-test-runner"
|
||||||
|
|||||||
8
.git-blame-ignore-revs
Normal file
8
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||||
|
|
||||||
|
# chore(rustfmt): use nightly
|
||||||
|
7b0c7b6cb2c62767ca0c73c857b299883f55a883
|
||||||
|
# Rustfmt: use `group_imports`
|
||||||
|
2665c120981af548433645c6383b3580dd8f8fc4
|
||||||
|
# Use Taplo for TOML formatting
|
||||||
|
3398ebe467c43ccfd91916c5b81ff3c68f598556
|
||||||
39
.github/CODEOWNERS
vendored
39
.github/CODEOWNERS
vendored
@@ -1,37 +1,26 @@
|
|||||||
# Android
|
# Android
|
||||||
/src/platform/android.rs @msiglreith @MarijnS95
|
/winit-android @MarijnS95
|
||||||
/src/platform_impl/android @msiglreith @MarijnS95
|
|
||||||
|
|
||||||
# iOS
|
# Apple (AppKit + UIKit)
|
||||||
/src/platform/ios.rs @madsmtm
|
/winit-appkit @madsmtm
|
||||||
/src/platform_impl/ios @madsmtm
|
/winit-uikit @madsmtm
|
||||||
|
/winit-common/src/core_foundation @madsmtm
|
||||||
|
/winit-common/src/event_handler.rs @madsmtm
|
||||||
|
|
||||||
# Unix
|
# XKB
|
||||||
/src/platform_impl/linux/mod.rs @kchibisov
|
/winit-common/src/xkb @kchibisov
|
||||||
|
|
||||||
# Wayland
|
# Wayland
|
||||||
/src/platform/wayland.rs @kchibisov
|
/winit-wayland @kchibisov
|
||||||
/src/platform_impl/linux/wayland @kchibisov
|
|
||||||
|
|
||||||
# X11
|
# X11
|
||||||
/src/platform/x11.rs @kchibisov @notgull
|
/winit-x11 @kchibisov
|
||||||
/src/platform_impl/linux/x11 @kchibisov @notgull
|
|
||||||
|
|
||||||
# macOS
|
|
||||||
/src/platform/macos.rs @madsmtm
|
|
||||||
/src/platform_impl/macos @madsmtm
|
|
||||||
|
|
||||||
# Web
|
# Web
|
||||||
/src/platform/web.rs @daxpedda
|
/winit-web @daxpedda
|
||||||
/src/platform_impl/web @daxpedda
|
|
||||||
|
|
||||||
# Windows
|
# Windows (Win32) (UNOWNED)
|
||||||
/src/platform/windows.rs @msiglreith
|
#/winit-win32
|
||||||
/src/platform_impl/windows @msiglreith
|
|
||||||
|
|
||||||
# Orbital (Redox OS)
|
# Orbital (Redox OS)
|
||||||
/src/platform/orbital.rs @jackpot51
|
/winit-orbital @jackpot51
|
||||||
/src/platform_impl/orbital @jackpot51
|
|
||||||
|
|
||||||
# Integration tests
|
|
||||||
/it @notgull
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
name: MacOS bug
|
name: macOS bug
|
||||||
description: Create a macOS-specific bug report
|
description: Create a macOS-specific bug report
|
||||||
labels:
|
labels:
|
||||||
- B - bug
|
- B - bug
|
||||||
- DS - macos
|
- DS - appkit
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
name: iOS bug
|
name: iOS bug
|
||||||
description: Create an iOS-specific bug report
|
description: Create an iOS/UIKit-specific bug report
|
||||||
labels:
|
labels:
|
||||||
- B - bug
|
- B - bug
|
||||||
- DS - ios
|
- DS - uikit
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
2
.github/ISSUE_TEMPLATE/bug_web.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_web.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: Web bug
|
name: Web bug
|
||||||
description: Create a web-specific bug report
|
description: Create a Web-specific bug report
|
||||||
labels:
|
labels:
|
||||||
- B - bug
|
- B - bug
|
||||||
- DS - web
|
- DS - web
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: Windows bug
|
|||||||
description: Create a Windows-specific bug report
|
description: Create a Windows-specific bug report
|
||||||
labels:
|
labels:
|
||||||
- B - bug
|
- B - bug
|
||||||
- DS - windows
|
- DS - win32
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,4 +2,3 @@
|
|||||||
- [ ] Added an entry to the `changelog` module if knowledge of this change could be valuable to users
|
- [ ] Added an entry to the `changelog` module if knowledge of this change could be valuable to users
|
||||||
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
|
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
|
||||||
- [ ] Created or updated an example program if it would help users understand this functionality
|
- [ ] Created or updated an example program if it would help users understand this functionality
|
||||||
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented
|
|
||||||
|
|||||||
22
.github/dependabot.yaml
vendored
Normal file
22
.github/dependabot.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
groups:
|
||||||
|
github-actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: src/platform_impl/web/script
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
groups:
|
||||||
|
github-actions:
|
||||||
|
patterns:
|
||||||
|
- '*'
|
||||||
|
labels:
|
||||||
|
- "DS - web"
|
||||||
235
.github/workflows/ci.yml
vendored
235
.github/workflows/ci.yml
vendored
@@ -10,19 +10,32 @@ jobs:
|
|||||||
name: Check formatting
|
name: Check formatting
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: taiki-e/checkout-action@v1
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
with:
|
with:
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
- name: Check Formatting
|
- name: Check Formatting
|
||||||
run: cargo fmt -- --check
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
|
taplo:
|
||||||
|
name: Taplo
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: taiki-e/checkout-action@v1
|
||||||
|
- name: Install Taplo
|
||||||
|
uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: taplo-cli
|
||||||
|
- name: Run Taplo
|
||||||
|
run: taplo fmt --check
|
||||||
|
|
||||||
typos:
|
typos:
|
||||||
name: Check for typos
|
name: Check for typos
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: taiki-e/checkout-action@v1
|
||||||
- uses: taiki-e/install-action@v2
|
- uses: taiki-e/install-action@v2
|
||||||
with:
|
with:
|
||||||
tool: typos-cli
|
tool: typos-cli
|
||||||
@@ -42,7 +55,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
toolchain: [stable, nightly, '1.70.0']
|
toolchain: [stable, nightly, '1.85']
|
||||||
platform:
|
platform:
|
||||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
||||||
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
|
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||||
@@ -53,25 +66,39 @@ jobs:
|
|||||||
- { name: 'Linux 64bit', target: x86_64-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: '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: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
|
||||||
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package winit --features=android-native-activity', cmd: 'apk -- ' }
|
||||||
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
|
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
|
||||||
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
|
- { name: 'macOS x86_64', target: x86_64-apple-darwin, os: macos-latest, }
|
||||||
|
- { name: 'macOS Aarch64', target: aarch64-apple-darwin, os: macos-latest, }
|
||||||
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
|
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
|
||||||
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
|
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
|
||||||
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
- { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
||||||
exclude:
|
exclude:
|
||||||
|
# Web on nightly needs extra arguments
|
||||||
|
|
||||||
|
- toolchain: nightly
|
||||||
|
platform: { name: 'Web' }
|
||||||
|
# Rustup is broken.
|
||||||
|
- toolchain: nightly
|
||||||
|
platform: { name: 'Windows 32bit GNU' }
|
||||||
# Android is tested on stable-3
|
# Android is tested on stable-3
|
||||||
- toolchain: '1.70.0'
|
- toolchain: '1.85'
|
||||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
platform: { name: 'Android' }
|
||||||
|
# Redox OS doesn't follow MSRV
|
||||||
|
- toolchain: '1.85'
|
||||||
|
platform: { name: 'Redox OS' }
|
||||||
include:
|
include:
|
||||||
- toolchain: '1.70.0'
|
- toolchain: '1.85'
|
||||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package winit --features=android-native-activity', cmd: 'apk -- ' }
|
||||||
|
- toolchain: 'nightly'
|
||||||
|
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, test-options: -Zdoctest-xcompile }
|
||||||
- toolchain: 'nightly'
|
- toolchain: 'nightly'
|
||||||
platform: {
|
platform: {
|
||||||
name: 'web Atomic',
|
name: 'Web Atomic',
|
||||||
target: wasm32-unknown-unknown,
|
target: wasm32-unknown-unknown,
|
||||||
os: ubuntu-latest,
|
os: ubuntu-latest,
|
||||||
options: '-Zbuild-std=panic_abort,std',
|
options: '-Zbuild-std=panic_abort,std',
|
||||||
|
test-options: -Zdoctest-xcompile,
|
||||||
rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
|
rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
|
||||||
components: rust-src,
|
components: rust-src,
|
||||||
}
|
}
|
||||||
@@ -83,19 +110,21 @@ jobs:
|
|||||||
|
|
||||||
# Faster compilation and error on warnings
|
# Faster compilation and error on warnings
|
||||||
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
|
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
|
||||||
|
RUSTDOCFLAGS: ${{ matrix.platform.rustflags }}
|
||||||
|
|
||||||
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
|
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
|
||||||
|
TEST_OPTIONS: ${{ matrix.platform.test-options }}
|
||||||
CMD: ${{ matrix.platform.cmd }}
|
CMD: ${{ matrix.platform.cmd }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: taiki-e/checkout-action@v1
|
||||||
|
|
||||||
- name: Restore cache of cargo folder
|
- name: Restore cache of cargo folder
|
||||||
# We use `restore` and later `save`, so that we can create the key after
|
# We use `restore` and later `save`, so that we can create the key after
|
||||||
# the cache has been downloaded.
|
# the cache has been downloaded.
|
||||||
#
|
#
|
||||||
# This could be avoided if we added Cargo.lock to the repository.
|
# This could be avoided if we added Cargo.lock to the repository.
|
||||||
uses: actions/cache/restore@v3
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
|
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
|
||||||
path: |
|
path: |
|
||||||
@@ -107,7 +136,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate lockfile
|
- name: Generate lockfile
|
||||||
# Also updates the crates.io index
|
# 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
|
run: cargo generate-lockfile && cargo update -p smol_str --precise 0.3.2 && cargo update -p unicode-segmentation --precise 1.12.0 && cargo update -p wayland-protocols --precise 0.32.12
|
||||||
|
|
||||||
- name: Install GCC Multilib
|
- name: Install GCC Multilib
|
||||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||||
@@ -116,7 +145,7 @@ jobs:
|
|||||||
- name: Cache cargo-apk
|
- name: Cache cargo-apk
|
||||||
if: contains(matrix.platform.target, 'android')
|
if: contains(matrix.platform.target, 'android')
|
||||||
id: cargo-apk-cache
|
id: cargo-apk-cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/bin/cargo-apk
|
path: ~/.cargo/bin/cargo-apk
|
||||||
# Change this key if we update the required cargo-apk version
|
# Change this key if we update the required cargo-apk version
|
||||||
@@ -131,6 +160,11 @@ jobs:
|
|||||||
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
|
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
|
||||||
run: cargo install cargo-apk --version=^0.9.7 --locked
|
run: cargo install cargo-apk --version=^0.9.7 --locked
|
||||||
|
|
||||||
|
- uses: taiki-e/cache-cargo-install-action@v3
|
||||||
|
if: contains(matrix.platform.target, 'wasm32') && matrix.toolchain == 'nightly'
|
||||||
|
with:
|
||||||
|
tool: wasm-bindgen-cli
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
|
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
|
||||||
@@ -145,56 +179,109 @@ jobs:
|
|||||||
- name: Build crate
|
- name: Build crate
|
||||||
run: cargo $CMD build $OPTIONS
|
run: cargo $CMD build $OPTIONS
|
||||||
|
|
||||||
|
- name: Test winit core
|
||||||
|
run: cargo test -p winit-core
|
||||||
|
|
||||||
|
- name: Test winit Android
|
||||||
|
if: contains(matrix.platform.target, 'android')
|
||||||
|
run: cargo $CMD test -p winit-android --features native-activity --no-run
|
||||||
|
|
||||||
|
- name: Test winit Common (EventHandler)
|
||||||
|
run: cargo $CMD test -p winit-common --features event-handler --no-run
|
||||||
|
|
||||||
|
- name: Test winit Common (CF)
|
||||||
|
if: contains(matrix.platform.target, 'apple')
|
||||||
|
run: cargo $CMD test -p winit-common --features core-foundation --no-run
|
||||||
|
|
||||||
|
- name: Test winit Common (XKB)
|
||||||
|
if: contains(matrix.platform.target, 'linux-gnu')
|
||||||
|
run: cargo $CMD test -p winit-common --features xkb,x11,wayland --no-run
|
||||||
|
|
||||||
|
- name: Test winit AppKit
|
||||||
|
if: contains(matrix.platform.target, 'macos')
|
||||||
|
run: cargo $CMD test -p winit-appkit $OPTIONS
|
||||||
|
|
||||||
|
- name: Test winit Orbital
|
||||||
|
if: contains(matrix.platform.target, 'redox')
|
||||||
|
run: cargo test -p winit-orbital
|
||||||
|
|
||||||
|
- name: Test winit UIKit
|
||||||
|
if: contains(matrix.platform.target, 'ios')
|
||||||
|
# TODO: Run on Simulator
|
||||||
|
run: cargo $CMD test -p winit-uikit $OPTIONS --no-run
|
||||||
|
|
||||||
|
- name: Test winit Web
|
||||||
|
if: contains(matrix.platform.target, 'wasm')
|
||||||
|
run: cargo $CMD test -p winit-web $OPTIONS --no-run
|
||||||
|
|
||||||
|
- name: Test winit Win32
|
||||||
|
if: contains(matrix.platform.target, 'windows')
|
||||||
|
run: cargo $CMD test -p winit-win32 $OPTIONS
|
||||||
|
|
||||||
|
- name: Test winit X11
|
||||||
|
if: contains(matrix.platform.target, 'linux-gnu')
|
||||||
|
run: cargo $CMD test -p winit-x11 --target=${{ matrix.platform.target }}
|
||||||
|
|
||||||
|
- name: Test winit Wayland
|
||||||
|
if: contains(matrix.platform.target, 'linux-gnu')
|
||||||
|
run: cargo $CMD test -p winit-wayland --target=${{ matrix.platform.target }}
|
||||||
|
|
||||||
# Test only on Linux x86_64, so we avoid spending unnecessary CI hours.
|
# Test only on Linux x86_64, so we avoid spending unnecessary CI hours.
|
||||||
- name: Test dpi crate
|
- name: Test dpi crate
|
||||||
if: >
|
if: >
|
||||||
contains(matrix.platform.name, 'Linux 64bit') &&
|
contains(matrix.platform.name, 'Linux 64bit') &&
|
||||||
matrix.toolchain != '1.70.0'
|
matrix.toolchain != '1.85'
|
||||||
run: cargo test -p dpi
|
run: cargo test -p dpi
|
||||||
|
|
||||||
|
- name: Check dpi crate (no_std)
|
||||||
|
if: >
|
||||||
|
contains(matrix.platform.name, 'Linux 64bit') &&
|
||||||
|
matrix.toolchain != '1.85'
|
||||||
|
run: cargo check -p dpi --no-default-features
|
||||||
|
|
||||||
- name: Build tests
|
- name: Build tests
|
||||||
if: >
|
if: >
|
||||||
!contains(matrix.platform.target, 'redox') &&
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
matrix.toolchain != '1.70.0'
|
matrix.toolchain != '1.85'
|
||||||
run: cargo $CMD test --no-run $OPTIONS
|
run: cargo $CMD test --no-run $OPTIONS
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: >
|
if: >
|
||||||
!contains(matrix.platform.target, 'android') &&
|
!contains(matrix.platform.target, 'android') &&
|
||||||
!contains(matrix.platform.target, 'ios') &&
|
!contains(matrix.platform.target, 'ios') &&
|
||||||
!contains(matrix.platform.target, 'wasm32') &&
|
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') &&
|
||||||
!contains(matrix.platform.target, 'redox') &&
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
matrix.toolchain != '1.70.0'
|
matrix.toolchain != '1.85'
|
||||||
run: cargo $CMD test $OPTIONS
|
run: cargo $CMD test $OPTIONS
|
||||||
|
|
||||||
- name: Lint with clippy
|
- name: Lint with clippy
|
||||||
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
|
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
|
||||||
run: cargo clippy --all-targets $OPTIONS -- -Dwarnings
|
run: cargo clippy --all-targets $OPTIONS $TEST_OPTIONS -- -Dwarnings
|
||||||
|
|
||||||
- name: Build tests with serde enabled
|
- name: Build tests with serde enabled
|
||||||
if: >
|
if: >
|
||||||
!contains(matrix.platform.target, 'redox') &&
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
matrix.toolchain != '1.70.0'
|
matrix.toolchain != '1.85'
|
||||||
run: cargo $CMD test --no-run $OPTIONS --features serde
|
run: cargo $CMD test --no-run $OPTIONS $TEST_OPTIONS --features serde
|
||||||
|
|
||||||
- name: Run tests with serde enabled
|
- name: Run tests with serde enabled
|
||||||
if: >
|
if: >
|
||||||
!contains(matrix.platform.target, 'android') &&
|
!contains(matrix.platform.target, 'android') &&
|
||||||
!contains(matrix.platform.target, 'ios') &&
|
!contains(matrix.platform.target, 'ios') &&
|
||||||
!contains(matrix.platform.target, 'wasm32') &&
|
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') &&
|
||||||
!contains(matrix.platform.target, 'redox') &&
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
matrix.toolchain != '1.70.0'
|
matrix.toolchain != '1.85'
|
||||||
run: cargo $CMD test $OPTIONS --features serde
|
run: cargo $CMD test $OPTIONS $TEST_OPTIONS --features serde
|
||||||
|
|
||||||
- name: Check docs.rs documentation
|
- name: Check docs.rs documentation
|
||||||
if: matrix.toolchain == 'nightly'
|
if: matrix.toolchain == 'nightly'
|
||||||
run: cargo doc --no-deps $OPTIONS --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
|
run: cargo doc --no-deps $OPTIONS --features=serde,mint,android-native-activity
|
||||||
env:
|
env:
|
||||||
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs --cfg=unreleased_changelogs'
|
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs --cfg=unreleased_changelogs'
|
||||||
|
|
||||||
# See restore step above
|
# See restore step above
|
||||||
- name: Save cache of cargo folder
|
- name: Save cache of cargo folder
|
||||||
uses: actions/cache/save@v3
|
uses: actions/cache/save@v5
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/registry/index/
|
~/.cargo/registry/index/
|
||||||
@@ -202,62 +289,46 @@ jobs:
|
|||||||
~/.cargo/git/db/
|
~/.cargo/git/db/
|
||||||
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
|
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
|
||||||
|
|
||||||
it:
|
|
||||||
name: Run integration tests on ${{ matrix.platform.name }}
|
|
||||||
runs-on: ${{ matrix.platform.os }}
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
toolchain: [stable, nightly]
|
|
||||||
platform:
|
|
||||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
|
||||||
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
|
|
||||||
|
|
||||||
env:
|
|
||||||
# Set more verbose terminal output
|
|
||||||
CARGO_TERM_VERBOSE: true
|
|
||||||
RUST_BACKTRACE: 1
|
|
||||||
|
|
||||||
# Faster compilation and error on warnings
|
|
||||||
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
|
|
||||||
|
|
||||||
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: dtolnay/rust-toolchain@master
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.toolchain }}
|
|
||||||
|
|
||||||
- name: Log into GHCR
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
|
||||||
|
|
||||||
- name: Common tests
|
|
||||||
run: cargo run -p gui-test-runner -- common-tests ${{ matrix.platform.target }}
|
|
||||||
|
|
||||||
cargo-deny:
|
cargo-deny:
|
||||||
name: Run cargo-deny on ${{ matrix.platform.name }}
|
name: Run cargo-deny
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
|
steps:
|
||||||
strategy:
|
- uses: taiki-e/checkout-action@v1
|
||||||
fail-fast: false
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||||
matrix:
|
with:
|
||||||
platform:
|
log-level: error
|
||||||
- { name: 'Android', target: aarch64-linux-android }
|
|
||||||
- { name: 'iOS', target: aarch64-apple-ios }
|
eslint:
|
||||||
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
|
name: ESLint
|
||||||
- { name: 'macOS', target: x86_64-apple-darwin }
|
|
||||||
- { name: 'Redox OS', target: x86_64-unknown-redox }
|
runs-on: ubuntu-latest
|
||||||
- { name: 'web', target: wasm32-unknown-unknown }
|
defaults:
|
||||||
- { name: 'Windows', target: x86_64-pc-windows-gnu }
|
run:
|
||||||
|
working-directory: ./winit-web/src/script
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: taiki-e/checkout-action@v1
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
- name: Setup NPM
|
||||||
with:
|
run: npm install
|
||||||
command: check
|
- name: Run ESLint
|
||||||
log-level: error
|
run: npx eslint@9.38.0
|
||||||
arguments: --all-features --target ${{ matrix.platform.target }}
|
|
||||||
|
swc:
|
||||||
|
name: Minimize JavaScript
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./winit-web/src/script
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: taiki-e/checkout-action@v1
|
||||||
|
- name: Install SWC
|
||||||
|
run: sudo npm i -g @swc/cli
|
||||||
|
- name: Run SWC
|
||||||
|
run: |
|
||||||
|
swc . --ignore node_modules,**/*.d.ts --only **/*.ts -d . --out-file-extension min.js
|
||||||
|
- name: Check for diff
|
||||||
|
run: |
|
||||||
|
[[ -z $(git status -s) ]]
|
||||||
|
|||||||
10
.github/workflows/docs.yml
vendored
10
.github/workflows/docs.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
@@ -29,10 +29,10 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RUSTDOCFLAGS: --crate-version master --cfg=docsrs --cfg=unreleased_changelogs
|
RUSTDOCFLAGS: --crate-version master --cfg=docsrs --cfg=unreleased_changelogs
|
||||||
run: |
|
run: |
|
||||||
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
|
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=serde,mint,android-native-activity
|
||||||
|
|
||||||
- name: Setup Pages
|
- name: Setup Pages
|
||||||
uses: actions/configure-pages@v4
|
uses: actions/configure-pages@v6
|
||||||
|
|
||||||
- name: Fix permissions
|
- name: Fix permissions
|
||||||
run: |
|
run: |
|
||||||
@@ -41,10 +41,10 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: target/doc
|
path: target/doc
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@v5
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -3,8 +3,8 @@ target/
|
|||||||
rls/
|
rls/
|
||||||
.vscode/
|
.vscode/
|
||||||
*~
|
*~
|
||||||
*.wasm
|
|
||||||
*.ts
|
|
||||||
*.js
|
|
||||||
#*#
|
#*#
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
# NPM package used to run ESLint.
|
||||||
|
/src/platform_impl/web/script/node_modules
|
||||||
|
/src/platform_impl/web/script/package-lock.json
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ Changelog entries should be put in the [`changelog::unreleased`].
|
|||||||
The changelog can also be viewed [on docs.rs][docs_rs] or [on the current
|
The changelog can also be viewed [on docs.rs][docs_rs] or [on the current
|
||||||
master docs][master_docs].
|
master docs][master_docs].
|
||||||
|
|
||||||
[`changelog::unreleased`]: src/changelog/unreleased.md
|
[`changelog::unreleased`]: winit/src/changelog/unreleased.md
|
||||||
[docs_rs]: https://docs.rs/winit/latest/winit/changelog/index.html
|
[docs_rs]: https://docs.rs/winit/latest/winit/changelog/index.html
|
||||||
[master_docs]: https://rust-windowing.github.io/winit/winit/changelog/index.html
|
[master_docs]: https://rust-windowing.github.io/winit/winit/changelog/index.html
|
||||||
|
|||||||
160
CONTRIBUTING.md
160
CONTRIBUTING.md
@@ -1,52 +1,139 @@
|
|||||||
# Winit Contributing Guidelines
|
# Contribution Guidelines
|
||||||
|
|
||||||
## Scope
|
This document contains guidelines for contributing code to winit. It has to be
|
||||||
[See `FEATURES.md`](./FEATURES.md). When requesting or implementing a new Winit feature, you should
|
followed in order for your patch to be approved and applied.
|
||||||
consider whether or not it's directly related to window creation or input handling. If it isn't, it
|
|
||||||
may be worth creating a separate crate that extends Winit's API to add that functionality.
|
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
## Reporting an issue
|
Anyone can contribute to winit, however given that it's a cross platform
|
||||||
|
windowing toolkit getting certain changes incorporated could be challenging.
|
||||||
|
|
||||||
When reporting an issue, in order to help the maintainers understand what the problem is, please make
|
To save your time it's wise to check already opened [pull requests][prs] and
|
||||||
your description of the issue as detailed as possible:
|
[issues][issues]. In general, bug fixes and missing implementations are always
|
||||||
|
accepted, however larger new API proposals should go into the issue first. When
|
||||||
|
in doubt contact us on [Matrix][matrix] or via opening an issue.
|
||||||
|
|
||||||
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
|
### Windows
|
||||||
reproduce the issue, ideally by providing a minimal program exhibiting the problem
|
|
||||||
- if it is a feature request, please provide a clear argumentation about why you believe this feature
|
|
||||||
should be supported by winit
|
|
||||||
|
|
||||||
## Making a pull request
|
To run the examples on Windows, you must have symlinks enabled, see
|
||||||
|
[this][git-windows-symlinks].
|
||||||
|
|
||||||
When making a code contribution to winit, before opening your pull request, please make sure that:
|
[git-windows-symlinks]: https://gitforwindows.org/symbolic-links.html
|
||||||
|
|
||||||
- your patch builds with Winit's minimal supported rust version - Rust 1.70.
|
### Submitting your work and handling review
|
||||||
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
|
|
||||||
were not tested, and what should be tested, so that a maintainer or another contributor can test them
|
|
||||||
- you updated any relevant documentation in winit
|
|
||||||
- you left comments in your code explaining any part that is not straightforward, so that the
|
|
||||||
maintainers and future contributors don't have to try to guess what your code is supposed to do
|
|
||||||
- your PR adds an entry to the current changelog if the introduced change is relevant to winit users.
|
|
||||||
|
|
||||||
You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will
|
All patches have to be sent on Github as [pull requests][prs]. To simplify your
|
||||||
handle those for you when merging (see below).
|
life during review it's recommended to check the "give contributors write access
|
||||||
- if your PR affects the platform compatibility of one or more features or adds another feature, the
|
to the branch" checkbox.
|
||||||
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 a review by a maintainer of your platform. Winit's merging policy
|
We use unstable Rustfmt options across the project, so please run
|
||||||
is that a PR must be approved by at least two maintainers of winit before being merged, including
|
`cargo +nightly fmt` before submitting your work. If you are unable to do so,
|
||||||
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
|
the maintainers can do it for you before merging, just state so in your pull
|
||||||
|
request description. For details on how to use nightly, consult [the
|
||||||
|
documentation][toolchains].
|
||||||
|
|
||||||
Once your PR is deemed ready, the merging maintainer will take care of resolving conflicts in the
|
When editing markdown files (`.md`) they must be wrapped at 80 characters.
|
||||||
`changelog` module (but you must resolve other conflicts yourself). Doing this requires that you check the
|
|
||||||
"give contributors write access to the branch" checkbox when creating the PR.
|
[toolchains]: https://rust-lang.github.io/rustup/concepts/toolchains.html
|
||||||
|
|
||||||
|
#### Handling review
|
||||||
|
|
||||||
|
During the review process certain events could require an action from your side,
|
||||||
|
common patterns and reactions are described below.
|
||||||
|
|
||||||
|
_Event:_ The CI fails to build, but it looks like it is not your fault. Not
|
||||||
|
communicating so could result in maintainers not looking into your patch,
|
||||||
|
since they may assume that you're still working on it.\
|
||||||
|
_Desired behaviour:_ Write a message saying roughly the following "The CI
|
||||||
|
failure is unrelated", so that the maintainers will fix it for you.
|
||||||
|
|
||||||
|
_Event:_ Maintainer requested changes to your PR.\
|
||||||
|
_Desired behavior:_ Once you address the request, you should re-request a review
|
||||||
|
with GitHub's UI. If you don't agree with what maintainer suggested, you
|
||||||
|
should state your objections and re-request the review. That will indicate that
|
||||||
|
the ball is on maintainer's side.
|
||||||
|
|
||||||
|
_Event:_ You've opened a PR, but maintainer shortly after commented that they
|
||||||
|
want to work on that themselves.\
|
||||||
|
_Desired behavior:_ Discuss with the maintainer regarding their plans if they
|
||||||
|
were not outlined in the initial response, because such response means that they
|
||||||
|
are not interested in reviewing your code. Such thing could happen when
|
||||||
|
underestimating complexity of the task you're solving or when your patch
|
||||||
|
mandate certain downstream designs. In general, the maintainer will likely
|
||||||
|
close your PR in order to prevent work being done on it.
|
||||||
|
|
||||||
|
[prs]: https://github.com/rust-windowing/winit/pulls
|
||||||
|
[issues]: https://github.com/rust-windowing/winit/issues
|
||||||
|
[matrix]: https://matrix.to/#/#rust-windowing:matrix.org
|
||||||
|
|
||||||
## Maintainers
|
## Maintainers
|
||||||
|
|
||||||
The current maintainers for each platform are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
|
Winit has plenty of maintainers with different backgrounds, different time
|
||||||
|
available to work on Winit, and reasons to be winit maintainer in the first
|
||||||
|
place. To ensure that Winit's code quality does not decrease over time and to
|
||||||
|
make it easier to teach new maintainers the "winit way of doing things" the
|
||||||
|
common policies and routines are defined in this section.
|
||||||
|
|
||||||
## Release process
|
The current maintainers for each platform are listed in [this file][CODEOWNERS].
|
||||||
|
|
||||||
|
### Contributions handling
|
||||||
|
|
||||||
|
The maintainers must ensure that the external contributions meet Winit's
|
||||||
|
quality standards. If it's not, it **is the maintainer's responsibility** to
|
||||||
|
bring it on par, which includes:
|
||||||
|
|
||||||
|
- Ensure that formatting is consistent and `CHANGELOG` messages are clear
|
||||||
|
for the end users.
|
||||||
|
- Improve the commit message, so it'll be easier for other maintainers to
|
||||||
|
understand the motivation without going through all the discussions on the
|
||||||
|
particular patch/PR.
|
||||||
|
- Ensure that the proposed patch doesn't break platform parity. If the
|
||||||
|
breakage is desired by contributor, an issue should be opened to discuss
|
||||||
|
with other maintainers before merging.
|
||||||
|
- Always fix CI issues before merging if they don't originate from the
|
||||||
|
submitted work.
|
||||||
|
|
||||||
|
However, maintainers should give some leeway for external contributors, so they
|
||||||
|
don't feel discouraged contributing, for example:
|
||||||
|
|
||||||
|
- Suggest a patch to resolve style issues, if it's the only issue with the
|
||||||
|
submitted work. Keep in mind that pushing the resolution yourself is not
|
||||||
|
desired, because the contributor might not agree with what you did.
|
||||||
|
- Be more explicit on how things should be done if you don't like the
|
||||||
|
approach.
|
||||||
|
- Suggest to finish the PR for them if they're absent for a while and you need
|
||||||
|
the proposed changes to move forward with something. In such cases the
|
||||||
|
maintainer must preserve attribution with `Co-authored-by`, `Suggested-by`,
|
||||||
|
or keep the original committer.
|
||||||
|
- Rebase their work for them when massive changes to the Winit codebase were
|
||||||
|
introduced.
|
||||||
|
|
||||||
|
When reviewing code of other maintainers all of the above is on the maintainer
|
||||||
|
who submitted the patch. Interested maintainers could help push the work over
|
||||||
|
the finish line, but teaching other maintainers should be preferred.
|
||||||
|
|
||||||
|
For a _regular_ contributor to winit, the maintainer should slowly start
|
||||||
|
requiring contributor to match *maintainer* quality standards when writing
|
||||||
|
patches and commit messages.
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
When submitting a patch, the maintainer should follow the general contributing
|
||||||
|
guidelines, however greater attention to detail is expected in this case.
|
||||||
|
|
||||||
|
To make life simpler for other maintainers it's suggested to create your branch
|
||||||
|
under the project repository instead of your own fork. The naming scheme is
|
||||||
|
`github_user_name/branch_name`. Doing so will make your work easier to rebase
|
||||||
|
for other maintainers when you're absent.
|
||||||
|
|
||||||
|
### Administrative Actions
|
||||||
|
|
||||||
|
Some things (such as changing required CI steps, adding contributors, ...)
|
||||||
|
require administrative permissions. If you don't have those, ask about the
|
||||||
|
change in an issue. If you have the permissions, discuss it with at least one
|
||||||
|
other admin before making the change.
|
||||||
|
|
||||||
|
### Release process
|
||||||
|
|
||||||
Given that winit is a widely used library, we should be able to make a patch
|
Given that winit is a widely used library, we should be able to make a patch
|
||||||
releases at any time we want without blocking the development of new features.
|
releases at any time we want without blocking the development of new features.
|
||||||
@@ -58,7 +145,8 @@ The exact steps for an exemplary `0.2.0` release might look like this:
|
|||||||
1. Initially, the version on the latest master is `0.1.0`
|
1. Initially, the version on the latest master is `0.1.0`
|
||||||
2. A new `v0.2.x` branch is created for the release
|
2. A new `v0.2.x` branch is created for the release
|
||||||
3. Update released `cfg_attr` in `src/changelog/mod.rs` to `v0.2.md`
|
3. Update released `cfg_attr` in `src/changelog/mod.rs` to `v0.2.md`
|
||||||
4. Move entries from `src/changelog/unreleased.md` into `src/changelog/v0.2.md`
|
4. Move entries from `src/changelog/unreleased.md` into
|
||||||
|
`src/changelog/v0.2.md`
|
||||||
5. In the branch, the version is bumped to `v0.2.0`
|
5. In the branch, the version is bumped to `v0.2.0`
|
||||||
6. The new commit in the branch is tagged `v0.2.0`
|
6. The new commit in the branch is tagged `v0.2.0`
|
||||||
7. The version is pushed to crates.io
|
7. The version is pushed to crates.io
|
||||||
@@ -70,3 +158,5 @@ When doing a patch release, the process is similar:
|
|||||||
2. Checkout the `v0.2.x` branch
|
2. Checkout the `v0.2.x` branch
|
||||||
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
|
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
|
||||||
4. Follow steps 4-9 of the regular release example
|
4. Follow steps 4-9 of the regular release example
|
||||||
|
|
||||||
|
[CODEOWNERS]: .github/CODEOWNERS
|
||||||
|
|||||||
364
Cargo.toml
364
Cargo.toml
@@ -1,285 +1,97 @@
|
|||||||
[package]
|
|
||||||
name = "winit"
|
|
||||||
version = "0.29.15"
|
|
||||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
|
||||||
description = "Cross-platform window creation library."
|
|
||||||
keywords = ["windowing"]
|
|
||||||
readme = "README.md"
|
|
||||||
documentation = "https://docs.rs/winit"
|
|
||||||
categories = ["gui"]
|
|
||||||
rust-version.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
features = [
|
|
||||||
"rwh_04",
|
|
||||||
"rwh_05",
|
|
||||||
"rwh_06",
|
|
||||||
"serde",
|
|
||||||
"mint",
|
|
||||||
# Enabled to get docs to compile
|
|
||||||
"android-native-activity",
|
|
||||||
]
|
|
||||||
# These are all tested in CI
|
|
||||||
targets = [
|
|
||||||
# Windows
|
|
||||||
"i686-pc-windows-msvc",
|
|
||||||
"x86_64-pc-windows-msvc",
|
|
||||||
# macOS
|
|
||||||
"x86_64-apple-darwin",
|
|
||||||
# Unix (X11 & Wayland)
|
|
||||||
"i686-unknown-linux-gnu",
|
|
||||||
"x86_64-unknown-linux-gnu",
|
|
||||||
# iOS
|
|
||||||
"x86_64-apple-ios",
|
|
||||||
# Android
|
|
||||||
"aarch64-linux-android",
|
|
||||||
# Web
|
|
||||||
"wasm32-unknown-unknown",
|
|
||||||
]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
# Features are documented in either `lib.rs` or under `winit::platform`.
|
|
||||||
[features]
|
|
||||||
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
|
||||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
|
|
||||||
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
|
|
||||||
wayland-dlopen = ["wayland-backend/dlopen"]
|
|
||||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
|
|
||||||
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
|
|
||||||
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", "dpi/serde"]
|
|
||||||
mint = ["dpi/mint"]
|
|
||||||
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
|
|
||||||
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
|
|
||||||
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
cfg_aliases = "0.2.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bitflags = "2"
|
|
||||||
cursor-icon = "1.1.0"
|
|
||||||
dpi = { version = "0.1.1", path = "dpi" }
|
|
||||||
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
|
|
||||||
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true }
|
|
||||||
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
|
|
||||||
serde = { workspace = true, optional = true }
|
|
||||||
smol_str = "0.2.0"
|
|
||||||
tracing = { version = "0.1.40", default_features = false }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
image = { version = "0.24.0", default-features = false, features = ["png"] }
|
|
||||||
tracing = { version = "0.1.40", default_features = false, features = ["log"] }
|
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
|
||||||
winit = { path = ".", features = ["rwh_05"] }
|
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
|
|
||||||
softbuffer = { version = "0.3.0", default-features = false, features = ["x11", "x11-dlopen", "wayland", "wayland-dlopen"] }
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
|
||||||
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.5.0"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
|
||||||
core-graphics = "0.23.1"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies.icrate]
|
|
||||||
version = "0.1.0"
|
|
||||||
features = [
|
|
||||||
"dispatch",
|
|
||||||
"Foundation",
|
|
||||||
"Foundation_NSArray",
|
|
||||||
"Foundation_NSAttributedString",
|
|
||||||
"Foundation_NSMutableAttributedString",
|
|
||||||
"Foundation_NSData",
|
|
||||||
"Foundation_NSDictionary",
|
|
||||||
"Foundation_NSString",
|
|
||||||
"Foundation_NSProcessInfo",
|
|
||||||
"Foundation_NSThread",
|
|
||||||
"Foundation_NSNumber",
|
|
||||||
"AppKit",
|
|
||||||
"AppKit_NSAppearance",
|
|
||||||
"AppKit_NSApplication",
|
|
||||||
"AppKit_NSBitmapImageRep",
|
|
||||||
"AppKit_NSButton",
|
|
||||||
"AppKit_NSColor",
|
|
||||||
"AppKit_NSControl",
|
|
||||||
"AppKit_NSCursor",
|
|
||||||
"AppKit_NSEvent",
|
|
||||||
"AppKit_NSGraphicsContext",
|
|
||||||
"AppKit_NSImage",
|
|
||||||
"AppKit_NSImageRep",
|
|
||||||
"AppKit_NSMenu",
|
|
||||||
"AppKit_NSMenuItem",
|
|
||||||
"AppKit_NSPasteboard",
|
|
||||||
"AppKit_NSResponder",
|
|
||||||
"AppKit_NSScreen",
|
|
||||||
"AppKit_NSTextInputContext",
|
|
||||||
"AppKit_NSView",
|
|
||||||
"AppKit_NSWindow",
|
|
||||||
"AppKit_NSWindowTabGroup",
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "ios")'.dependencies.icrate]
|
|
||||||
version = "0.1.0"
|
|
||||||
features = [
|
|
||||||
"dispatch",
|
|
||||||
"Foundation",
|
|
||||||
"Foundation_NSArray",
|
|
||||||
"Foundation_NSString",
|
|
||||||
"Foundation_NSProcessInfo",
|
|
||||||
"Foundation_NSThread",
|
|
||||||
"Foundation_NSSet",
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
|
||||||
unicode-segmentation = "1.7.1"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
|
||||||
version = "0.48"
|
|
||||||
features = [
|
|
||||||
"Win32_Devices_HumanInterfaceDevice",
|
|
||||||
"Win32_Foundation",
|
|
||||||
"Win32_Globalization",
|
|
||||||
"Win32_Graphics_Dwm",
|
|
||||||
"Win32_Graphics_Gdi",
|
|
||||||
"Win32_Media",
|
|
||||||
"Win32_System_Com_StructuredStorage",
|
|
||||||
"Win32_System_Com",
|
|
||||||
"Win32_System_LibraryLoader",
|
|
||||||
"Win32_System_Ole",
|
|
||||||
"Win32_System_SystemInformation",
|
|
||||||
"Win32_System_SystemServices",
|
|
||||||
"Win32_System_Threading",
|
|
||||||
"Win32_System_WindowsProgramming",
|
|
||||||
"Win32_UI_Accessibility",
|
|
||||||
"Win32_UI_Controls",
|
|
||||||
"Win32_UI_HiDpi",
|
|
||||||
"Win32_UI_Input_Ime",
|
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
|
||||||
"Win32_UI_Input_Pointer",
|
|
||||||
"Win32_UI_Input_Touch",
|
|
||||||
"Win32_UI_Shell",
|
|
||||||
"Win32_UI_TextServices",
|
|
||||||
"Win32_UI_WindowsAndMessaging",
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
|
|
||||||
ahash = { version = "0.8.7", 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 }
|
|
||||||
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
|
|
||||||
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
|
|
||||||
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
|
|
||||||
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
|
|
||||||
wayland-client = { version = "0.31.1", optional = true }
|
|
||||||
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
|
|
||||||
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
|
|
||||||
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.47", default-features = false }
|
|
||||||
redox_syscall = "0.4.1"
|
|
||||||
|
|
||||||
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
|
|
||||||
package = "web-sys"
|
|
||||||
version = "0.3.64"
|
|
||||||
features = [
|
|
||||||
'AbortController',
|
|
||||||
'AbortSignal',
|
|
||||||
'Blob',
|
|
||||||
'console',
|
|
||||||
'CssStyleDeclaration',
|
|
||||||
'Document',
|
|
||||||
'DomException',
|
|
||||||
'DomRect',
|
|
||||||
'DomRectReadOnly',
|
|
||||||
'Element',
|
|
||||||
'Event',
|
|
||||||
'EventTarget',
|
|
||||||
'FocusEvent',
|
|
||||||
'HtmlCanvasElement',
|
|
||||||
'HtmlElement',
|
|
||||||
'HtmlImageElement',
|
|
||||||
'ImageBitmap',
|
|
||||||
'ImageBitmapOptions',
|
|
||||||
'ImageBitmapRenderingContext',
|
|
||||||
'ImageData',
|
|
||||||
'IntersectionObserver',
|
|
||||||
'IntersectionObserverEntry',
|
|
||||||
'KeyboardEvent',
|
|
||||||
'MediaQueryList',
|
|
||||||
'MessageChannel',
|
|
||||||
'MessagePort',
|
|
||||||
'Node',
|
|
||||||
'PageTransitionEvent',
|
|
||||||
'PointerEvent',
|
|
||||||
'PremultiplyAlpha',
|
|
||||||
'ResizeObserver',
|
|
||||||
'ResizeObserverBoxOptions',
|
|
||||||
'ResizeObserverEntry',
|
|
||||||
'ResizeObserverOptions',
|
|
||||||
'ResizeObserverSize',
|
|
||||||
'VisibilityState',
|
|
||||||
'Window',
|
|
||||||
'WheelEvent',
|
|
||||||
'Url',
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
|
||||||
js-sys = "0.3.64"
|
|
||||||
pin-project = "1"
|
|
||||||
wasm-bindgen = "0.2"
|
|
||||||
wasm-bindgen-futures = "0.4"
|
|
||||||
web-time = "1"
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
|
|
||||||
atomic-waker = "1"
|
|
||||||
concurrent-queue = { version = "2", default-features = false }
|
|
||||||
|
|
||||||
[target.'cfg(target_family = "wasm")'.dev-dependencies]
|
|
||||||
console_log = "1"
|
|
||||||
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
doc-scrape-examples = true
|
|
||||||
name = "window"
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
default-members = ["winit"]
|
||||||
|
members = ["dpi", "winit*"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
|
||||||
"dpi",
|
|
||||||
"it/common-tests",
|
|
||||||
"it/gui-test",
|
|
||||||
"it/gui-test-runner",
|
|
||||||
"run-wasm",
|
|
||||||
]
|
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
rust-version = "1.70.0"
|
edition = "2024"
|
||||||
repository = "https://github.com/rust-windowing/winit"
|
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
edition = "2021"
|
repository = "https://github.com/rust-windowing/winit"
|
||||||
|
rust-version = "1.85"
|
||||||
|
version = "0.31.0-beta.2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
async-io = "2.3.1"
|
# Workspace dependencies.
|
||||||
gui-test = { path = "it/gui-test" }
|
# `winit` has no version here to allow using it in dev deps for docs.
|
||||||
|
winit = { path = "winit" }
|
||||||
|
winit-android = { version = "=0.31.0-beta.2", path = "winit-android" }
|
||||||
|
winit-appkit = { version = "=0.31.0-beta.2", path = "winit-appkit" }
|
||||||
|
winit-common = { version = "=0.31.0-beta.2", path = "winit-common" }
|
||||||
|
winit-core = { version = "=0.31.0-beta.2", path = "winit-core" }
|
||||||
|
winit-orbital = { version = "=0.31.0-beta.2", path = "winit-orbital" }
|
||||||
|
winit-uikit = { version = "=0.31.0-beta.2", path = "winit-uikit" }
|
||||||
|
winit-wayland = { version = "=0.31.0-beta.2", path = "winit-wayland", default-features = false }
|
||||||
|
winit-web = { version = "=0.31.0-beta.2", path = "winit-web" }
|
||||||
|
winit-win32 = { version = "=0.31.0-beta.2", path = "winit-win32" }
|
||||||
|
winit-x11 = { version = "=0.31.0-beta.2", path = "winit-x11" }
|
||||||
|
|
||||||
|
# Core dependencies.
|
||||||
|
bitflags = "2"
|
||||||
|
cfg_aliases = "0.2.1"
|
||||||
|
cursor-icon = "1.1.0"
|
||||||
|
dpi = { version = "0.1.2", path = "dpi" }
|
||||||
|
keyboard-types = "0.8.0"
|
||||||
mint = "0.5.6"
|
mint = "0.5.6"
|
||||||
|
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] }
|
||||||
serde = { version = "1", features = ["serde_derive"] }
|
serde = { version = "1", features = ["serde_derive"] }
|
||||||
serde_json = "1.0.114"
|
smol_str = "0.3"
|
||||||
winit = { path = "." }
|
tracing = { version = "0.1.40", default-features = false }
|
||||||
|
|
||||||
|
# Dev dependencies.
|
||||||
|
image = { version = "0.25.0", default-features = false }
|
||||||
|
softbuffer = { version = "0.4.8", default-features = false, features = [
|
||||||
|
"x11",
|
||||||
|
"x11-dlopen",
|
||||||
|
"wayland",
|
||||||
|
"wayland-dlopen",
|
||||||
|
] }
|
||||||
|
tracing-subscriber = "0.3.18"
|
||||||
|
|
||||||
|
# Android dependencies.
|
||||||
|
android-activity = "0.6.0"
|
||||||
|
ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
|
||||||
|
|
||||||
|
# Apple dependencies.
|
||||||
|
block2 = "0.6.1"
|
||||||
|
dispatch2 = { version = "0.3.0", default-features = false, features = ["std", "objc2"] }
|
||||||
|
objc2 = { version = "0.6.1", features = ["relax-sign-encoding"] }
|
||||||
|
objc2-app-kit = { version = "0.3.2", default-features = false }
|
||||||
|
objc2-core-foundation = { version = "0.3.2", default-features = false }
|
||||||
|
objc2-core-graphics = { version = "0.3.2", default-features = false }
|
||||||
|
objc2-core-video = { version = "0.3.2", default-features = false }
|
||||||
|
objc2-foundation = { version = "0.3.2", default-features = false }
|
||||||
|
objc2-ui-kit = { version = "0.3.2", default-features = false }
|
||||||
|
|
||||||
|
# Windows dependencies.
|
||||||
|
unicode-segmentation = "1.7.1"
|
||||||
|
windows-sys = "0.61"
|
||||||
|
|
||||||
|
# Linux dependencies.
|
||||||
|
bytemuck = { version = "1.13.1", default-features = false }
|
||||||
|
calloop = "0.14.3"
|
||||||
|
foldhash = { version = "0.2.0", default-features = false, features = ["std"] }
|
||||||
|
libc = "0.2.64"
|
||||||
|
memmap2 = "0.9.0"
|
||||||
|
percent-encoding = "2.0"
|
||||||
|
rustix = { version = "1.0.7", default-features = false }
|
||||||
|
x11-dl = "2.19.1"
|
||||||
|
x11rb = { version = "0.13.0", default-features = false }
|
||||||
|
xkbcommon-dl = "0.4.2"
|
||||||
|
|
||||||
|
# Orbital dependencies.
|
||||||
|
libredox = "0.1.12"
|
||||||
|
orbclient = { version = "0.3.47", default-features = false }
|
||||||
|
redox_event = { package = "redox_event", version = "0.4.5" }
|
||||||
|
|
||||||
|
# Web dependencies.
|
||||||
|
atomic-waker = "1"
|
||||||
|
concurrent-queue = { version = "2", default-features = false }
|
||||||
|
console_error_panic_hook = "0.1"
|
||||||
|
js-sys = "0.3.70"
|
||||||
|
pin-project = "1"
|
||||||
|
tracing-web = "0.1"
|
||||||
|
wasm-bindgen = "0.2.93"
|
||||||
|
wasm-bindgen-futures = "0.4.43"
|
||||||
|
wasm-bindgen-test = "0.3"
|
||||||
|
web-time = "1"
|
||||||
|
web_sys = { package = "web-sys", version = "0.3.70" }
|
||||||
|
|||||||
199
FEATURES.md
199
FEATURES.md
@@ -47,202 +47,3 @@ through the implementation work necessary to function on all platforms. When one
|
|||||||
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
|
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
|
||||||
If that gets accepted, the platform-specific functions get deprecated and become permanently
|
If that gets accepted, the platform-specific functions get deprecated and become permanently
|
||||||
exposed through the core, cross-platform API.
|
exposed through the core, cross-platform API.
|
||||||
|
|
||||||
# Features
|
|
||||||
|
|
||||||
## Extending this section
|
|
||||||
|
|
||||||
If your PR makes notable changes to Winit's features, please update this section as follows:
|
|
||||||
|
|
||||||
- If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core
|
|
||||||
feature, add a row to the feature matrix and describe what platforms the feature has been implemented on.
|
|
||||||
|
|
||||||
- If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the
|
|
||||||
API rework on all relevant platforms, please move it to the `Completed API Reworks` table.
|
|
||||||
|
|
||||||
- If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*,
|
|
||||||
or mark it as *mostly completed* and link to an issue describing the problems with the implementation.
|
|
||||||
|
|
||||||
## Core
|
|
||||||
|
|
||||||
### Windowing
|
|
||||||
- **Window initialization**: Winit allows the creation of a window
|
|
||||||
- **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context
|
|
||||||
- **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan
|
|
||||||
- **Window decorations**: The windows created by winit are properly decorated, and the decorations can
|
|
||||||
be deactivated
|
|
||||||
- **Window decorations toggle**: Decorations can be turned on or off after window creation
|
|
||||||
- **Window resizing**: The windows created by winit can be resized and generate the appropriate events
|
|
||||||
when they are. The application can precisely control its window size if desired.
|
|
||||||
- **Window resize increments**: When the window gets resized, the application can choose to snap the window's
|
|
||||||
size to specific values.
|
|
||||||
- **Window transparency**: Winit allows the creation of windows with a transparent background.
|
|
||||||
- **Window maximization**: The windows created by winit can be maximized upon creation.
|
|
||||||
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
|
|
||||||
creation.
|
|
||||||
- **Window minimization**: The windows created by winit can be minimized after creation.
|
|
||||||
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
|
|
||||||
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
|
|
||||||
creation.
|
|
||||||
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
|
|
||||||
for fullscreen windows and, if applicable, captures the monitor for exclusive
|
|
||||||
use by this application.
|
|
||||||
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
|
|
||||||
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
|
|
||||||
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
|
|
||||||
get drawn above their owner.
|
|
||||||
|
|
||||||
|
|
||||||
### System Information
|
|
||||||
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
|
|
||||||
- **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth).
|
|
||||||
|
|
||||||
### Input Handling
|
|
||||||
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
|
|
||||||
- **Mouse set location**: Forcibly changing the location of the pointer.
|
|
||||||
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
|
|
||||||
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
|
|
||||||
- **Cursor icon**: Changing the cursor icon or hiding the cursor.
|
|
||||||
- **Cursor image**: Changing the cursor to your own image.
|
|
||||||
- **Cursor hittest**: Handle or ignore mouse events for a window.
|
|
||||||
- **Touch events**: Single-touch events.
|
|
||||||
- **Touch pressure**: Touch events contain information about the amount of force being applied.
|
|
||||||
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
|
|
||||||
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
|
|
||||||
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
|
|
||||||
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
|
|
||||||
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
|
|
||||||
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
|
|
||||||
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
|
|
||||||
|
|
||||||
## Platform
|
|
||||||
### Windows
|
|
||||||
* Setting the name of the internal window class
|
|
||||||
* Setting the taskbar icon
|
|
||||||
* Setting the parent window
|
|
||||||
* Setting a menu bar
|
|
||||||
* `WS_EX_NOREDIRECTIONBITMAP` support
|
|
||||||
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
|
|
||||||
* Changing a system-drawn backdrop
|
|
||||||
* Setting the window border color
|
|
||||||
* Setting the title bar background color
|
|
||||||
* Setting the title color
|
|
||||||
* Setting the corner rounding preference
|
|
||||||
|
|
||||||
### macOS
|
|
||||||
* Window activation policy
|
|
||||||
* Window movable by background
|
|
||||||
* Transparent titlebar
|
|
||||||
* Hidden titlebar
|
|
||||||
* Hidden titlebar buttons
|
|
||||||
* Full-size content view
|
|
||||||
* Accepts first mouse
|
|
||||||
* Set a preferred theme and get current theme.
|
|
||||||
|
|
||||||
### Unix
|
|
||||||
* Window urgency
|
|
||||||
* X11 Window Class
|
|
||||||
* X11 Override Redirect Flag
|
|
||||||
* GTK Theme Variant
|
|
||||||
* Base window size
|
|
||||||
* Setting the X11 parent window
|
|
||||||
|
|
||||||
### iOS
|
|
||||||
* Get the `UIScreen` object pointer
|
|
||||||
* Setting the `UIView` hidpi factor
|
|
||||||
* Valid orientations
|
|
||||||
* Home indicator visibility
|
|
||||||
* Status bar visibility and style
|
|
||||||
* Deferring system gestures
|
|
||||||
* Getting the device idiom
|
|
||||||
* Getting the preferred video mode
|
|
||||||
|
|
||||||
### Web
|
|
||||||
* Get if the systems preferred color scheme is "dark"
|
|
||||||
|
|
||||||
## Compatibility Matrix
|
|
||||||
|
|
||||||
Legend:
|
|
||||||
|
|
||||||
- ✔️: Works as intended
|
|
||||||
- ▢: Mostly works, but some bugs are known
|
|
||||||
- ❌: Missing feature or large bugs making it unusable
|
|
||||||
- **N/A**: Not applicable for this platform
|
|
||||||
- ❓: Unknown status
|
|
||||||
|
|
||||||
### Windowing
|
|
||||||
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|
|
||||||
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
|
||||||
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|
|
||||||
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|
|
||||||
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|
|
||||||
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|
|
||||||
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
|
||||||
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|
|
||||||
|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|
|
||||||
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|
|
||||||
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|
|
||||||
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
|
||||||
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
|
||||||
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
|
||||||
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|
|
||||||
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|
|
||||||
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** |
|
|
||||||
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |
|
|
||||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
|
|
||||||
|
|
||||||
### System information
|
|
||||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
|
||||||
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|
|
||||||
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|
|
||||||
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|
|
||||||
|
|
||||||
### Input handling
|
|
||||||
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
|
||||||
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
|
||||||
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|
|
||||||
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|
|
||||||
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|
|
||||||
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
|
||||||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|
|
||||||
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|
|
||||||
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
|
||||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|
|
||||||
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|
|
||||||
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|
|
||||||
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|
|
||||||
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |▢[#720] |**N/A**|**N/A**|❓ |**N/A** |
|
|
||||||
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|
|
||||||
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|
|
||||||
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|
|
||||||
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|
|
||||||
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|
|
||||||
|
|
||||||
### Pending API Reworks
|
|
||||||
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|
|
||||||
|
|
||||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
|
||||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
|
||||||
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|
|
||||||
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|
|
||||||
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|
|
||||||
|
|
||||||
### Completed API Reworks
|
|
||||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
|
||||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
|
||||||
|
|
||||||
[#165]: https://github.com/rust-windowing/winit/issues/165
|
|
||||||
[#219]: https://github.com/rust-windowing/winit/issues/219
|
|
||||||
[#242]: https://github.com/rust-windowing/winit/issues/242
|
|
||||||
[#306]: https://github.com/rust-windowing/winit/issues/306
|
|
||||||
[#315]: https://github.com/rust-windowing/winit/issues/315
|
|
||||||
[#319]: https://github.com/rust-windowing/winit/issues/319
|
|
||||||
[#33]: https://github.com/rust-windowing/winit/issues/33
|
|
||||||
[#459]: https://github.com/rust-windowing/winit/issues/459
|
|
||||||
[#5]: https://github.com/rust-windowing/winit/issues/5
|
|
||||||
[#63]: https://github.com/rust-windowing/winit/issues/63
|
|
||||||
[#720]: https://github.com/rust-windowing/winit/issues/720
|
|
||||||
[#721]: https://github.com/rust-windowing/winit/issues/721
|
|
||||||
[#750]: https://github.com/rust-windowing/winit/issues/750
|
|
||||||
[#753]: https://github.com/rust-windowing/winit/issues/753
|
|
||||||
[#804]: https://github.com/rust-windowing/winit/issues/804
|
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
[](https://crates.io/crates/winit)
|
[](https://crates.io/crates/winit)
|
||||||
[](https://docs.rs/winit)
|
[](https://docs.rs/winit)
|
||||||
[](https://rust-windowing.github.io/winit/winit/index.html)
|
)](https://rust-windowing.github.io/winit/winit/index.html)
|
||||||
[](https://github.com/rust-windowing/winit/actions)
|
[](https://github.com/rust-windowing/winit/actions)
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
winit = "0.29.15"
|
winit = "0.31.0-beta.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
## [Documentation](https://docs.rs/winit)
|
## [Documentation](https://docs.rs/winit)
|
||||||
@@ -19,7 +19,7 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
|
|||||||
|
|
||||||
## Contact Us
|
## Contact Us
|
||||||
|
|
||||||
Join us in our [](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [](https://web.libera.chat/#winit).
|
Join us in our [](https://matrix.to/#/#rust-windowing:matrix.org) room.
|
||||||
|
|
||||||
The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
|
The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
|
||||||
|
|
||||||
@@ -33,9 +33,13 @@ Winit is designed to be a low-level brick in a hierarchy of libraries. Consequen
|
|||||||
show something on the window you need to use the platform-specific getters provided by winit, or
|
show something on the window you need to use the platform-specific getters provided by winit, or
|
||||||
another library.
|
another library.
|
||||||
|
|
||||||
|
## CONTRIBUTING
|
||||||
|
|
||||||
|
For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||||
|
|
||||||
## MSRV Policy
|
## MSRV Policy
|
||||||
|
|
||||||
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
|
This crate's Minimum Supported Rust Version (MSRV) is **1.85**. Changes to
|
||||||
the MSRV will be accompanied by a minor version bump.
|
the MSRV will be accompanied by a minor version bump.
|
||||||
|
|
||||||
As a **tentative** policy, the upper bound of the MSRV is given by the following
|
As a **tentative** policy, the upper bound of the MSRV is given by the following
|
||||||
@@ -50,12 +54,15 @@ Where `sid` is the current version of `rustc` provided by [Debian Sid], and
|
|||||||
|
|
||||||
[Debian Sid]: https://packages.debian.org/sid/rustc
|
[Debian Sid]: https://packages.debian.org/sid/rustc
|
||||||
|
|
||||||
The exception is for the Android platform, where a higher Rust version
|
An exception is made for the Android platform, where a higher Rust version
|
||||||
must be used for certain Android features. In this case, the MSRV will be
|
must be used for certain Android features. In this case, the MSRV will be
|
||||||
capped at the latest stable version of Rust minus three. This inconsistency is
|
capped at the latest stable version of Rust minus three. This inconsistency is
|
||||||
not reflected in Cargo metadata, as it is not powerful enough to expose this
|
not reflected in Cargo metadata, as it is not powerful enough to expose this
|
||||||
restriction.
|
restriction.
|
||||||
|
|
||||||
|
Redox OS is also not covered by this MSRV policy, as it requires a Rust nightly
|
||||||
|
toolchain to compile.
|
||||||
|
|
||||||
All crates in the [`rust-windowing`] organizations have the
|
All crates in the [`rust-windowing`] organizations have the
|
||||||
same MSRV policy.
|
same MSRV policy.
|
||||||
|
|
||||||
@@ -63,4 +70,10 @@ same MSRV policy.
|
|||||||
|
|
||||||
### Platform-specific usage
|
### Platform-specific usage
|
||||||
|
|
||||||
Check out the [`winit::platform`](https://rust-windowing.github.io/winit/winit/platform/index.html) module for platform-specific usage.
|
Check out the [`winit::platform`](https://docs.rs/winit/latest/winit/platform/index.html) module for platform-specific usage.
|
||||||
|
|
||||||
|
### Repository License
|
||||||
|
|
||||||
|
Note that the license in `LICENSE` doesn't apply in full to the DPI package [./dpi](./dpi).
|
||||||
|
Full details can be found in that folder's README.
|
||||||
|
<!-- This doesn't apply to users of the Winit crate, but this is also the repository level README -->
|
||||||
|
|||||||
40
clippy.toml
40
clippy.toml
@@ -1,15 +1,27 @@
|
|||||||
disallowed-methods = [
|
# Using allow-invalid because this is platform-specific code
|
||||||
{ path = "web_sys::window", reason = "is not available in every context" },
|
disallowed-macros = [
|
||||||
{ path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
|
{ path = "std::print", reason = "works badly on web", replacement = "tracing::info" },
|
||||||
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
|
{ path = "std::println", reason = "works badly on web", replacement = "tracing::info" },
|
||||||
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
|
{ path = "std::eprint", reason = "works badly on web", replacement = "tracing::error" },
|
||||||
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
|
{ path = "std::eprintln", reason = "works badly on web", replacement = "tracing::error" },
|
||||||
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
|
{ path = "std::dbg", reason = "leftover debugging aid, remove it or use tracing" },
|
||||||
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
|
]
|
||||||
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
|
disallowed-methods = [
|
||||||
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
{ allow-invalid = true, path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
|
||||||
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
{ allow-invalid = true, path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
|
||||||
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
|
{ allow-invalid = true, path = "web_sys::Document::exit_fullscreen", 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." },
|
{ allow-invalid = true, path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
|
||||||
{ path = "icrate::AppKit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
|
{ allow-invalid = true, path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::MouseEvent::buttons", reason = "Use `backend::event::cursor_buttons()` to avoid wrong conversions" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::MouseEvent::button", reason = "Use `backend::event::cursor_button()` to avoid wrong conversions" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::PointerEvent::pointer_type", reason = "Use `WebPointerType` to emit warnings" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::window", reason = "is not available in every context" },
|
||||||
]
|
]
|
||||||
|
|||||||
102
deny.toml
102
deny.toml
@@ -1,63 +1,61 @@
|
|||||||
# https://embarkstudios.github.io/cargo-deny/
|
# https://embarkstudios.github.io/cargo-deny
|
||||||
# cargo install cargo-deny
|
# cargo install cargo-deny
|
||||||
# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check
|
# cargo update && cargo deny check
|
||||||
# Note: running just `cargo deny check` without a `--target` will result in
|
[graph]
|
||||||
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
|
all-features = true
|
||||||
|
exclude-dev = true
|
||||||
targets = [
|
targets = [
|
||||||
{ triple = "aarch64-apple-ios" },
|
"aarch64-apple-darwin",
|
||||||
{ triple = "aarch64-linux-android" },
|
"aarch64-apple-ios",
|
||||||
{ triple = "i686-pc-windows-gnu" },
|
"aarch64-linux-android",
|
||||||
{ triple = "i686-pc-windows-msvc" },
|
"i686-pc-windows-gnu",
|
||||||
{ triple = "i686-unknown-linux-gnu" },
|
"i686-pc-windows-msvc",
|
||||||
{ triple = "wasm32-unknown-unknown" },
|
"i686-unknown-linux-gnu",
|
||||||
{ triple = "x86_64-apple-darwin" },
|
{ triple = "wasm32-unknown-unknown", features = [
|
||||||
{ triple = "x86_64-apple-ios" },
|
"atomics",
|
||||||
{ triple = "x86_64-pc-windows-gnu" },
|
] },
|
||||||
{ triple = "x86_64-pc-windows-msvc" },
|
"x86_64-apple-darwin",
|
||||||
{ triple = "x86_64-unknown-linux-gnu" },
|
"x86_64-apple-ios",
|
||||||
{ triple = "x86_64-unknown-redox" },
|
"x86_64-pc-windows-gnu",
|
||||||
|
"x86_64-pc-windows-msvc",
|
||||||
|
"x86_64-unknown-linux-gnu",
|
||||||
|
"x86_64-unknown-redox",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[advisories]
|
|
||||||
vulnerability = "deny"
|
|
||||||
unmaintained = "warn"
|
|
||||||
yanked = "deny"
|
|
||||||
ignore = []
|
|
||||||
|
|
||||||
|
|
||||||
[bans]
|
|
||||||
multiple-versions = "deny"
|
|
||||||
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
|
||||||
deny = []
|
|
||||||
skip = [
|
|
||||||
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
|
|
||||||
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
|
|
||||||
{ name = "libloading" }, # x11rb uses a different version until the next update
|
|
||||||
]
|
|
||||||
skip-tree = []
|
|
||||||
|
|
||||||
|
|
||||||
[licenses]
|
[licenses]
|
||||||
private = { ignore = true }
|
|
||||||
unlicensed = "deny"
|
|
||||||
allow-osi-fsf-free = "neither"
|
|
||||||
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
|
|
||||||
copyleft = "deny"
|
|
||||||
allow = [
|
allow = [
|
||||||
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
|
|
||||||
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
|
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
|
||||||
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
|
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
|
||||||
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
|
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
|
||||||
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
|
"ISC", # https://tldrlegal.com/license/isc-license
|
||||||
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
|
|
||||||
"ISC", # https://tldrlegal.com/license/-isc-license
|
|
||||||
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
|
|
||||||
"MIT-0", # https://choosealicense.com/licenses/mit-0/
|
|
||||||
"MIT", # https://tldrlegal.com/license/mit-license
|
"MIT", # https://tldrlegal.com/license/mit-license
|
||||||
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
|
"Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
|
||||||
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
|
"Zlib", # https://spdx.org/licenses/Zlib.html
|
||||||
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
|
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/
|
||||||
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
|
|
||||||
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
|
|
||||||
]
|
]
|
||||||
|
confidence-threshold = 1.0
|
||||||
|
private = { ignore = true }
|
||||||
|
|
||||||
|
[bans]
|
||||||
|
multiple-versions = "deny"
|
||||||
|
skip = [
|
||||||
|
{ crate = "jni-sys@0.3", reason = "uses the semver trick to depend on v0.4, but `ndk` hasn't been updated to v0.4 yet" },
|
||||||
|
{ crate = "thiserror@1.0", reason = "dep of `ndk` crate, yet to be updated" },
|
||||||
|
{ crate = "thiserror-impl@1.0", reason = "dep of `thiserror`" },
|
||||||
|
{ crate = "objc2@0.5", reason = "used by crossfont" },
|
||||||
|
{ crate = "objc2-foundation@0.2", reason = "used by crossfont" },
|
||||||
|
]
|
||||||
|
skip-tree = [
|
||||||
|
{ crate = "windows-sys", reason = "foundational but bumps fairly often, nothing we can do about it not having a shared version" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[bans.build]
|
||||||
|
bypass = [
|
||||||
|
{ crate = "android-activity", allow-globs = ["android-games-sdk/import-games-sdk.sh"] },
|
||||||
|
{ crate = "freetype-sys", allow-globs = ["freetype2/*"] },
|
||||||
|
# `crossfont` still depends (partially transitively) on `winapi`.
|
||||||
|
{ crate = "winapi-i686-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
|
||||||
|
{ crate = "winapi-x86_64-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
|
||||||
|
]
|
||||||
|
include-archives = true
|
||||||
|
interpreted = "deny"
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
# syntax=docker/dockerfile:1
|
|
||||||
# Copyright 2024 The Winit Contributors
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
ARG DISTRO=ubuntu
|
|
||||||
ARG DISTRO_VERSION=22.04
|
|
||||||
|
|
||||||
FROM "${DISTRO}":"${DISTRO_VERSION}"
|
|
||||||
SHELL ["/bin/bash", "-eEuxo", "pipefail", "-c"]
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
apt-get -o Acquire::Retries=10 -qq update && \
|
|
||||||
apt-get -o Acquire::Retries=10 -o Dpkg::Use-Pty=0 install -y --no-install-recommends \
|
|
||||||
cargo \
|
|
||||||
ca-certificates \
|
|
||||||
libx11-dev \
|
|
||||||
libxcursor-dev \
|
|
||||||
libxcb1-dev \
|
|
||||||
libxi-dev \
|
|
||||||
libxkbcommon-dev \
|
|
||||||
libxkbcommon-x11-dev \
|
|
||||||
xvfb && \
|
|
||||||
rm -rf \
|
|
||||||
/var/lib/apt/lists/* \
|
|
||||||
/var/cache/* \
|
|
||||||
/var/log/* \
|
|
||||||
/usr/share/{doc,man}
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Image Attribution
|
|
||||||
|
|
||||||
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 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.
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 73 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 73 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 73 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 73 KiB |
@@ -11,6 +11,11 @@ Unreleased` header.
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.1.2
|
||||||
|
|
||||||
|
- Added `Insets`, `LogicalInsets` and `PhysicalInsets` types.
|
||||||
|
- Make `no_std` compatible. If you use this functionality, DPI's license has changed.
|
||||||
|
|
||||||
## 0.1.1
|
## 0.1.1
|
||||||
|
|
||||||
- Derive `Debug`, `Copy`, `Clone`, `PartialEq`, `Serialize`, `Deserialize` traits for `PixelUnit`.
|
- Derive `Debug`, `Copy`, `Clone`, `PartialEq`, `Serialize`, `Deserialize` traits for `PixelUnit`.
|
||||||
|
|||||||
@@ -1,39 +1,46 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dpi"
|
|
||||||
version = "0.1.1"
|
|
||||||
description = "Types for handling UI scaling"
|
|
||||||
keywords = ["DPI", "HiDPI", "scale-factor"]
|
|
||||||
categories = ["gui"]
|
categories = ["gui"]
|
||||||
rust-version.workspace = true
|
description = "Types for handling UI scaling"
|
||||||
repository.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
keywords = ["DPI", "HiDPI", "scale-factor"]
|
||||||
|
# N.B. This is "AND", because of the imported libm code.
|
||||||
|
license = "Apache-2.0 AND MIT"
|
||||||
|
name = "dpi"
|
||||||
|
repository.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
version = "0.1.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
serde = ["dep:serde"]
|
default = ["std"]
|
||||||
|
|
||||||
mint = ["dep:mint"]
|
mint = ["dep:mint"]
|
||||||
|
serde = ["dep:serde"]
|
||||||
|
|
||||||
|
# Access mathematical functions using the standard library implementations
|
||||||
|
std = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { workspace = true, optional = true }
|
|
||||||
mint = { workspace = true, optional = true }
|
mint = { workspace = true, optional = true }
|
||||||
|
serde = { workspace = true, optional = true }
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["serde", "mint"]
|
features = ["mint", "serde"]
|
||||||
# These are all tested in CI
|
# These are all tested in CI
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
targets = [
|
targets = [
|
||||||
# Windows
|
# Windows
|
||||||
"i686-pc-windows-msvc",
|
"i686-pc-windows-msvc",
|
||||||
"x86_64-pc-windows-msvc",
|
"x86_64-pc-windows-msvc",
|
||||||
# macOS
|
# macOS
|
||||||
|
"aarch64-apple-darwin",
|
||||||
"x86_64-apple-darwin",
|
"x86_64-apple-darwin",
|
||||||
# Unix (X11 & Wayland)
|
# Unix (X11 & Wayland)
|
||||||
"i686-unknown-linux-gnu",
|
"i686-unknown-linux-gnu",
|
||||||
"x86_64-unknown-linux-gnu",
|
"x86_64-unknown-linux-gnu",
|
||||||
# iOS
|
# iOS
|
||||||
"x86_64-apple-ios",
|
"aarch64-apple-ios",
|
||||||
# Android
|
# Android
|
||||||
"aarch64-linux-android",
|
"aarch64-linux-android",
|
||||||
# Web
|
# Web
|
||||||
"wasm32-unknown-unknown",
|
"wasm32-unknown-unknown",
|
||||||
]
|
]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|||||||
51
dpi/LICENSE-LIBM-MIT
Normal file
51
dpi/LICENSE-LIBM-MIT
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
rust-lang/libm as a whole is available for use under the MIT license:
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
This Rust library contains the following copyrights:
|
||||||
|
|
||||||
|
Copyright (c) 2018 Jorge Aparicio
|
||||||
|
|
||||||
|
Portions of this software are derived from third-party works licensed under
|
||||||
|
terms compatible with the above MIT license:
|
||||||
|
|
||||||
|
* musl libc https://www.musl-libc.org/. This library contains the following
|
||||||
|
copyright:
|
||||||
|
|
||||||
|
Copyright © 2005-2020 Rich Felker, et al.
|
||||||
|
|
||||||
|
* The CORE-MATH project https://core-math.gitlabpages.inria.fr/. CORE-MATH
|
||||||
|
routines are available under the MIT license on a per-file basis.
|
||||||
|
|
||||||
|
The musl libc COPYRIGHT file also includes the following notice relevant to
|
||||||
|
math portions of the library:
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Much of the math library code (src/math/* and src/complex/*) is
|
||||||
|
Copyright © 1993,2004 Sun Microsystems or
|
||||||
|
Copyright © 2003-2011 David Schultz or
|
||||||
|
Copyright © 2003-2009 Steven G. Kargl or
|
||||||
|
Copyright © 2003-2009 Bruce D. Evans or
|
||||||
|
Copyright © 2008 Stephen L. Moshier or
|
||||||
|
Copyright © 2017-2018 Arm Limited
|
||||||
|
and labelled as such in comments in the individual source files. All
|
||||||
|
have been licensed under extremely permissive terms.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
18
dpi/README.md
Normal file
18
dpi/README.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# DPI
|
||||||
|
|
||||||
|
Full docs can be found on docs.rs.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Most of DPI is licensed under the Apache License, Version 2.0 ([LICENSE](LICENSE)).
|
||||||
|
All files except for `src/libm.rs` (and `LICENSE-LIBM-MIT`) are available solely under that license.
|
||||||
|
|
||||||
|
For its `no_std` support, DPI uses code from the [libm](https://crates.io/crates/libm) crate.
|
||||||
|
This is in the `libm.rs` file, and is licensed solely under the MIT Licence ([LICENSE-LIBM-MIT](LICENSE-LIBM-MIT)).
|
||||||
|
That file contains details of all potentially applicable copyright notices.
|
||||||
|
This is feature gated to only be included if you disable the `std` feature, otherwise it will not be compiled into your final binary
|
||||||
|
(and so these license terms will not apply).
|
||||||
|
|
||||||
|
Overall, this means that the license for this crate depends on what features you have enabled.
|
||||||
|
If you enable the `std` feature, then DPI uses only code available under the Apache-2.0 license, and so can be used under the terms of that license.
|
||||||
|
However, if you disable the `std` feature, then both these licenses must be followed to use the crate as a whole.
|
||||||
541
dpi/src/lib.rs
541
dpi/src/lib.rs
@@ -35,9 +35,9 @@
|
|||||||
//!
|
//!
|
||||||
//! ### Position and Size types
|
//! ### Position and Size types
|
||||||
//!
|
//!
|
||||||
//! The [`PhysicalPosition`] / [`PhysicalSize`] / [`PhysicalUnit`] types correspond with the actual pixels on the
|
//! The [`PhysicalPosition`] / [`PhysicalSize`] / [`PhysicalUnit`] types correspond with the actual
|
||||||
//! device, and the [`LogicalPosition`] / [`LogicalSize`] / [`LogicalUnit`] types correspond to the physical pixels
|
//! pixels on the device, and the [`LogicalPosition`] / [`LogicalSize`] / [`LogicalUnit`] types
|
||||||
//! divided by the scale factor.
|
//! correspond to the physical pixels divided by the scale factor.
|
||||||
//!
|
//!
|
||||||
//! The position and size types are generic over their exact pixel type, `P`, to allow the
|
//! The position and size types are generic over their exact pixel type, `P`, to allow the
|
||||||
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
|
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
|
||||||
@@ -52,20 +52,27 @@
|
|||||||
//!
|
//!
|
||||||
//! This crate provides the following Cargo features:
|
//! This crate provides the following Cargo features:
|
||||||
//!
|
//!
|
||||||
//! * `serde`: Enables serialization/deserialization of certain types with
|
//! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
|
||||||
//! [Serde](https://crates.io/crates/serde).
|
|
||||||
//! * `mint`: Enables mint (math interoperability standard types) conversions.
|
//! * `mint`: Enables mint (math interoperability standard types) conversions.
|
||||||
|
//! * `std` (enabled by default): Uses the standard library mathematical functions (normally through
|
||||||
|
//! your target platform's libm). This feature also changes the library's license from `Apache-2.0
|
||||||
|
//! AND MIT` to `APACHE-2.0` (only). For full details, see the package README.
|
||||||
//!
|
//!
|
||||||
|
//! To use this library on a target without the standard library available, you should disable
|
||||||
|
//! default features (thus disabling the `std` feature, with the license consequences thereof).
|
||||||
//!
|
//!
|
||||||
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
||||||
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
||||||
|
|
||||||
#![cfg_attr(
|
#![cfg_attr(docsrs, feature(doc_cfg), doc(auto_cfg(hide(doc, docsrs))))]
|
||||||
docsrs,
|
#![cfg_attr(feature = "std", forbid(unsafe_code))]
|
||||||
feature(doc_auto_cfg, doc_cfg_hide),
|
#![no_std]
|
||||||
doc(cfg_hide(doc, docsrs))
|
|
||||||
)]
|
#[cfg(not(feature = "std"))]
|
||||||
#![forbid(unsafe_code)]
|
mod libm;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "std", test))]
|
||||||
|
extern crate std;
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -77,36 +84,18 @@ pub trait Pixel: Copy + Into<f64> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pixel for u8 {
|
macro_rules! pixel_int_impl {
|
||||||
|
($($t:ty),*) => {$(
|
||||||
|
impl Pixel for $t {
|
||||||
fn from_f64(f: f64) -> Self {
|
fn from_f64(f: f64) -> Self {
|
||||||
f.round() as u8
|
round(f) as $t
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl Pixel for u16 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f.round() as u16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Pixel for u32 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f.round() as u32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Pixel for i8 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f.round() as i8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Pixel for i16 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f.round() as i16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Pixel for i32 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f.round() as i32
|
|
||||||
}
|
}
|
||||||
|
)*}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pixel_int_impl!(u8, u16, u32, i8, i16, i32);
|
||||||
|
|
||||||
impl Pixel for f32 {
|
impl Pixel for f32 {
|
||||||
fn from_f64(f: f64) -> Self {
|
fn from_f64(f: f64) -> Self {
|
||||||
f as f32
|
f as f32
|
||||||
@@ -118,11 +107,20 @@ impl Pixel for f64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Round f to the closest integer, rounding away from `0.0`
|
||||||
|
#[inline]
|
||||||
|
fn round(f: f64) -> f64 {
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
return f.round();
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
return libm::round(f);
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks that the scale factor is a normal positive `f64`.
|
/// Checks that the scale factor is a normal positive `f64`.
|
||||||
///
|
///
|
||||||
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from
|
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing
|
||||||
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
|
/// scale factors from anywhere other than winit, it's recommended to validate them using this
|
||||||
/// otherwise, you risk panics.
|
/// function before passing them to winit; otherwise, you risk panics.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn validate_scale_factor(scale_factor: f64) -> bool {
|
pub fn validate_scale_factor(scale_factor: f64) -> bool {
|
||||||
scale_factor.is_sign_positive() && scale_factor.is_normal()
|
scale_factor.is_sign_positive() && scale_factor.is_normal()
|
||||||
@@ -134,12 +132,12 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
|
|||||||
pub struct LogicalUnit<P>(pub P);
|
pub struct LogicalUnit<P>(pub P);
|
||||||
|
|
||||||
impl<P> LogicalUnit<P> {
|
impl<P> LogicalUnit<P> {
|
||||||
|
/// Represents a maximum logical unit that is equal to [`f64::MAX`].
|
||||||
|
pub const MAX: LogicalUnit<f64> = LogicalUnit::new(f64::MAX);
|
||||||
/// Represents a minimum logical unit of [`f64::MAX`].
|
/// Represents a minimum logical unit of [`f64::MAX`].
|
||||||
pub const MIN: LogicalUnit<f64> = LogicalUnit::new(f64::MIN);
|
pub const MIN: LogicalUnit<f64> = LogicalUnit::new(f64::MIN);
|
||||||
/// Represents a logical unit of `0_f64`.
|
/// Represents a logical unit of `0_f64`.
|
||||||
pub const ZERO: LogicalUnit<f64> = LogicalUnit::new(0.0);
|
pub const ZERO: LogicalUnit<f64> = LogicalUnit::new(0.0);
|
||||||
/// Represents a maximum logical unit that is equal to [`f64::MAX`].
|
|
||||||
pub const MAX: LogicalUnit<f64> = LogicalUnit::new(f64::MAX);
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn new(v: P) -> Self {
|
pub const fn new(v: P) -> Self {
|
||||||
@@ -228,12 +226,12 @@ impl<P: Pixel> From<LogicalUnit<P>> for f64 {
|
|||||||
pub struct PhysicalUnit<P>(pub P);
|
pub struct PhysicalUnit<P>(pub P);
|
||||||
|
|
||||||
impl<P> PhysicalUnit<P> {
|
impl<P> PhysicalUnit<P> {
|
||||||
|
/// Represents a maximum physical unit that is equal to [`f64::MAX`].
|
||||||
|
pub const MAX: LogicalUnit<f64> = LogicalUnit::new(f64::MAX);
|
||||||
/// Represents a minimum physical unit of [`f64::MAX`].
|
/// Represents a minimum physical unit of [`f64::MAX`].
|
||||||
pub const MIN: LogicalUnit<f64> = LogicalUnit::new(f64::MIN);
|
pub const MIN: LogicalUnit<f64> = LogicalUnit::new(f64::MIN);
|
||||||
/// Represents a physical unit of `0_f64`.
|
/// Represents a physical unit of `0_f64`.
|
||||||
pub const ZERO: LogicalUnit<f64> = LogicalUnit::new(0.0);
|
pub const ZERO: LogicalUnit<f64> = LogicalUnit::new(0.0);
|
||||||
/// Represents a maximum physical unit that is equal to [`f64::MAX`].
|
|
||||||
pub const MAX: LogicalUnit<f64> = LogicalUnit::new(f64::MAX);
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn new(v: P) -> Self {
|
pub const fn new(v: P) -> Self {
|
||||||
@@ -322,12 +320,12 @@ pub enum PixelUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PixelUnit {
|
impl PixelUnit {
|
||||||
|
/// Represents a maximum logical unit that is equal to [`f64::MAX`].
|
||||||
|
pub const MAX: PixelUnit = PixelUnit::Logical(LogicalUnit::new(f64::MAX));
|
||||||
/// Represents a minimum logical unit of [`f64::MAX`].
|
/// Represents a minimum logical unit of [`f64::MAX`].
|
||||||
pub const MIN: PixelUnit = PixelUnit::Logical(LogicalUnit::new(f64::MIN));
|
pub const MIN: PixelUnit = PixelUnit::Logical(LogicalUnit::new(f64::MIN));
|
||||||
/// Represents a logical unit of `0_f64`.
|
/// Represents a logical unit of `0_f64`.
|
||||||
pub const ZERO: PixelUnit = PixelUnit::Logical(LogicalUnit::new(0.0));
|
pub const ZERO: PixelUnit = PixelUnit::Logical(LogicalUnit::new(0.0));
|
||||||
/// Represents a maximum logical unit that is equal to [`f64::MAX`].
|
|
||||||
pub const MAX: PixelUnit = PixelUnit::Logical(LogicalUnit::new(f64::MAX));
|
|
||||||
|
|
||||||
pub fn new<S: Into<PixelUnit>>(unit: S) -> PixelUnit {
|
pub fn new<S: Into<PixelUnit>>(unit: S) -> PixelUnit {
|
||||||
unit.into()
|
unit.into()
|
||||||
@@ -362,6 +360,48 @@ impl<P: Pixel> From<LogicalUnit<P>> for PixelUnit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! vec2_from_impls {
|
||||||
|
($t:ident, $a:ident, $b:ident, $mint_ty:ident) => {
|
||||||
|
impl<P: Pixel, X: Pixel> From<(X, X)> for $t<P> {
|
||||||
|
fn from(($a, $b): (X, X)) -> Self {
|
||||||
|
Self::new($a.cast(), $b.cast())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Pixel, X: Pixel> From<$t<P>> for (X, X) {
|
||||||
|
fn from(p: $t<P>) -> Self {
|
||||||
|
(p.$a.cast(), p.$b.cast())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Pixel, X: Pixel> From<[X; 2]> for $t<P> {
|
||||||
|
fn from([$a, $b]: [X; 2]) -> Self {
|
||||||
|
Self::new($a.cast(), $b.cast())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Pixel, X: Pixel> From<$t<P>> for [X; 2] {
|
||||||
|
fn from(p: $t<P>) -> Self {
|
||||||
|
[p.$a.cast(), p.$b.cast()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mint")]
|
||||||
|
impl<P: Pixel> From<mint::$mint_ty<P>> for $t<P> {
|
||||||
|
fn from(p: mint::$mint_ty<P>) -> Self {
|
||||||
|
Self::new(p.x, p.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mint")]
|
||||||
|
impl<P: Pixel> From<$t<P>> for mint::$mint_ty<P> {
|
||||||
|
fn from(p: $t<P>) -> Self {
|
||||||
|
Self { x: p.$a, y: p.$b }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// A position represented in logical pixels.
|
/// A position represented in logical pixels.
|
||||||
///
|
///
|
||||||
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
|
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
|
||||||
@@ -400,50 +440,11 @@ impl<P: Pixel> LogicalPosition<P> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> {
|
pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> {
|
||||||
LogicalPosition {
|
LogicalPosition { x: self.x.cast(), y: self.y.cast() }
|
||||||
x: self.x.cast(),
|
|
||||||
y: self.y.cast(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
|
vec2_from_impls!(LogicalPosition, x, y, Point2);
|
||||||
fn from((x, y): (X, X)) -> LogicalPosition<P> {
|
|
||||||
LogicalPosition::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) {
|
|
||||||
fn from(p: LogicalPosition<P>) -> (X, X) {
|
|
||||||
(p.x.cast(), p.y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
|
|
||||||
fn from([x, y]: [X; 2]) -> LogicalPosition<P> {
|
|
||||||
LogicalPosition::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] {
|
|
||||||
fn from(p: LogicalPosition<P>) -> [X; 2] {
|
|
||||||
[p.x.cast(), p.y.cast()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mint")]
|
|
||||||
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
|
|
||||||
fn from(p: mint::Point2<P>) -> Self {
|
|
||||||
Self::new(p.x, p.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mint")]
|
|
||||||
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
|
|
||||||
fn from(p: LogicalPosition<P>) -> Self {
|
|
||||||
mint::Point2 { x: p.x, y: p.y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A position represented in physical pixels.
|
/// A position represented in physical pixels.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||||
@@ -479,50 +480,11 @@ impl<P: Pixel> PhysicalPosition<P> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> {
|
pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> {
|
||||||
PhysicalPosition {
|
PhysicalPosition { x: self.x.cast(), y: self.y.cast() }
|
||||||
x: self.x.cast(),
|
|
||||||
y: self.y.cast(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
|
vec2_from_impls!(PhysicalPosition, x, y, Point2);
|
||||||
fn from((x, y): (X, X)) -> PhysicalPosition<P> {
|
|
||||||
PhysicalPosition::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) {
|
|
||||||
fn from(p: PhysicalPosition<P>) -> (X, X) {
|
|
||||||
(p.x.cast(), p.y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
|
|
||||||
fn from([x, y]: [X; 2]) -> PhysicalPosition<P> {
|
|
||||||
PhysicalPosition::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] {
|
|
||||||
fn from(p: PhysicalPosition<P>) -> [X; 2] {
|
|
||||||
[p.x.cast(), p.y.cast()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mint")]
|
|
||||||
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
|
|
||||||
fn from(p: mint::Point2<P>) -> Self {
|
|
||||||
Self::new(p.x, p.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mint")]
|
|
||||||
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
|
|
||||||
fn from(p: PhysicalPosition<P>) -> Self {
|
|
||||||
mint::Point2 { x: p.x, y: p.y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A size represented in logical pixels.
|
/// A size represented in logical pixels.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||||
@@ -558,53 +520,11 @@ impl<P: Pixel> LogicalSize<P> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cast<X: Pixel>(&self) -> LogicalSize<X> {
|
pub fn cast<X: Pixel>(&self) -> LogicalSize<X> {
|
||||||
LogicalSize {
|
LogicalSize { width: self.width.cast(), height: self.height.cast() }
|
||||||
width: self.width.cast(),
|
|
||||||
height: self.height.cast(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
|
vec2_from_impls!(LogicalSize, width, height, Vector2);
|
||||||
fn from((x, y): (X, X)) -> LogicalSize<P> {
|
|
||||||
LogicalSize::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for (X, X) {
|
|
||||||
fn from(s: LogicalSize<P>) -> (X, X) {
|
|
||||||
(s.width.cast(), s.height.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
|
|
||||||
fn from([x, y]: [X; 2]) -> LogicalSize<P> {
|
|
||||||
LogicalSize::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for [X; 2] {
|
|
||||||
fn from(s: LogicalSize<P>) -> [X; 2] {
|
|
||||||
[s.width.cast(), s.height.cast()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mint")]
|
|
||||||
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
|
|
||||||
fn from(v: mint::Vector2<P>) -> Self {
|
|
||||||
Self::new(v.x, v.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mint")]
|
|
||||||
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
|
|
||||||
fn from(s: LogicalSize<P>) -> Self {
|
|
||||||
mint::Vector2 {
|
|
||||||
x: s.width,
|
|
||||||
y: s.height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A size represented in physical pixels.
|
/// A size represented in physical pixels.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||||
@@ -637,53 +557,11 @@ impl<P: Pixel> PhysicalSize<P> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cast<X: Pixel>(&self) -> PhysicalSize<X> {
|
pub fn cast<X: Pixel>(&self) -> PhysicalSize<X> {
|
||||||
PhysicalSize {
|
PhysicalSize { width: self.width.cast(), height: self.height.cast() }
|
||||||
width: self.width.cast(),
|
|
||||||
height: self.height.cast(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
|
vec2_from_impls!(PhysicalSize, width, height, Vector2);
|
||||||
fn from((x, y): (X, X)) -> PhysicalSize<P> {
|
|
||||||
PhysicalSize::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for (X, X) {
|
|
||||||
fn from(s: PhysicalSize<P>) -> (X, X) {
|
|
||||||
(s.width.cast(), s.height.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
|
|
||||||
fn from([x, y]: [X; 2]) -> PhysicalSize<P> {
|
|
||||||
PhysicalSize::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for [X; 2] {
|
|
||||||
fn from(s: PhysicalSize<P>) -> [X; 2] {
|
|
||||||
[s.width.cast(), s.height.cast()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mint")]
|
|
||||||
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
|
|
||||||
fn from(v: mint::Vector2<P>) -> Self {
|
|
||||||
Self::new(v.x, v.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mint")]
|
|
||||||
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
|
|
||||||
fn from(s: PhysicalSize<P>) -> Self {
|
|
||||||
mint::Vector2 {
|
|
||||||
x: s.width,
|
|
||||||
y: s.height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A size that's either physical or logical.
|
/// A size that's either physical or logical.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
@@ -782,11 +660,156 @@ impl<P: Pixel> From<LogicalPosition<P>> for Position {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The logical distance between the edges of two rectangles.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct LogicalInsets<P> {
|
||||||
|
/// The distance to the top edge.
|
||||||
|
pub top: P,
|
||||||
|
/// The distance to the left edge.
|
||||||
|
pub left: P,
|
||||||
|
/// The distance to the bottom edge.
|
||||||
|
pub bottom: P,
|
||||||
|
/// The distance to the right edge.
|
||||||
|
pub right: P,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> LogicalInsets<P> {
|
||||||
|
#[inline]
|
||||||
|
pub const fn new(top: P, left: P, bottom: P, right: P) -> Self {
|
||||||
|
Self { top, left, bottom, right }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Pixel> LogicalInsets<P> {
|
||||||
|
#[inline]
|
||||||
|
pub fn from_physical<T: Into<PhysicalInsets<X>>, X: Pixel>(
|
||||||
|
physical: T,
|
||||||
|
scale_factor: f64,
|
||||||
|
) -> Self {
|
||||||
|
physical.into().to_logical(scale_factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalInsets<X> {
|
||||||
|
assert!(validate_scale_factor(scale_factor));
|
||||||
|
let top = self.top.into() * scale_factor;
|
||||||
|
let left = self.left.into() * scale_factor;
|
||||||
|
let bottom = self.bottom.into() * scale_factor;
|
||||||
|
let right = self.right.into() * scale_factor;
|
||||||
|
PhysicalInsets::new(top, left, bottom, right).cast()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cast<X: Pixel>(&self) -> LogicalInsets<X> {
|
||||||
|
LogicalInsets {
|
||||||
|
top: self.top.cast(),
|
||||||
|
left: self.left.cast(),
|
||||||
|
bottom: self.bottom.cast(),
|
||||||
|
right: self.right.cast(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The physical distance between the edges of two rectangles.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct PhysicalInsets<P> {
|
||||||
|
/// The distance to the top edge.
|
||||||
|
pub top: P,
|
||||||
|
/// The distance to the left edge.
|
||||||
|
pub left: P,
|
||||||
|
/// The distance to the bottom edge.
|
||||||
|
pub bottom: P,
|
||||||
|
/// The distance to the right edge.
|
||||||
|
pub right: P,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> PhysicalInsets<P> {
|
||||||
|
#[inline]
|
||||||
|
pub const fn new(top: P, left: P, bottom: P, right: P) -> Self {
|
||||||
|
Self { top, left, bottom, right }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Pixel> PhysicalInsets<P> {
|
||||||
|
#[inline]
|
||||||
|
pub fn from_logical<T: Into<LogicalInsets<X>>, X: Pixel>(
|
||||||
|
logical: T,
|
||||||
|
scale_factor: f64,
|
||||||
|
) -> Self {
|
||||||
|
logical.into().to_physical(scale_factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalInsets<X> {
|
||||||
|
assert!(validate_scale_factor(scale_factor));
|
||||||
|
let top = self.top.into() / scale_factor;
|
||||||
|
let left = self.left.into() / scale_factor;
|
||||||
|
let bottom = self.bottom.into() / scale_factor;
|
||||||
|
let right = self.right.into() / scale_factor;
|
||||||
|
LogicalInsets::new(top, left, bottom, right).cast()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cast<X: Pixel>(&self) -> PhysicalInsets<X> {
|
||||||
|
PhysicalInsets {
|
||||||
|
top: self.top.cast(),
|
||||||
|
left: self.left.cast(),
|
||||||
|
bottom: self.bottom.cast(),
|
||||||
|
right: self.right.cast(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insets that are either physical or logical.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum Insets {
|
||||||
|
Physical(PhysicalInsets<u32>),
|
||||||
|
Logical(LogicalInsets<f64>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Insets {
|
||||||
|
pub fn new<S: Into<Self>>(insets: S) -> Self {
|
||||||
|
insets.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalInsets<P> {
|
||||||
|
match *self {
|
||||||
|
Self::Physical(insets) => insets.to_logical(scale_factor),
|
||||||
|
Self::Logical(insets) => insets.cast(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalInsets<P> {
|
||||||
|
match *self {
|
||||||
|
Self::Physical(insets) => insets.cast(),
|
||||||
|
Self::Logical(insets) => insets.to_physical(scale_factor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Pixel> From<PhysicalInsets<P>> for Insets {
|
||||||
|
#[inline]
|
||||||
|
fn from(insets: PhysicalInsets<P>) -> Self {
|
||||||
|
Self::Physical(insets.cast())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Pixel> From<LogicalInsets<P>> for Insets {
|
||||||
|
#[inline]
|
||||||
|
fn from(insets: LogicalInsets<P>) -> Self {
|
||||||
|
Self::Logical(insets.cast())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
macro_rules! test_pixel_int_impl {
|
macro_rules! test_pixel_int_impl {
|
||||||
($($name:ident => $ty:ty),*) => {$(
|
($($name:ident => $ty:ty),*) => {$(
|
||||||
#[test]
|
#[test]
|
||||||
@@ -846,12 +869,7 @@ mod tests {
|
|||||||
|
|
||||||
macro_rules! assert_approx_eq {
|
macro_rules! assert_approx_eq {
|
||||||
($a:expr, $b:expr $(,)?) => {
|
($a:expr, $b:expr $(,)?) => {
|
||||||
assert!(
|
assert!(($a - $b).abs() < 0.001, "{} is not approximately equal to {}", $a, $b);
|
||||||
($a - $b).abs() < 0.001,
|
|
||||||
"{} is not approximately equal to {}",
|
|
||||||
$a,
|
|
||||||
$b
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -970,14 +988,8 @@ mod tests {
|
|||||||
assert_eq!(log_unit.to_physical::<u32>(1.0), PhysicalUnit::new(1));
|
assert_eq!(log_unit.to_physical::<u32>(1.0), PhysicalUnit::new(1));
|
||||||
assert_eq!(log_unit.to_physical::<u32>(2.0), PhysicalUnit::new(2));
|
assert_eq!(log_unit.to_physical::<u32>(2.0), PhysicalUnit::new(2));
|
||||||
assert_eq!(log_unit.cast::<u32>(), LogicalUnit::new(1));
|
assert_eq!(log_unit.cast::<u32>(), LogicalUnit::new(1));
|
||||||
assert_eq!(
|
assert_eq!(log_unit, LogicalUnit::from_physical(PhysicalUnit::new(1.0), 1.0));
|
||||||
log_unit,
|
assert_eq!(log_unit, LogicalUnit::from_physical(PhysicalUnit::new(2.0), 2.0));
|
||||||
LogicalUnit::from_physical(PhysicalUnit::new(1.0), 1.0)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
log_unit,
|
|
||||||
LogicalUnit::from_physical(PhysicalUnit::new(2.0), 2.0)
|
|
||||||
);
|
|
||||||
assert_eq!(LogicalUnit::from(2.0), LogicalUnit::new(2.0));
|
assert_eq!(LogicalUnit::from(2.0), LogicalUnit::new(2.0));
|
||||||
|
|
||||||
let x: f64 = log_unit.into();
|
let x: f64 = log_unit.into();
|
||||||
@@ -986,14 +998,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_physical_unit() {
|
fn test_physical_unit() {
|
||||||
assert_eq!(
|
assert_eq!(PhysicalUnit::from_logical(LogicalUnit::new(1.0), 1.0), PhysicalUnit::new(1));
|
||||||
PhysicalUnit::from_logical(LogicalUnit::new(1.0), 1.0),
|
assert_eq!(PhysicalUnit::from_logical(LogicalUnit::new(2.0), 0.5), PhysicalUnit::new(1));
|
||||||
PhysicalUnit::new(1)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
PhysicalUnit::from_logical(LogicalUnit::new(2.0), 0.5),
|
|
||||||
PhysicalUnit::new(1)
|
|
||||||
);
|
|
||||||
assert_eq!(PhysicalUnit::from(2.0), PhysicalUnit::new(2.0,));
|
assert_eq!(PhysicalUnit::from(2.0), PhysicalUnit::new(2.0,));
|
||||||
assert_eq!(PhysicalUnit::from(2.0), PhysicalUnit::new(2.0));
|
assert_eq!(PhysicalUnit::from(2.0), PhysicalUnit::new(2.0));
|
||||||
|
|
||||||
@@ -1007,22 +1013,10 @@ mod tests {
|
|||||||
assert_eq!(log_pos.to_physical::<u32>(1.0), PhysicalPosition::new(1, 2));
|
assert_eq!(log_pos.to_physical::<u32>(1.0), PhysicalPosition::new(1, 2));
|
||||||
assert_eq!(log_pos.to_physical::<u32>(2.0), PhysicalPosition::new(2, 4));
|
assert_eq!(log_pos.to_physical::<u32>(2.0), PhysicalPosition::new(2, 4));
|
||||||
assert_eq!(log_pos.cast::<u32>(), LogicalPosition::new(1, 2));
|
assert_eq!(log_pos.cast::<u32>(), LogicalPosition::new(1, 2));
|
||||||
assert_eq!(
|
assert_eq!(log_pos, LogicalPosition::from_physical(PhysicalPosition::new(1.0, 2.0), 1.0));
|
||||||
log_pos,
|
assert_eq!(log_pos, LogicalPosition::from_physical(PhysicalPosition::new(2.0, 4.0), 2.0));
|
||||||
LogicalPosition::from_physical(PhysicalPosition::new(1.0, 2.0), 1.0)
|
assert_eq!(LogicalPosition::from((2.0, 2.0)), LogicalPosition::new(2.0, 2.0));
|
||||||
);
|
assert_eq!(LogicalPosition::from([2.0, 3.0]), LogicalPosition::new(2.0, 3.0));
|
||||||
assert_eq!(
|
|
||||||
log_pos,
|
|
||||||
LogicalPosition::from_physical(PhysicalPosition::new(2.0, 4.0), 2.0)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
LogicalPosition::from((2.0, 2.0)),
|
|
||||||
LogicalPosition::new(2.0, 2.0)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
LogicalPosition::from([2.0, 3.0]),
|
|
||||||
LogicalPosition::new(2.0, 3.0)
|
|
||||||
);
|
|
||||||
|
|
||||||
let x: (f64, f64) = log_pos.into();
|
let x: (f64, f64) = log_pos.into();
|
||||||
assert_eq!(x, (1.0, 2.0));
|
assert_eq!(x, (1.0, 2.0));
|
||||||
@@ -1040,14 +1034,8 @@ mod tests {
|
|||||||
PhysicalPosition::from_logical(LogicalPosition::new(2.0, 4.0), 0.5),
|
PhysicalPosition::from_logical(LogicalPosition::new(2.0, 4.0), 0.5),
|
||||||
PhysicalPosition::new(1, 2)
|
PhysicalPosition::new(1, 2)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(PhysicalPosition::from((2.0, 2.0)), PhysicalPosition::new(2.0, 2.0));
|
||||||
PhysicalPosition::from((2.0, 2.0)),
|
assert_eq!(PhysicalPosition::from([2.0, 3.0]), PhysicalPosition::new(2.0, 3.0));
|
||||||
PhysicalPosition::new(2.0, 2.0)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
PhysicalPosition::from([2.0, 3.0]),
|
|
||||||
PhysicalPosition::new(2.0, 3.0)
|
|
||||||
);
|
|
||||||
|
|
||||||
let x: (f64, f64) = PhysicalPosition::new(1, 2).into();
|
let x: (f64, f64) = PhysicalPosition::new(1, 2).into();
|
||||||
assert_eq!(x, (1.0, 2.0));
|
assert_eq!(x, (1.0, 2.0));
|
||||||
@@ -1061,14 +1049,8 @@ mod tests {
|
|||||||
assert_eq!(log_size.to_physical::<u32>(1.0), PhysicalSize::new(1, 2));
|
assert_eq!(log_size.to_physical::<u32>(1.0), PhysicalSize::new(1, 2));
|
||||||
assert_eq!(log_size.to_physical::<u32>(2.0), PhysicalSize::new(2, 4));
|
assert_eq!(log_size.to_physical::<u32>(2.0), PhysicalSize::new(2, 4));
|
||||||
assert_eq!(log_size.cast::<u32>(), LogicalSize::new(1, 2));
|
assert_eq!(log_size.cast::<u32>(), LogicalSize::new(1, 2));
|
||||||
assert_eq!(
|
assert_eq!(log_size, LogicalSize::from_physical(PhysicalSize::new(1.0, 2.0), 1.0));
|
||||||
log_size,
|
assert_eq!(log_size, LogicalSize::from_physical(PhysicalSize::new(2.0, 4.0), 2.0));
|
||||||
LogicalSize::from_physical(PhysicalSize::new(1.0, 2.0), 1.0)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
log_size,
|
|
||||||
LogicalSize::from_physical(PhysicalSize::new(2.0, 4.0), 2.0)
|
|
||||||
);
|
|
||||||
assert_eq!(LogicalSize::from((2.0, 2.0)), LogicalSize::new(2.0, 2.0));
|
assert_eq!(LogicalSize::from((2.0, 2.0)), LogicalSize::new(2.0, 2.0));
|
||||||
assert_eq!(LogicalSize::from([2.0, 3.0]), LogicalSize::new(2.0, 3.0));
|
assert_eq!(LogicalSize::from([2.0, 3.0]), LogicalSize::new(2.0, 3.0));
|
||||||
|
|
||||||
@@ -1099,10 +1081,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
assert_eq!(
|
assert_eq!(Size::new(PhysicalSize::new(1, 2)), Size::Physical(PhysicalSize::new(1, 2)));
|
||||||
Size::new(PhysicalSize::new(1, 2)),
|
|
||||||
Size::Physical(PhysicalSize::new(1, 2))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Size::new(LogicalSize::new(1.0, 2.0)),
|
Size::new(LogicalSize::new(1.0, 2.0)),
|
||||||
Size::Logical(LogicalSize::new(1.0, 2.0))
|
Size::Logical(LogicalSize::new(1.0, 2.0))
|
||||||
@@ -1192,20 +1171,20 @@ mod tests {
|
|||||||
// Eat coverage for the Debug impls et al
|
// Eat coverage for the Debug impls et al
|
||||||
#[test]
|
#[test]
|
||||||
fn ensure_attrs_do_not_panic() {
|
fn ensure_attrs_do_not_panic() {
|
||||||
let _ = format!("{:?}", LogicalPosition::<u32>::default().clone());
|
let _ = std::format!("{:?}", LogicalPosition::<u32>::default().clone());
|
||||||
HashSet::new().insert(LogicalPosition::<u32>::default());
|
HashSet::new().insert(LogicalPosition::<u32>::default());
|
||||||
|
|
||||||
let _ = format!("{:?}", PhysicalPosition::<u32>::default().clone());
|
let _ = std::format!("{:?}", PhysicalPosition::<u32>::default().clone());
|
||||||
HashSet::new().insert(PhysicalPosition::<u32>::default());
|
HashSet::new().insert(PhysicalPosition::<u32>::default());
|
||||||
|
|
||||||
let _ = format!("{:?}", LogicalSize::<u32>::default().clone());
|
let _ = std::format!("{:?}", LogicalSize::<u32>::default().clone());
|
||||||
HashSet::new().insert(LogicalSize::<u32>::default());
|
HashSet::new().insert(LogicalSize::<u32>::default());
|
||||||
|
|
||||||
let _ = format!("{:?}", PhysicalSize::<u32>::default().clone());
|
let _ = std::format!("{:?}", PhysicalSize::<u32>::default().clone());
|
||||||
HashSet::new().insert(PhysicalSize::<u32>::default());
|
HashSet::new().insert(PhysicalSize::<u32>::default());
|
||||||
|
|
||||||
let _ = format!("{:?}", Size::Physical((1, 2).into()).clone());
|
let _ = std::format!("{:?}", Size::Physical((1, 2).into()).clone());
|
||||||
let _ = format!("{:?}", Position::Physical((1, 2).into()).clone());
|
let _ = std::format!("{:?}", Position::Physical((1, 2).into()).clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
56
dpi/src/libm.rs
Normal file
56
dpi/src/libm.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright (c) 2018 Jorge Aparicio
|
||||||
|
// Copyright © 2005-2020 Rich Felker, et al.
|
||||||
|
// Copyright © 1993,2004 Sun Microsystems or
|
||||||
|
// Copyright © 2003-2011 David Schultz or
|
||||||
|
// Copyright © 2003-2009 Steven G. Kargl or
|
||||||
|
// Copyright © 2003-2009 Bruce D. Evans or
|
||||||
|
// Copyright © 2008 Stephen L. Moshier or
|
||||||
|
// Copyright © 2017-2018 Arm Limited
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// This file is licensed solely under the terms discussed in LICENSE-LIBM-MIT at the crate root.
|
||||||
|
// See the package-level README for full details.
|
||||||
|
|
||||||
|
// Taken from https://github.com/rust-lang/libm/blob/master/src/math/mod.rs#L1
|
||||||
|
macro_rules! force_eval {
|
||||||
|
($e:expr) => {
|
||||||
|
unsafe { ::core::ptr::read_volatile(&$e) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/round.rs
|
||||||
|
pub(crate) fn round(x: f64) -> f64 {
|
||||||
|
trunc(x + copysign(0.5 - 0.25 * f64::EPSILON, x))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from: https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/trunc.rs#L8-L12
|
||||||
|
#[allow(clippy::needless_late_init /*, reason = "The original libm code uses this style" */)]
|
||||||
|
fn trunc(x: f64) -> f64 {
|
||||||
|
let x1p120 = f64::from_bits(0x4770000000000000); // 0x1p120f === 2 ^ 120
|
||||||
|
|
||||||
|
let mut i: u64 = x.to_bits();
|
||||||
|
let mut e: i64 = ((i >> 52) & 0x7ff) as i64 - 0x3ff + 12;
|
||||||
|
let m: u64;
|
||||||
|
|
||||||
|
if e >= 52 + 12 {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
if e < 12 {
|
||||||
|
e = 1;
|
||||||
|
}
|
||||||
|
m = -1i64 as u64 >> e;
|
||||||
|
if (i & m) == 0 {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
force_eval!(x + x1p120);
|
||||||
|
i &= !m;
|
||||||
|
f64::from_bits(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/copysign.rs
|
||||||
|
fn copysign(x: f64, y: f64) -> f64 {
|
||||||
|
let mut ux = x.to_bits();
|
||||||
|
let uy = y.to_bits();
|
||||||
|
ux &= (!0) >> 1;
|
||||||
|
ux |= uy & (1 << 63);
|
||||||
|
f64::from_bits(ux)
|
||||||
|
}
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
#[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};
|
|
||||||
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
|
|
||||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
|
||||||
use winit::raw_window_handle::HasRawWindowHandle;
|
|
||||||
use winit::window::Window;
|
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
|
||||||
mod fill;
|
|
||||||
|
|
||||||
fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window {
|
|
||||||
let parent = parent.raw_window_handle().unwrap();
|
|
||||||
let mut window_attributes = Window::default_attributes()
|
|
||||||
.with_title("child window")
|
|
||||||
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
|
|
||||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
|
||||||
.with_visible(true);
|
|
||||||
// `with_parent_window` is unsafe. Parent window must be a valid window.
|
|
||||||
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };
|
|
||||||
|
|
||||||
event_loop.create_window(window_attributes).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut windows = HashMap::new();
|
|
||||||
|
|
||||||
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
|
|
||||||
let mut parent_window_id = None;
|
|
||||||
|
|
||||||
event_loop.run(move |event: Event<()>, event_loop| {
|
|
||||||
match event {
|
|
||||||
Event::Resumed => {
|
|
||||||
let attributes = Window::default_attributes()
|
|
||||||
.with_title("parent window")
|
|
||||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
|
||||||
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
|
|
||||||
let window = event_loop.create_window(attributes).unwrap();
|
|
||||||
|
|
||||||
parent_window_id = Some(window.id());
|
|
||||||
|
|
||||||
println!("Parent window id: {parent_window_id:?})");
|
|
||||||
windows.insert(window.id(), window);
|
|
||||||
}
|
|
||||||
Event::WindowEvent { window_id, event } => match event {
|
|
||||||
WindowEvent::CloseRequested => {
|
|
||||||
windows.clear();
|
|
||||||
event_loop.exit();
|
|
||||||
}
|
|
||||||
WindowEvent::CursorEntered { device_id: _ } => {
|
|
||||||
// On x11, println when the cursor entered in a window even if the child window is created
|
|
||||||
// by some key inputs.
|
|
||||||
// the child windows are always placed at (0, 0) with size (200, 200) in the parent window,
|
|
||||||
// so we also can see this log when we move the cursor around (200, 200) in parent window.
|
|
||||||
println!("cursor entered in the window {window_id:?}");
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
event:
|
|
||||||
KeyEvent {
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let parent_window = windows.get(&parent_window_id.unwrap()).unwrap();
|
|
||||||
let child_window = spawn_child_window(parent_window, event_loop);
|
|
||||||
let child_id = child_window.id();
|
|
||||||
println!("Child window created with id: {child_id:?}");
|
|
||||||
windows.insert(child_id, child_window);
|
|
||||||
}
|
|
||||||
WindowEvent::RedrawRequested => {
|
|
||||||
if let Some(window) = windows.get(&window_id) {
|
|
||||||
fill::fill_window(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
feature = "rwh_06",
|
|
||||||
not(any(x11_platform, macos_platform, windows_platform))
|
|
||||||
))]
|
|
||||||
fn main() {
|
|
||||||
panic!("This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature enabled.");
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
// Limit this example to only compatible platforms.
|
|
||||||
#[cfg(any(
|
|
||||||
windows_platform,
|
|
||||||
macos_platform,
|
|
||||||
x11_platform,
|
|
||||||
wayland_platform,
|
|
||||||
android_platform,
|
|
||||||
))]
|
|
||||||
fn main() -> std::process::ExitCode {
|
|
||||||
use std::{process::ExitCode, thread::sleep, time::Duration};
|
|
||||||
|
|
||||||
use winit::application::ApplicationHandler;
|
|
||||||
use winit::event::WindowEvent;
|
|
||||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
|
||||||
use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
|
|
||||||
use winit::window::{Window, WindowId};
|
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
|
||||||
mod fill;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct PumpDemo {
|
|
||||||
window: Option<Window>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationHandler for PumpDemo {
|
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
let window_attributes = Window::default_attributes().with_title("A fantastic window!");
|
|
||||||
self.window = Some(event_loop.create_window(window_attributes).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &ActiveEventLoop,
|
|
||||||
_window_id: WindowId,
|
|
||||||
event: WindowEvent,
|
|
||||||
) {
|
|
||||||
println!("{event:?}");
|
|
||||||
|
|
||||||
let window = match self.window.as_ref() {
|
|
||||||
Some(window) => window,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
|
||||||
WindowEvent::CloseRequested => event_loop.exit(),
|
|
||||||
WindowEvent::RedrawRequested => {
|
|
||||||
fill::fill_window(window);
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut event_loop = EventLoop::new().unwrap();
|
|
||||||
|
|
||||||
tracing_subscriber::fmt::init();
|
|
||||||
|
|
||||||
let mut app = PumpDemo::default();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let timeout = Some(Duration::ZERO);
|
|
||||||
let status = event_loop.pump_app_events(timeout, &mut app);
|
|
||||||
|
|
||||||
if let PumpStatus::Exit(exit_code) = status {
|
|
||||||
break ExitCode::from(exit_code as u8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep for 1/60 second to simulate application work
|
|
||||||
//
|
|
||||||
// Since `pump_events` doesn't block it will be important to
|
|
||||||
// throttle the loop in the app somehow.
|
|
||||||
println!("Update()");
|
|
||||||
sleep(Duration::from_millis(16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(ios_platform, web_platform, orbital_platform))]
|
|
||||||
fn main() {
|
|
||||||
println!("This platform doesn't support pump_events.");
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
// Limit this example to only compatible platforms.
|
|
||||||
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))]
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use winit::application::ApplicationHandler;
|
|
||||||
use winit::event::WindowEvent;
|
|
||||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
|
||||||
use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
|
|
||||||
use winit::window::{Window, WindowId};
|
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
|
||||||
mod fill;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct App {
|
|
||||||
idx: usize,
|
|
||||||
window_id: Option<WindowId>,
|
|
||||||
window: Option<Window>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationHandler for App {
|
|
||||||
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
|
|
||||||
if let Some(window) = self.window.as_ref() {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
let window_attributes = Window::default_attributes()
|
|
||||||
.with_title("Fantastic window number one!")
|
|
||||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0));
|
|
||||||
let window = event_loop.create_window(window_attributes).unwrap();
|
|
||||||
self.window_id = Some(window.id());
|
|
||||||
self.window = Some(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &ActiveEventLoop,
|
|
||||||
window_id: WindowId,
|
|
||||||
event: WindowEvent,
|
|
||||||
) {
|
|
||||||
if event == WindowEvent::Destroyed && self.window_id == Some(window_id) {
|
|
||||||
println!(
|
|
||||||
"--------------------------------------------------------- Window {} Destroyed",
|
|
||||||
self.idx
|
|
||||||
);
|
|
||||||
self.window_id = None;
|
|
||||||
event_loop.exit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let window = match self.window.as_mut() {
|
|
||||||
Some(window) => window,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
|
||||||
WindowEvent::CloseRequested => {
|
|
||||||
println!("--------------------------------------------------------- Window {} CloseRequested", self.idx);
|
|
||||||
fill::cleanup_window(window);
|
|
||||||
self.window = None;
|
|
||||||
}
|
|
||||||
WindowEvent::RedrawRequested => {
|
|
||||||
fill::fill_window(window);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing_subscriber::fmt::init();
|
|
||||||
|
|
||||||
let mut event_loop = EventLoop::new().unwrap();
|
|
||||||
|
|
||||||
let mut app = App {
|
|
||||||
idx: 1,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
event_loop.run_app_on_demand(&mut app)?;
|
|
||||||
|
|
||||||
println!("--------------------------------------------------------- Finished first loop");
|
|
||||||
println!("--------------------------------------------------------- Waiting 5 seconds");
|
|
||||||
std::thread::sleep(Duration::from_secs(5));
|
|
||||||
|
|
||||||
app.idx += 1;
|
|
||||||
event_loop.run_app_on_demand(&mut app)?;
|
|
||||||
println!("--------------------------------------------------------- Finished second loop");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))]
|
|
||||||
fn main() {
|
|
||||||
println!("This example is not supported on this platform");
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
//! Fill the window buffer with a solid color.
|
|
||||||
//!
|
|
||||||
//! Launching a window without drawing to it has unpredictable results varying from platform to
|
|
||||||
//! platform. In order to have well-defined examples, this module provides an easy way to
|
|
||||||
//! fill the window buffer with a solid color.
|
|
||||||
//!
|
|
||||||
//! 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.
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use platform::cleanup_window;
|
|
||||||
pub use platform::fill_window;
|
|
||||||
|
|
||||||
#[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.
|
|
||||||
context: Context,
|
|
||||||
|
|
||||||
/// The hash map of window IDs to surfaces.
|
|
||||||
surfaces: HashMap<WindowId, Surface>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GraphicsContext {
|
|
||||||
fn new(w: &Window) -> Self {
|
|
||||||
Self {
|
|
||||||
context: unsafe { Context::new(w) }.expect("Failed to create a softbuffer context"),
|
|
||||||
surfaces: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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(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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,984 +0,0 @@
|
|||||||
//! Simple winit application.
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
use std::num::NonZeroU32;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use cursor_icon::CursorIcon;
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
use rwh_05::HasRawDisplayHandle;
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
use softbuffer::{Context, Surface};
|
|
||||||
|
|
||||||
use winit::application::ApplicationHandler;
|
|
||||||
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
|
|
||||||
use winit::event::{DeviceEvent, DeviceId, Ime, WindowEvent};
|
|
||||||
use winit::event::{MouseButton, MouseScrollDelta};
|
|
||||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
|
||||||
use winit::keyboard::{Key, ModifiersState};
|
|
||||||
use winit::window::{
|
|
||||||
Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection,
|
|
||||||
Theme,
|
|
||||||
};
|
|
||||||
use winit::window::{Window, WindowId};
|
|
||||||
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS};
|
|
||||||
#[cfg(any(x11_platform, wayland_platform))]
|
|
||||||
use winit::platform::startup_notify::{
|
|
||||||
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The amount of points to around the window for drag resize direction calculations.
|
|
||||||
const BORDER_SIZE: f64 = 20.;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
let event_loop = EventLoop::<UserEvent>::with_user_event().build()?;
|
|
||||||
let _event_loop_proxy = event_loop.create_proxy();
|
|
||||||
|
|
||||||
// Wire the user event from another thread.
|
|
||||||
#[cfg(not(web_platform))]
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
// Wake up the `event_loop` once every second and dispatch a custom event
|
|
||||||
// from a different thread.
|
|
||||||
println!("Starting to send user event every second");
|
|
||||||
loop {
|
|
||||||
let _ = _event_loop_proxy.send_event(UserEvent::WakeUp);
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut state = Application::new(&event_loop);
|
|
||||||
|
|
||||||
event_loop.run_app(&mut state).map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum UserEvent {
|
|
||||||
WakeUp,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Application state and event handling.
|
|
||||||
struct Application {
|
|
||||||
/// Custom cursors assets.
|
|
||||||
custom_cursors: Vec<CustomCursor>,
|
|
||||||
/// Application icon.
|
|
||||||
icon: Icon,
|
|
||||||
windows: HashMap<WindowId, WindowState>,
|
|
||||||
/// Drawing context.
|
|
||||||
///
|
|
||||||
/// With OpenGL it could be EGLDisplay.
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
context: Option<Context>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Application {
|
|
||||||
fn new<T>(event_loop: &EventLoop<T>) -> Self {
|
|
||||||
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
let context = Some(unsafe { Context::from_raw(event_loop.raw_display_handle()).unwrap() });
|
|
||||||
|
|
||||||
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
|
|
||||||
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
|
|
||||||
// since it seems to work well enough in most cases. Be careful about going too high, or
|
|
||||||
// you'll be bitten by the low-quality downscaling built into the WM.
|
|
||||||
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/data/icon.png");
|
|
||||||
|
|
||||||
let icon = load_icon(Path::new(path));
|
|
||||||
|
|
||||||
println!("Loading cursor assets");
|
|
||||||
let custom_cursors = vec![
|
|
||||||
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross.png"))),
|
|
||||||
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross2.png"))),
|
|
||||||
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/gradient.png"))),
|
|
||||||
];
|
|
||||||
|
|
||||||
Self {
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
context,
|
|
||||||
custom_cursors,
|
|
||||||
icon,
|
|
||||||
windows: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_window(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &ActiveEventLoop,
|
|
||||||
_tab_id: Option<String>,
|
|
||||||
) -> Result<WindowId, Box<dyn Error>> {
|
|
||||||
// TODO read-out activation token.
|
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut window_attributes = Window::default_attributes()
|
|
||||||
.with_title("Winit window")
|
|
||||||
.with_transparent(true)
|
|
||||||
.with_window_icon(Some(self.icon.clone()));
|
|
||||||
|
|
||||||
#[cfg(any(x11_platform, wayland_platform))]
|
|
||||||
if let Some(token) = event_loop.read_token_from_env() {
|
|
||||||
startup_notify::reset_activation_token_env();
|
|
||||||
println!("Using token {:?} to activate a window", token);
|
|
||||||
window_attributes = window_attributes.with_activation_token(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
if let Some(tab_id) = _tab_id {
|
|
||||||
window_attributes = window_attributes.with_tabbing_identifier(&tab_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let window = event_loop.create_window(window_attributes)?;
|
|
||||||
|
|
||||||
#[cfg(ios_platform)]
|
|
||||||
{
|
|
||||||
use winit::platform::ios::WindowExtIOS;
|
|
||||||
window.recognize_doubletap_gesture(true);
|
|
||||||
window.recognize_pinch_gesture(true);
|
|
||||||
window.recognize_rotation_gesture(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let window_state = WindowState::new(self, window)?;
|
|
||||||
let window_id = window_state.window.id();
|
|
||||||
println!("Created new window with id={window_id:?}");
|
|
||||||
self.windows.insert(window_id, window_state);
|
|
||||||
Ok(window_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_action(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, action: Action) {
|
|
||||||
// let cursor_position = self.cursor_position;
|
|
||||||
let window = self.windows.get_mut(&window_id).unwrap();
|
|
||||||
println!("Executing action: {action:?}");
|
|
||||||
match action {
|
|
||||||
Action::CloseWindow => {
|
|
||||||
let _ = self.windows.remove(&window_id);
|
|
||||||
}
|
|
||||||
Action::CreateNewWindow => {
|
|
||||||
#[cfg(any(x11_platform, wayland_platform))]
|
|
||||||
if let Err(err) = window.window.request_activation_token() {
|
|
||||||
println!("Failed to get activation token: {err}");
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(err) = self.create_window(event_loop, None) {
|
|
||||||
eprintln!("Error creating new window: {err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::ToggleResizeIncrements => window.toggle_resize_increments(),
|
|
||||||
Action::ToggleCursorVisibility => window.toggle_cursor_visibility(),
|
|
||||||
Action::ToggleResizable => window.toggle_resizable(),
|
|
||||||
Action::ToggleDecorations => window.toggle_decorations(),
|
|
||||||
Action::ToggleFullscreen => window.toggle_fullscreen(),
|
|
||||||
Action::ToggleMaximize => window.toggle_maximize(),
|
|
||||||
Action::ToggleImeInput => window.toggle_ime(),
|
|
||||||
Action::Minimize => window.minimize(),
|
|
||||||
Action::NextCursor => window.next_cursor(),
|
|
||||||
Action::NextCustomCursor => window.next_custom_cursor(&self.custom_cursors),
|
|
||||||
Action::CycleCursorGrab => window.cycle_cursor_grab(),
|
|
||||||
Action::DragWindow => window.drag_window(),
|
|
||||||
Action::DragResizeWindow => window.drag_resize_window(),
|
|
||||||
Action::ShowWindowMenu => window.show_menu(),
|
|
||||||
Action::PrintHelp => self.print_help(),
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
Action::CreateNewTab => {
|
|
||||||
let tab_id = window.window.tabbing_identifier();
|
|
||||||
if let Err(err) = self.create_window(event_loop, Some(tab_id)) {
|
|
||||||
eprintln!("Error creating new window: {err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dump_monitors(&self, event_loop: &ActiveEventLoop) {
|
|
||||||
println!("Monitors information");
|
|
||||||
let primary_monitor = event_loop.primary_monitor();
|
|
||||||
for monitor in event_loop.available_monitors() {
|
|
||||||
let intro = if primary_monitor.as_ref() == Some(&monitor) {
|
|
||||||
"Primary monitor"
|
|
||||||
} else {
|
|
||||||
"Monitor"
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(name) = monitor.name() {
|
|
||||||
println!("{intro}: {name}");
|
|
||||||
} else {
|
|
||||||
println!("{intro}: [no name]");
|
|
||||||
}
|
|
||||||
|
|
||||||
let PhysicalSize { width, height } = monitor.size();
|
|
||||||
print!(" Current mode: {width}x{height}");
|
|
||||||
if let Some(m_hz) = monitor.refresh_rate_millihertz() {
|
|
||||||
println!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000);
|
|
||||||
} else {
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
|
|
||||||
let PhysicalPosition { x, y } = monitor.position();
|
|
||||||
println!(" Position: {x},{y}");
|
|
||||||
|
|
||||||
println!(" Scale factor: {}", monitor.scale_factor());
|
|
||||||
|
|
||||||
println!(" Available modes (width x height x bit-depth):");
|
|
||||||
for mode in monitor.video_modes() {
|
|
||||||
let PhysicalSize { width, height } = mode.size();
|
|
||||||
let bits = mode.bit_depth();
|
|
||||||
let m_hz = mode.refresh_rate_millihertz();
|
|
||||||
println!(
|
|
||||||
" {width}x{height}x{bits} @ {}.{} Hz",
|
|
||||||
m_hz / 1000,
|
|
||||||
m_hz % 1000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Process the key binding.
|
|
||||||
fn process_key_binding(key: &str, mods: &ModifiersState) -> Option<Action> {
|
|
||||||
KEY_BINDINGS.iter().find_map(|binding| {
|
|
||||||
binding
|
|
||||||
.is_triggered_by(&key, mods)
|
|
||||||
.then_some(binding.action)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Process mouse binding.
|
|
||||||
fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option<Action> {
|
|
||||||
MOUSE_BINDINGS.iter().find_map(|binding| {
|
|
||||||
binding
|
|
||||||
.is_triggered_by(&button, mods)
|
|
||||||
.then_some(binding.action)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_help(&self) {
|
|
||||||
println!("Keyboard bindings:");
|
|
||||||
for binding in KEY_BINDINGS {
|
|
||||||
println!(
|
|
||||||
"{}{:<10} - {} ({})",
|
|
||||||
modifiers_to_string(binding.mods),
|
|
||||||
binding.trigger,
|
|
||||||
binding.action,
|
|
||||||
binding.action.help(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
println!("Mouse bindings:");
|
|
||||||
for binding in MOUSE_BINDINGS {
|
|
||||||
println!(
|
|
||||||
"{}{:<10} - {} ({})",
|
|
||||||
modifiers_to_string(binding.mods),
|
|
||||||
mouse_button_to_string(binding.trigger),
|
|
||||||
binding.action,
|
|
||||||
binding.action.help(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationHandler<UserEvent> for Application {
|
|
||||||
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
|
|
||||||
println!("User event: {event:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &ActiveEventLoop,
|
|
||||||
window_id: WindowId,
|
|
||||||
event: WindowEvent,
|
|
||||||
) {
|
|
||||||
let window = match self.windows.get_mut(&window_id) {
|
|
||||||
Some(window) => window,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
|
||||||
WindowEvent::Resized(size) => {
|
|
||||||
window.resize(size);
|
|
||||||
}
|
|
||||||
WindowEvent::Focused(focused) => {
|
|
||||||
if focused {
|
|
||||||
println!("Window={window_id:?} fosused");
|
|
||||||
} else {
|
|
||||||
println!("Window={window_id:?} unfosused");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
|
||||||
println!("Window={window_id:?} changed scale to {scale_factor}");
|
|
||||||
}
|
|
||||||
WindowEvent::ThemeChanged(theme) => {
|
|
||||||
println!("Theme changed to {theme:?}");
|
|
||||||
window.set_theme(theme);
|
|
||||||
}
|
|
||||||
WindowEvent::RedrawRequested => {
|
|
||||||
if let Err(err) = window.draw() {
|
|
||||||
eprintln!("Error drawing window: {err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::Occluded(occluded) => {
|
|
||||||
window.set_occluded(occluded);
|
|
||||||
}
|
|
||||||
WindowEvent::CloseRequested => {
|
|
||||||
println!("Closing Window={window_id:?}");
|
|
||||||
self.windows.remove(&window_id);
|
|
||||||
}
|
|
||||||
WindowEvent::ModifiersChanged(modifiers) => {
|
|
||||||
window.modifiers = modifiers.state();
|
|
||||||
println!("Modifiers changed to {:?}", window.modifiers);
|
|
||||||
}
|
|
||||||
WindowEvent::MouseWheel { delta, .. } => match delta {
|
|
||||||
MouseScrollDelta::LineDelta(x, y) => {
|
|
||||||
println!("Mouse wheel Line Delta: ({x},{y})");
|
|
||||||
}
|
|
||||||
MouseScrollDelta::PixelDelta(px) => {
|
|
||||||
println!("Mouse wheel Pixel Delta: ({},{})", px.x, px.y);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
event,
|
|
||||||
is_synthetic: false,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let mods = window.modifiers;
|
|
||||||
|
|
||||||
// Dispatch actions only on press.
|
|
||||||
if event.state.is_pressed() {
|
|
||||||
let action = if let Key::Character(ch) = event.logical_key.as_ref() {
|
|
||||||
Self::process_key_binding(&ch.to_uppercase(), &mods)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(action) = action {
|
|
||||||
self.handle_action(event_loop, window_id, action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::MouseInput { button, state, .. } => {
|
|
||||||
let mods = window.modifiers;
|
|
||||||
if let Some(action) = state
|
|
||||||
.is_pressed()
|
|
||||||
.then(|| Self::process_mouse_binding(button, &mods))
|
|
||||||
.flatten()
|
|
||||||
{
|
|
||||||
self.handle_action(event_loop, window_id, action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::CursorLeft { .. } => {
|
|
||||||
println!("Cursor left Window={window_id:?}");
|
|
||||||
window.cursor_left();
|
|
||||||
}
|
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
|
||||||
println!("Moved cursor to {position:?}");
|
|
||||||
window.cursor_moved(position);
|
|
||||||
}
|
|
||||||
WindowEvent::ActivationTokenDone { token: _token, .. } => {
|
|
||||||
#[cfg(any(x11_platform, wayland_platform))]
|
|
||||||
{
|
|
||||||
startup_notify::set_activation_token_env(_token);
|
|
||||||
if let Err(err) = self.create_window(event_loop, None) {
|
|
||||||
eprintln!("Error creating new window: {err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::Ime(event) => match event {
|
|
||||||
Ime::Enabled => println!("IME enabled for Window={window_id:?}"),
|
|
||||||
Ime::Preedit(text, caret_pos) => {
|
|
||||||
println!("Preedit: {}, with caret at {:?}", text, caret_pos);
|
|
||||||
}
|
|
||||||
Ime::Commit(text) => {
|
|
||||||
println!("Committed: {}", text);
|
|
||||||
}
|
|
||||||
Ime::Disabled => println!("IME disabled for Window={window_id:?}"),
|
|
||||||
},
|
|
||||||
WindowEvent::PinchGesture { delta, .. } => {
|
|
||||||
window.zoom += delta;
|
|
||||||
let zoom = window.zoom;
|
|
||||||
if delta > 0.0 {
|
|
||||||
println!("Zoomed in {delta:.5} (now: {zoom:.5})");
|
|
||||||
} else {
|
|
||||||
println!("Zoomed out {delta:.5} (now: {zoom:.5})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::RotationGesture { delta, .. } => {
|
|
||||||
window.rotated += delta;
|
|
||||||
let rotated = window.rotated;
|
|
||||||
if delta > 0.0 {
|
|
||||||
println!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})");
|
|
||||||
} else {
|
|
||||||
println!("Rotated clockwise {delta:.5} (now: {rotated:.5})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::DoubleTapGesture { .. } => {
|
|
||||||
println!("Smart zoom");
|
|
||||||
}
|
|
||||||
WindowEvent::TouchpadPressure { .. }
|
|
||||||
| WindowEvent::HoveredFileCancelled
|
|
||||||
| WindowEvent::KeyboardInput { .. }
|
|
||||||
| WindowEvent::CursorEntered { .. }
|
|
||||||
| WindowEvent::AxisMotion { .. }
|
|
||||||
| WindowEvent::DroppedFile(_)
|
|
||||||
| WindowEvent::HoveredFile(_)
|
|
||||||
| WindowEvent::Destroyed
|
|
||||||
| WindowEvent::Touch(_)
|
|
||||||
| WindowEvent::Moved(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn device_event(
|
|
||||||
&mut self,
|
|
||||||
_event_loop: &ActiveEventLoop,
|
|
||||||
device_id: DeviceId,
|
|
||||||
event: DeviceEvent,
|
|
||||||
) {
|
|
||||||
println!("Device {device_id:?} event: {event:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
println!("Resumed the event loop");
|
|
||||||
self.dump_monitors(event_loop);
|
|
||||||
|
|
||||||
// Create initial window.
|
|
||||||
self.create_window(event_loop, None)
|
|
||||||
.expect("failed to create initial window");
|
|
||||||
|
|
||||||
self.print_help();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
if self.windows.is_empty() {
|
|
||||||
println!("No windows left, exiting...");
|
|
||||||
event_loop.exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
|
|
||||||
// We must drop the context here.
|
|
||||||
self.context = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// State of the window.
|
|
||||||
struct WindowState {
|
|
||||||
/// IME input.
|
|
||||||
ime: bool,
|
|
||||||
/// Render surface.
|
|
||||||
///
|
|
||||||
/// NOTE: This surface must be dropped before the `Window`.
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
surface: Surface,
|
|
||||||
/// The actual winit Window.
|
|
||||||
window: Window,
|
|
||||||
/// The window theme we're drawing with.
|
|
||||||
theme: Theme,
|
|
||||||
/// Cursor position over the window.
|
|
||||||
cursor_position: Option<PhysicalPosition<f64>>,
|
|
||||||
/// Window modifiers state.
|
|
||||||
modifiers: ModifiersState,
|
|
||||||
/// Occlusion state of the window.
|
|
||||||
occluded: bool,
|
|
||||||
/// Current cursor grab mode.
|
|
||||||
cursor_grab: CursorGrabMode,
|
|
||||||
/// The amount of zoom into window.
|
|
||||||
zoom: f64,
|
|
||||||
/// The amount of rotation of the window.
|
|
||||||
rotated: f32,
|
|
||||||
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
option_as_alt: OptionAsAlt,
|
|
||||||
|
|
||||||
// Cursor states.
|
|
||||||
named_idx: usize,
|
|
||||||
custom_idx: usize,
|
|
||||||
cursor_hidden: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowState {
|
|
||||||
fn new(app: &Application, window: Window) -> Result<Self, Box<dyn Error>> {
|
|
||||||
// SAFETY: the surface is dropped before the `window` which provided it with handle, thus
|
|
||||||
// it doesn't outlive it.
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
let surface = unsafe { Surface::new(app.context.as_ref().unwrap(), &window)? };
|
|
||||||
|
|
||||||
let theme = window.theme().unwrap_or(Theme::Dark);
|
|
||||||
println!("Theme: {theme:?}");
|
|
||||||
let named_idx = 0;
|
|
||||||
window.set_cursor(CURSORS[named_idx]);
|
|
||||||
|
|
||||||
// Allow IME out of the box.
|
|
||||||
let ime = true;
|
|
||||||
window.set_ime_allowed(ime);
|
|
||||||
|
|
||||||
let size = window.inner_size();
|
|
||||||
let mut state = Self {
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
option_as_alt: window.option_as_alt(),
|
|
||||||
custom_idx: app.custom_cursors.len() - 1,
|
|
||||||
cursor_grab: CursorGrabMode::None,
|
|
||||||
named_idx,
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
surface,
|
|
||||||
window,
|
|
||||||
theme,
|
|
||||||
ime,
|
|
||||||
cursor_position: Default::default(),
|
|
||||||
cursor_hidden: Default::default(),
|
|
||||||
modifiers: Default::default(),
|
|
||||||
occluded: Default::default(),
|
|
||||||
rotated: Default::default(),
|
|
||||||
zoom: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
state.resize(size);
|
|
||||||
Ok(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle_ime(&mut self) {
|
|
||||||
self.ime = !self.ime;
|
|
||||||
self.window.set_ime_allowed(self.ime);
|
|
||||||
if let Some(position) = self.ime.then_some(self.cursor_position).flatten() {
|
|
||||||
self.window
|
|
||||||
.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn minimize(&mut self) {
|
|
||||||
self.window.set_minimized(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cursor_moved(&mut self, position: PhysicalPosition<f64>) {
|
|
||||||
self.cursor_position = Some(position);
|
|
||||||
if self.ime {
|
|
||||||
self.window
|
|
||||||
.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cursor_left(&mut self) {
|
|
||||||
self.cursor_position = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggle maximized.
|
|
||||||
fn toggle_maximize(&self) {
|
|
||||||
let maximized = self.window.is_maximized();
|
|
||||||
self.window.set_maximized(!maximized);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggle window decorations.
|
|
||||||
fn toggle_decorations(&self) {
|
|
||||||
let decorated = self.window.is_decorated();
|
|
||||||
self.window.set_decorations(!decorated);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggle window resizable state.
|
|
||||||
fn toggle_resizable(&self) {
|
|
||||||
let resizable = self.window.is_resizable();
|
|
||||||
self.window.set_resizable(!resizable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggle cursor visibility
|
|
||||||
fn toggle_cursor_visibility(&mut self) {
|
|
||||||
self.cursor_hidden = !self.cursor_hidden;
|
|
||||||
self.window.set_cursor_visible(!self.cursor_hidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggle resize increments on a window.
|
|
||||||
fn toggle_resize_increments(&mut self) {
|
|
||||||
let new_increments = match self.window.resize_increments() {
|
|
||||||
Some(_) => None,
|
|
||||||
None => Some(LogicalSize::new(25.0, 25.0)),
|
|
||||||
};
|
|
||||||
println!("Had increments: {}", new_increments.is_none());
|
|
||||||
self.window.set_resize_increments(new_increments);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggle fullscreen.
|
|
||||||
fn toggle_fullscreen(&self) {
|
|
||||||
let fullscreen = if self.window.fullscreen().is_some() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(Fullscreen::Borderless(None))
|
|
||||||
};
|
|
||||||
|
|
||||||
self.window.set_fullscreen(fullscreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cycle through the grab modes ignoring errors.
|
|
||||||
fn cycle_cursor_grab(&mut self) {
|
|
||||||
self.cursor_grab = match self.cursor_grab {
|
|
||||||
CursorGrabMode::None => CursorGrabMode::Confined,
|
|
||||||
CursorGrabMode::Confined => CursorGrabMode::Locked,
|
|
||||||
CursorGrabMode::Locked => CursorGrabMode::None,
|
|
||||||
};
|
|
||||||
println!("Changing cursor grab mode to {:?}", self.cursor_grab);
|
|
||||||
if let Err(err) = self.window.set_cursor_grab(self.cursor_grab) {
|
|
||||||
eprintln!("Error setting cursor grab: {err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
fn cycle_option_as_alt(&mut self) {
|
|
||||||
self.option_as_alt = match self.option_as_alt {
|
|
||||||
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
|
|
||||||
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
|
|
||||||
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
|
|
||||||
OptionAsAlt::Both => OptionAsAlt::None,
|
|
||||||
};
|
|
||||||
println!("Setting option as alt {:?}", self.option_as_alt);
|
|
||||||
self.window.set_option_as_alt(self.option_as_alt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pick the next cursor.
|
|
||||||
fn next_cursor(&mut self) {
|
|
||||||
self.named_idx = (self.named_idx + 1) % CURSORS.len();
|
|
||||||
println!("Setting cursor to \"{:?}\"", CURSORS[self.named_idx]);
|
|
||||||
self.window
|
|
||||||
.set_cursor(Cursor::Icon(CURSORS[self.named_idx]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pick the next custom cursor.
|
|
||||||
fn next_custom_cursor(&mut self, custom_cursors: &[CustomCursor]) {
|
|
||||||
self.custom_idx = (self.custom_idx + 1) % custom_cursors.len();
|
|
||||||
let cursor = Cursor::Custom(custom_cursors[self.custom_idx].clone());
|
|
||||||
self.window.set_cursor(cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resize the window to the new size.
|
|
||||||
fn resize(&mut self, _size: PhysicalSize<u32>) {
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
{
|
|
||||||
let (width, height) =
|
|
||||||
match (NonZeroU32::new(_size.width), NonZeroU32::new(_size.height)) {
|
|
||||||
(Some(width), Some(height)) => (width, height),
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
self.surface
|
|
||||||
.resize(width, height)
|
|
||||||
.expect("failed to resize inner buffer");
|
|
||||||
}
|
|
||||||
self.window.request_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change the theme.
|
|
||||||
fn set_theme(&mut self, theme: Theme) {
|
|
||||||
self.theme = theme;
|
|
||||||
self.window.request_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Show window menu.
|
|
||||||
fn show_menu(&self) {
|
|
||||||
if let Some(position) = self.cursor_position {
|
|
||||||
self.window.show_window_menu(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Drag the window.
|
|
||||||
fn drag_window(&self) {
|
|
||||||
if let Err(err) = self.window.drag_window() {
|
|
||||||
println!("Error starting window drag: {err}");
|
|
||||||
} else {
|
|
||||||
println!("Dragging window Window={:?}", self.window.id());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Drag-resize the window.
|
|
||||||
fn drag_resize_window(&self) {
|
|
||||||
let position = match self.cursor_position {
|
|
||||||
Some(position) => position,
|
|
||||||
None => {
|
|
||||||
println!("Drag-resize requires cursor to be inside the window");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let win_size = self.window.inner_size();
|
|
||||||
let border_size = BORDER_SIZE * self.window.scale_factor();
|
|
||||||
|
|
||||||
let x_direction = if position.x < border_size {
|
|
||||||
ResizeDirection::West
|
|
||||||
} else if position.x > (win_size.width as f64 - border_size) {
|
|
||||||
ResizeDirection::East
|
|
||||||
} else {
|
|
||||||
// Use arbitrary direction instead of None for simplicity.
|
|
||||||
ResizeDirection::SouthEast
|
|
||||||
};
|
|
||||||
|
|
||||||
let y_direction = if position.y < border_size {
|
|
||||||
ResizeDirection::North
|
|
||||||
} else if position.y > (win_size.height as f64 - border_size) {
|
|
||||||
ResizeDirection::South
|
|
||||||
} else {
|
|
||||||
// Use arbitrary direction instead of None for simplicity.
|
|
||||||
ResizeDirection::SouthEast
|
|
||||||
};
|
|
||||||
|
|
||||||
let direction = match (x_direction, y_direction) {
|
|
||||||
(ResizeDirection::West, ResizeDirection::North) => ResizeDirection::NorthWest,
|
|
||||||
(ResizeDirection::West, ResizeDirection::South) => ResizeDirection::SouthWest,
|
|
||||||
(ResizeDirection::West, _) => ResizeDirection::West,
|
|
||||||
(ResizeDirection::East, ResizeDirection::North) => ResizeDirection::NorthEast,
|
|
||||||
(ResizeDirection::East, ResizeDirection::South) => ResizeDirection::SouthEast,
|
|
||||||
(ResizeDirection::East, _) => ResizeDirection::East,
|
|
||||||
(_, ResizeDirection::South) => ResizeDirection::South,
|
|
||||||
(_, ResizeDirection::North) => ResizeDirection::North,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(err) = self.window.drag_resize_window(direction) {
|
|
||||||
println!("Error starting window drag-resize: {err}");
|
|
||||||
} else {
|
|
||||||
println!("Drag-resizing window Window={:?}", self.window.id());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change window occlusion state.
|
|
||||||
fn set_occluded(&mut self, occluded: bool) {
|
|
||||||
self.occluded = occluded;
|
|
||||||
if !occluded {
|
|
||||||
self.window.request_redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw the window contents.
|
|
||||||
#[cfg(not(any(android_platform, ios_platform)))]
|
|
||||||
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
|
|
||||||
if self.occluded {
|
|
||||||
println!("Skipping drawing occluded window={:?}", self.window.id());
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
const WHITE: u32 = 0xFFFFFFFF;
|
|
||||||
const DARK_GRAY: u32 = 0xFF181818;
|
|
||||||
|
|
||||||
let color = match self.theme {
|
|
||||||
Theme::Light => WHITE,
|
|
||||||
Theme::Dark => DARK_GRAY,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buffer = self.surface.buffer_mut()?;
|
|
||||||
buffer.fill(color);
|
|
||||||
self.window.pre_present_notify();
|
|
||||||
buffer.present()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(android_platform, ios_platform))]
|
|
||||||
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
|
|
||||||
println!("Drawing but without rendering...");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Binding<T: Eq> {
|
|
||||||
trigger: T,
|
|
||||||
mods: ModifiersState,
|
|
||||||
action: Action,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Eq> Binding<T> {
|
|
||||||
const fn new(trigger: T, mods: ModifiersState, action: Action) -> Self {
|
|
||||||
Self {
|
|
||||||
trigger,
|
|
||||||
mods,
|
|
||||||
action,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_triggered_by(&self, trigger: &T, mods: &ModifiersState) -> bool {
|
|
||||||
&self.trigger == trigger && &self.mods == mods
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum Action {
|
|
||||||
CloseWindow,
|
|
||||||
ToggleCursorVisibility,
|
|
||||||
CreateNewWindow,
|
|
||||||
ToggleResizeIncrements,
|
|
||||||
ToggleImeInput,
|
|
||||||
ToggleDecorations,
|
|
||||||
ToggleResizable,
|
|
||||||
ToggleFullscreen,
|
|
||||||
ToggleMaximize,
|
|
||||||
Minimize,
|
|
||||||
NextCursor,
|
|
||||||
NextCustomCursor,
|
|
||||||
CycleCursorGrab,
|
|
||||||
PrintHelp,
|
|
||||||
DragWindow,
|
|
||||||
DragResizeWindow,
|
|
||||||
ShowWindowMenu,
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
CycleOptionAsAlt,
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
CreateNewTab,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Action {
|
|
||||||
fn help(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Action::CloseWindow => "Close window",
|
|
||||||
Action::ToggleCursorVisibility => "Hide cursor",
|
|
||||||
Action::CreateNewWindow => "Create new window",
|
|
||||||
Action::ToggleImeInput => "Toggle IME input",
|
|
||||||
Action::ToggleDecorations => "Toggle decorations",
|
|
||||||
Action::ToggleResizable => "Toggle window resizable state",
|
|
||||||
Action::ToggleFullscreen => "Toggle fullscreen",
|
|
||||||
Action::ToggleMaximize => "Maximize",
|
|
||||||
Action::Minimize => "Minimize",
|
|
||||||
Action::ToggleResizeIncrements => "Use resize increments when resizing window",
|
|
||||||
Action::NextCursor => "Advance the cursor to the next value",
|
|
||||||
Action::NextCustomCursor => "Advance custom cursor to the next value",
|
|
||||||
Action::CycleCursorGrab => "Cycle through cursor grab mode",
|
|
||||||
Action::PrintHelp => "Print help",
|
|
||||||
Action::DragWindow => "Start window drag",
|
|
||||||
Action::DragResizeWindow => "Start window drag-resize",
|
|
||||||
Action::ShowWindowMenu => "Show window menu",
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
Action::CycleOptionAsAlt => "Cycle option as alt mode",
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
Action::CreateNewTab => "Create new tab",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Action {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
Debug::fmt(&self, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
|
|
||||||
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
|
|
||||||
let samples = img.into_flat_samples();
|
|
||||||
let (_, w, h) = samples.extents();
|
|
||||||
let (w, h) = (w as u16, h as u16);
|
|
||||||
CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_icon(path: &Path) -> Icon {
|
|
||||||
let (icon_rgba, icon_width, icon_height) = {
|
|
||||||
let image = image::open(path)
|
|
||||||
.expect("Failed to open icon path")
|
|
||||||
.into_rgba8();
|
|
||||||
let (width, height) = image.dimensions();
|
|
||||||
let rgba = image.into_raw();
|
|
||||||
(rgba, width, height)
|
|
||||||
};
|
|
||||||
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modifiers_to_string(mods: ModifiersState) -> String {
|
|
||||||
let mut mods_line = String::new();
|
|
||||||
// Always add + since it's printed as a part of the bindings.
|
|
||||||
for (modifier, desc) in [
|
|
||||||
(ModifiersState::SUPER, "Super+"),
|
|
||||||
(ModifiersState::ALT, "Alt+"),
|
|
||||||
(ModifiersState::CONTROL, "Ctrl+"),
|
|
||||||
(ModifiersState::SHIFT, "Shift+"),
|
|
||||||
] {
|
|
||||||
if !mods.contains(modifier) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
mods_line.push_str(desc);
|
|
||||||
}
|
|
||||||
mods_line
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mouse_button_to_string(button: MouseButton) -> &'static str {
|
|
||||||
match button {
|
|
||||||
MouseButton::Left => "LMB",
|
|
||||||
MouseButton::Right => "RMB",
|
|
||||||
MouseButton::Middle => "MMB",
|
|
||||||
MouseButton::Back => "Back",
|
|
||||||
MouseButton::Forward => "Forward",
|
|
||||||
MouseButton::Other(_) => "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cursor list to cycle through.
|
|
||||||
const CURSORS: &[CursorIcon] = &[
|
|
||||||
CursorIcon::Default,
|
|
||||||
CursorIcon::Crosshair,
|
|
||||||
CursorIcon::Pointer,
|
|
||||||
CursorIcon::Move,
|
|
||||||
CursorIcon::Text,
|
|
||||||
CursorIcon::Wait,
|
|
||||||
CursorIcon::Help,
|
|
||||||
CursorIcon::Progress,
|
|
||||||
CursorIcon::NotAllowed,
|
|
||||||
CursorIcon::ContextMenu,
|
|
||||||
CursorIcon::Cell,
|
|
||||||
CursorIcon::VerticalText,
|
|
||||||
CursorIcon::Alias,
|
|
||||||
CursorIcon::Copy,
|
|
||||||
CursorIcon::NoDrop,
|
|
||||||
CursorIcon::Grab,
|
|
||||||
CursorIcon::Grabbing,
|
|
||||||
CursorIcon::AllScroll,
|
|
||||||
CursorIcon::ZoomIn,
|
|
||||||
CursorIcon::ZoomOut,
|
|
||||||
CursorIcon::EResize,
|
|
||||||
CursorIcon::NResize,
|
|
||||||
CursorIcon::NeResize,
|
|
||||||
CursorIcon::NwResize,
|
|
||||||
CursorIcon::SResize,
|
|
||||||
CursorIcon::SeResize,
|
|
||||||
CursorIcon::SwResize,
|
|
||||||
CursorIcon::WResize,
|
|
||||||
CursorIcon::EwResize,
|
|
||||||
CursorIcon::NsResize,
|
|
||||||
CursorIcon::NeswResize,
|
|
||||||
CursorIcon::NwseResize,
|
|
||||||
CursorIcon::ColResize,
|
|
||||||
CursorIcon::RowResize,
|
|
||||||
];
|
|
||||||
|
|
||||||
const KEY_BINDINGS: &[Binding<&'static str>] = &[
|
|
||||||
Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow),
|
|
||||||
Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp),
|
|
||||||
Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen),
|
|
||||||
Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations),
|
|
||||||
Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput),
|
|
||||||
Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),
|
|
||||||
Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements),
|
|
||||||
Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
|
|
||||||
// M.
|
|
||||||
Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
|
|
||||||
Binding::new("M", ModifiersState::ALT, Action::Minimize),
|
|
||||||
// N.
|
|
||||||
Binding::new("N", ModifiersState::CONTROL, Action::CreateNewWindow),
|
|
||||||
// C.
|
|
||||||
Binding::new("C", ModifiersState::CONTROL, Action::NextCursor),
|
|
||||||
Binding::new("C", ModifiersState::ALT, Action::NextCustomCursor),
|
|
||||||
Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility),
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
|
|
||||||
];
|
|
||||||
|
|
||||||
const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[
|
|
||||||
Binding::new(
|
|
||||||
MouseButton::Left,
|
|
||||||
ModifiersState::ALT,
|
|
||||||
Action::DragResizeWindow,
|
|
||||||
),
|
|
||||||
Binding::new(
|
|
||||||
MouseButton::Left,
|
|
||||||
ModifiersState::CONTROL,
|
|
||||||
Action::DragWindow,
|
|
||||||
),
|
|
||||||
Binding::new(
|
|
||||||
MouseButton::Right,
|
|
||||||
ModifiersState::CONTROL,
|
|
||||||
Action::ShowWindowMenu,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
//! A demonstration of embedding a winit window in an existing X11 application.
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
#[cfg(x11_platform)]
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
use winit::application::ApplicationHandler;
|
|
||||||
use winit::event::WindowEvent;
|
|
||||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
|
||||||
use winit::platform::x11::WindowAttributesExtX11;
|
|
||||||
use winit::window::{Window, WindowId};
|
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
|
||||||
mod fill;
|
|
||||||
|
|
||||||
pub struct XEmbedDemo {
|
|
||||||
parent_window_id: u32,
|
|
||||||
window: Option<Window>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationHandler for XEmbedDemo {
|
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
let window_attributes = Window::default_attributes()
|
|
||||||
.with_title("An embedded window!")
|
|
||||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
|
||||||
.with_embed_parent_window(self.parent_window_id);
|
|
||||||
|
|
||||||
self.window = Some(event_loop.create_window(window_attributes).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &ActiveEventLoop,
|
|
||||||
_window_id: WindowId,
|
|
||||||
event: WindowEvent,
|
|
||||||
) {
|
|
||||||
let window = self.window.as_ref().unwrap();
|
|
||||||
match event {
|
|
||||||
WindowEvent::CloseRequested => event_loop.exit(),
|
|
||||||
WindowEvent::RedrawRequested => {
|
|
||||||
window.pre_present_notify();
|
|
||||||
fill::fill_window(window);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
|
|
||||||
self.window.as_ref().unwrap().request_redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// First argument should be a 32-bit X11 window ID.
|
|
||||||
let parent_window_id = std::env::args()
|
|
||||||
.nth(1)
|
|
||||||
.ok_or("Expected a 32-bit X11 window ID as the first argument.")?
|
|
||||||
.parse::<u32>()?;
|
|
||||||
|
|
||||||
tracing_subscriber::fmt::init();
|
|
||||||
let event_loop = EventLoop::new()?;
|
|
||||||
|
|
||||||
let mut app = XEmbedDemo {
|
|
||||||
parent_window_id,
|
|
||||||
window: None,
|
|
||||||
};
|
|
||||||
event_loop.run_app(&mut app).map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(x11_platform))]
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
println!("This example is only supported on X11 platforms.");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "common-tests"
|
|
||||||
version = "0.1.0"
|
|
||||||
rust-version.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
gui-test.workspace = true
|
|
||||||
macro_rules_attribute = "0.2.0"
|
|
||||||
winit.workspace = true
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
//! Run the test.
|
|
||||||
|
|
||||||
use gui_test::{test, Harness};
|
|
||||||
use macro_rules_attribute::apply;
|
|
||||||
|
|
||||||
use winit::event_loop::EventLoop;
|
|
||||||
|
|
||||||
#[allow(deprecated)]
|
|
||||||
#[apply(test)]
|
|
||||||
fn initialize(harness: &mut Harness) {
|
|
||||||
let mut group = harness.group("sanity");
|
|
||||||
group.harness().with_test("startup/shutdown", || {
|
|
||||||
let evl = EventLoop::new().expect("initialization");
|
|
||||||
evl.run(|_event, elwt| {
|
|
||||||
elwt.exit();
|
|
||||||
})
|
|
||||||
.expect("running");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
gui_test::main! {
|
|
||||||
gui_test::remote::handler()
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "gui-test-runner"
|
|
||||||
version = "0.1.0"
|
|
||||||
rust-version.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
camino = "1.1.6"
|
|
||||||
fastrand = "2.0.1"
|
|
||||||
gui-test.workspace = true
|
|
||||||
serde_json.workspace = true
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
// Copyright 2024 The Winit Contributors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
//! A wrapper around the `Command` type that dumps the command to stderr.
|
|
||||||
//!
|
|
||||||
//! Essentially it's like `set -x` in Bash.
|
|
||||||
|
|
||||||
use std::ffi::{OsStr, OsString};
|
|
||||||
use std::io::{self, prelude::*};
|
|
||||||
use std::process::Child;
|
|
||||||
|
|
||||||
/// Simple `Command` wrapper.
|
|
||||||
pub(super) struct Command {
|
|
||||||
/// Actual inner command.
|
|
||||||
inner: std::process::Command,
|
|
||||||
|
|
||||||
/// Command to run.
|
|
||||||
text: Vec<OsString>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command {
|
|
||||||
/// Create a new `Command`.
|
|
||||||
pub(super) fn new(cmd: impl AsRef<OsStr>) -> Self {
|
|
||||||
let cmd = cmd.as_ref();
|
|
||||||
Self {
|
|
||||||
inner: std::process::Command::new(cmd),
|
|
||||||
text: vec![cmd.to_os_string()],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add an argument to the `Command`.
|
|
||||||
pub(super) fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
|
|
||||||
let arg = arg.as_ref();
|
|
||||||
self.inner.arg(arg);
|
|
||||||
self.text.push(arg.to_os_string());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add multiple arguments to the `Command`.
|
|
||||||
pub(super) fn args<T: AsRef<OsStr>>(&mut self, args: impl IntoIterator<Item = T>) -> &mut Self {
|
|
||||||
for arg in args {
|
|
||||||
let arg = arg.as_ref();
|
|
||||||
self.inner.arg(arg);
|
|
||||||
self.text.push(arg.to_os_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawn the process.
|
|
||||||
pub(super) fn spawn(&mut self) -> io::Result<Child> {
|
|
||||||
dump_text(&self.text);
|
|
||||||
self.inner.spawn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dump `OsString` list to stderr.
|
|
||||||
fn dump_text(text: &[OsString]) {
|
|
||||||
let mut cerr = io::stderr().lock();
|
|
||||||
write!(&mut cerr, "+").unwrap();
|
|
||||||
|
|
||||||
for arg in text {
|
|
||||||
match arg.to_str() {
|
|
||||||
Some(arg) => write!(&mut cerr, " {}", arg).unwrap(),
|
|
||||||
None => write!(&mut cerr, " {:?}", arg).unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(&mut cerr).unwrap();
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
// Copyright 2024 The Winit Contributors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
//! Run the actual Docker command.
|
|
||||||
|
|
||||||
use crate::command::Command;
|
|
||||||
|
|
||||||
use camino::Utf8Path;
|
|
||||||
|
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::io;
|
|
||||||
use std::process::Child;
|
|
||||||
|
|
||||||
/// The Docker command line.
|
|
||||||
pub(super) struct DockerRun {
|
|
||||||
command: Command,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DockerRun {
|
|
||||||
/// Start the command.
|
|
||||||
pub(super) fn new() -> Self {
|
|
||||||
let mut command = Command::new("docker");
|
|
||||||
command.arg("run");
|
|
||||||
|
|
||||||
Self { command }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run with an environment variable.
|
|
||||||
pub(super) fn env(&mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> &mut Self {
|
|
||||||
let env_arg = format!("{}={}", name.as_ref(), value.as_ref());
|
|
||||||
self.command.args(["--env", &env_arg]);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run with a simple `init` process.
|
|
||||||
pub(super) fn init(&mut self) -> &mut Self {
|
|
||||||
self.command.arg("--init");
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the working directory.
|
|
||||||
pub(super) fn workdir(&mut self, dir: impl AsRef<OsStr>) -> &mut Self {
|
|
||||||
self.command.arg("--workdir");
|
|
||||||
self.command.arg(dir);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove the container once it is complete.
|
|
||||||
pub(super) fn rm(&mut self) -> &mut Self {
|
|
||||||
self.command.arg("--rm");
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pass a volume into the container.
|
|
||||||
pub(super) fn volume(
|
|
||||||
&mut self,
|
|
||||||
host: impl AsRef<Utf8Path>,
|
|
||||||
container: impl AsRef<Utf8Path>,
|
|
||||||
) -> &mut Self {
|
|
||||||
let list = format!("{}:{}", host.as_ref(), container.as_ref());
|
|
||||||
self.command.args(["--volume", &list]);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run the container with a command.
|
|
||||||
pub(super) fn run_with_command<T: AsRef<OsStr>>(
|
|
||||||
&mut self,
|
|
||||||
container_name: impl AsRef<str>,
|
|
||||||
container_version: impl AsRef<str>,
|
|
||||||
command: impl IntoIterator<Item = T>,
|
|
||||||
) -> io::Result<Child> {
|
|
||||||
self.command.arg(format!(
|
|
||||||
"{}:{}",
|
|
||||||
container_name.as_ref(),
|
|
||||||
container_version.as_ref()
|
|
||||||
));
|
|
||||||
self.command.args(command);
|
|
||||||
self.command.spawn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
// Copyright 2024 The Winit Contributors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
//! Run tests inside of a Linux docker container.
|
|
||||||
|
|
||||||
use super::command::DockerRun;
|
|
||||||
use crate::stream::StreamReader;
|
|
||||||
|
|
||||||
use gui_test::remote::handler;
|
|
||||||
use gui_test::TestHandler;
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::os::unix::net::UnixListener;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
const UBUNTU_DOCKERFILE: &str = "ghcr.io/rust-windowing/testubuntu";
|
|
||||||
const LATEST: &str = "latest";
|
|
||||||
|
|
||||||
/// Run the provided test in a Linux docker container.
|
|
||||||
pub(crate) fn linux_test(test_name: &str) -> io::Result<()> {
|
|
||||||
// Create a Unix socket to listen for events on.
|
|
||||||
let unix_path = format!("/tmp/gui_test_{}.sock", fastrand::u16(..));
|
|
||||||
let listener = UnixListener::bind(&unix_path)?;
|
|
||||||
|
|
||||||
// Spawn the Docker container.
|
|
||||||
let mut container = {
|
|
||||||
let mut docker = DockerRun::new();
|
|
||||||
|
|
||||||
// Usual options.
|
|
||||||
docker.rm().init();
|
|
||||||
|
|
||||||
// Pass through the socket as a volume.
|
|
||||||
docker.volume(&unix_path, &unix_path);
|
|
||||||
|
|
||||||
// Pass through the winit directory.
|
|
||||||
let winit_directory = Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.ancestors()
|
|
||||||
.find_map(|path| {
|
|
||||||
let cargo_toml = path.join("Cargo.toml");
|
|
||||||
let contents = std::fs::read(cargo_toml).ok()?;
|
|
||||||
|
|
||||||
if std::str::from_utf8(&contents)
|
|
||||||
.ok()?
|
|
||||||
.contains("name = \"winit\"")
|
|
||||||
{
|
|
||||||
Some(path)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
docker.volume(
|
|
||||||
camino::Utf8Path::from_path(winit_directory).unwrap(),
|
|
||||||
"/app/winit/",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set the working dir to this directory.
|
|
||||||
docker.workdir("/app/winit/");
|
|
||||||
|
|
||||||
// Set GUI_TEST_UNIX_STREAM to the socket.
|
|
||||||
docker.env("GUI_TEST_UNIX_STREAM", &unix_path);
|
|
||||||
|
|
||||||
// Set CARGO_TARGET_DIR to a random other directory.
|
|
||||||
docker.env("CARGO_TARGET_DIR", "/tmp/");
|
|
||||||
|
|
||||||
// The command to run the test.
|
|
||||||
let command = ["xvfb-run", "cargo", "run", "-p", test_name];
|
|
||||||
|
|
||||||
// Spawn the test container.
|
|
||||||
docker.run_with_command(UBUNTU_DOCKERFILE, LATEST, command)?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Run the console listener in another thread.
|
|
||||||
let handle = thread::spawn(move || {
|
|
||||||
// Attach to the listener.
|
|
||||||
let (event_reader, _) = listener.accept().unwrap();
|
|
||||||
|
|
||||||
// Read events and output them as we get them.
|
|
||||||
let input = StreamReader::new(event_reader);
|
|
||||||
let mut output = handler();
|
|
||||||
|
|
||||||
for event in input {
|
|
||||||
let event = event?;
|
|
||||||
output.handle_test(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
io::Result::Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for the container to finish.
|
|
||||||
if !container.wait()?.success() {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"docker exited with a failure exit code",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the thread.
|
|
||||||
handle.join().unwrap().unwrap();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright 2024 The Winit Contributors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
//! Dealing with Docker.
|
|
||||||
|
|
||||||
mod command;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
pub(super) mod linux;
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
//! Runner for the `gui-test` system.
|
|
||||||
|
|
||||||
mod command;
|
|
||||||
mod docker;
|
|
||||||
mod stream;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut args = env::args();
|
|
||||||
|
|
||||||
// Get the test crate name.
|
|
||||||
let test_crate = args.nth(1).unwrap();
|
|
||||||
|
|
||||||
// Get the target.
|
|
||||||
let target_tag = args.next().unwrap();
|
|
||||||
|
|
||||||
// Split the target into the target and the tag.
|
|
||||||
let (target, tag) = {
|
|
||||||
let mut split = target_tag.splitn(1, ':');
|
|
||||||
let target = split.next().unwrap();
|
|
||||||
let tag = split.next();
|
|
||||||
(target, tag)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the current target.
|
|
||||||
let current_target = current_target();
|
|
||||||
|
|
||||||
// If we are building for Linux, run the Linux Docker container.
|
|
||||||
// TODO: Architecture differences.
|
|
||||||
if target.contains("linux") {
|
|
||||||
docker::linux::linux_test(&test_crate).unwrap();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For now, we only support building for the current target.
|
|
||||||
assert_eq!(target, current_target);
|
|
||||||
assert!(tag.is_none());
|
|
||||||
|
|
||||||
// Just run the crate.
|
|
||||||
if !Command::new("cargo")
|
|
||||||
.args(["run", "-p", &test_crate])
|
|
||||||
.status()
|
|
||||||
.unwrap()
|
|
||||||
.success()
|
|
||||||
{
|
|
||||||
panic!("test failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current target.
|
|
||||||
fn current_target() -> String {
|
|
||||||
let output = Command::new("rustc")
|
|
||||||
.arg("-vV")
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.output()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Look for the line that starts with "host".
|
|
||||||
let stdout = String::from_utf8(output.stdout).unwrap();
|
|
||||||
for line in stdout.lines() {
|
|
||||||
if let Some(host) = line.strip_prefix("host: ") {
|
|
||||||
return host.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
panic!("failed to find host: line in rustc output")
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
// Copyright 2024 The Winit Contributors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
//! Read test events from a stream.
|
|
||||||
|
|
||||||
use gui_test::{TestEvent, TestEventType};
|
|
||||||
use std::io::{self, Read};
|
|
||||||
|
|
||||||
/// Read events from a stream.
|
|
||||||
pub(super) struct StreamReader<R> {
|
|
||||||
/// The inner reader.
|
|
||||||
reader: Option<R>,
|
|
||||||
|
|
||||||
/// Reused buffer.
|
|
||||||
buffer: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read> StreamReader<R> {
|
|
||||||
/// Create a new stream reader.
|
|
||||||
pub(super) fn new(reader: R) -> Self {
|
|
||||||
Self {
|
|
||||||
reader: Some(reader),
|
|
||||||
buffer: vec![0u8; 1024],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! leap {
|
|
||||||
($self:expr, $e:expr) => {{
|
|
||||||
match ($e) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(err) => {
|
|
||||||
($self).reader = None;
|
|
||||||
return Some(Err(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read> Iterator for StreamReader<R> {
|
|
||||||
type Item = io::Result<TestEvent>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let reader = self.reader.as_mut()?;
|
|
||||||
|
|
||||||
// Read eight bytes from the reader to get payload length.
|
|
||||||
let mut len_buffer = [0u8; 8];
|
|
||||||
leap!(self, reader.read_exact(&mut len_buffer));
|
|
||||||
|
|
||||||
// Parse that, then read the length's worth of bytes.
|
|
||||||
let length = u64::from_be_bytes(len_buffer);
|
|
||||||
self.buffer.resize(length as usize, 0);
|
|
||||||
leap!(self, reader.read_exact(&mut self.buffer));
|
|
||||||
|
|
||||||
// Parse as a test event.
|
|
||||||
let event: TestEvent = leap!(
|
|
||||||
self,
|
|
||||||
serde_json::from_slice(&self.buffer)
|
|
||||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
|
||||||
);
|
|
||||||
|
|
||||||
// If this is complete, stop running.
|
|
||||||
if matches!(event.ty, TestEventType::Complete { .. }) {
|
|
||||||
self.reader = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are okay.
|
|
||||||
Some(Ok(event))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "gui-test"
|
|
||||||
version = "0.1.0"
|
|
||||||
rust-version.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
async-executor = "1.8.0"
|
|
||||||
async-io.workspace = true
|
|
||||||
async-lock = "3.3.0"
|
|
||||||
async-process = "2.1.0"
|
|
||||||
inventory = "0.3.15"
|
|
||||||
owo-colors = "4.0.0"
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
|
||||||
serde_json.workspace = true
|
|
||||||
@@ -1,443 +0,0 @@
|
|||||||
//! A testing framework that can be run remotely.
|
|
||||||
|
|
||||||
pub mod remote;
|
|
||||||
pub mod stream;
|
|
||||||
pub mod user;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::ffi::{OsStr, OsString};
|
|
||||||
use std::mem;
|
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
use std::panic;
|
|
||||||
|
|
||||||
const GUI_TEST_CURRENT_TEST_NAME: &str = "GUI_TEST_CURRENT_TEST_NAME";
|
|
||||||
const GUI_TEST_SUBPROCESS_LIMIT: &str = "GUI_TEST_SUBPROCESS_LIMIT";
|
|
||||||
const DEFAULT_LIMIT: usize = 1;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub use inventory as __inventory;
|
|
||||||
|
|
||||||
/// Replacement for the `main` function.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! main {
|
|
||||||
($handler:expr) => {
|
|
||||||
fn main() {
|
|
||||||
$crate::__entry(|| $handler)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up a test for the test framework.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! test {
|
|
||||||
(
|
|
||||||
$(#[$attr:meta])*
|
|
||||||
fn $name:ident ($hname:ident : $htype:ty) $bl:block
|
|
||||||
) => {
|
|
||||||
const _: () = {
|
|
||||||
$(#[$attr])*
|
|
||||||
fn $name ($hname: $htype) $bl
|
|
||||||
|
|
||||||
$crate::__inventory::submit! {
|
|
||||||
$crate::__TestStart::__new(
|
|
||||||
stringify!($name),
|
|
||||||
$name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test start.
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct __TestStart {
|
|
||||||
/// The name of the test.
|
|
||||||
name: &'static str,
|
|
||||||
|
|
||||||
/// The function to call.
|
|
||||||
func: fn(&mut Harness),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl __TestStart {
|
|
||||||
/// Create a new test start.
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub const fn __new(name: &'static str, func: fn(&mut Harness)) -> Self {
|
|
||||||
Self { name, func }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inventory::collect! {
|
|
||||||
__TestStart
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A harness for running the tests.
|
|
||||||
pub struct Harness {
|
|
||||||
/// Name of the test start.
|
|
||||||
name: String,
|
|
||||||
|
|
||||||
/// The inner test handler.
|
|
||||||
handler: Box<dyn TestHandler + Send + 'static>,
|
|
||||||
|
|
||||||
/// Number of tests that have been run so far.
|
|
||||||
test_count: usize,
|
|
||||||
|
|
||||||
/// Number of tests that have failed so far.
|
|
||||||
test_fails: usize,
|
|
||||||
|
|
||||||
/// Number of tests that have succeeded.
|
|
||||||
test_passed: usize,
|
|
||||||
|
|
||||||
/// Current state of the test harness.
|
|
||||||
state: State,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Harness {
|
|
||||||
/// Create a new test harness.
|
|
||||||
fn new<H: TestHandler + Send + 'static>(name: &str, handler: H) -> Self {
|
|
||||||
Self {
|
|
||||||
name: name.to_string(),
|
|
||||||
handler: Box::new(handler),
|
|
||||||
test_count: 0,
|
|
||||||
test_fails: 0,
|
|
||||||
test_passed: 0,
|
|
||||||
state: State::Default,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Begin a test.
|
|
||||||
pub fn test(&mut self, name: impl Into<String>) -> Testing<'_> {
|
|
||||||
// Make sure we aren't mid test.
|
|
||||||
match mem::replace(&mut self.state, State::Default) {
|
|
||||||
State::InTest { past_groups } => {
|
|
||||||
self.state = State::InTest { past_groups };
|
|
||||||
panic!("tried to start a test while another was underway");
|
|
||||||
}
|
|
||||||
|
|
||||||
State::InGroups(groups) => {
|
|
||||||
self.state = State::InTest {
|
|
||||||
past_groups: Some(groups),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
State::Default => {
|
|
||||||
self.state = State::InTest { past_groups: None };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the "test started" event to the handler.
|
|
||||||
self.send_event(TestEventType::TestStarted { name: name.into() });
|
|
||||||
|
|
||||||
// Return the handle.
|
|
||||||
Testing {
|
|
||||||
harness: Some(self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run a closure as a test.
|
|
||||||
pub fn with_test<T>(&mut self, name: impl Into<String>, f: impl FnOnce() -> T) -> T {
|
|
||||||
let test = self.test(name.into());
|
|
||||||
match panic::catch_unwind(panic::AssertUnwindSafe(f)) {
|
|
||||||
Ok(x) => x,
|
|
||||||
|
|
||||||
Err(err) => {
|
|
||||||
if let Some(panic) = err.downcast_ref::<&'static str>() {
|
|
||||||
test.fail(panic.to_string());
|
|
||||||
} else if let Some(panic) = err.downcast_ref::<String>() {
|
|
||||||
test.fail(panic.clone());
|
|
||||||
} else {
|
|
||||||
test.fail("unintelligible error".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
panic::resume_unwind(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Begin a test group.
|
|
||||||
pub fn group(&mut self, name: impl Into<String>) -> Grouping<'_> {
|
|
||||||
// Make sure we can begin a group.
|
|
||||||
match mem::replace(&mut self.state, State::Default) {
|
|
||||||
State::Default => {
|
|
||||||
self.state = State::InGroups(NonZeroUsize::new(1).unwrap());
|
|
||||||
}
|
|
||||||
State::InGroups(groups) => {
|
|
||||||
self.state = State::InGroups(groups.checked_add(1).unwrap());
|
|
||||||
}
|
|
||||||
State::InTest { past_groups } => {
|
|
||||||
self.state = State::InTest { past_groups };
|
|
||||||
panic!("cannot start group mid-test")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the "group started" event to the handler.
|
|
||||||
self.send_event(TestEventType::GroupStarted { name: name.into() });
|
|
||||||
|
|
||||||
// Return the handle.
|
|
||||||
Grouping { harness: self }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run a closure inside of a group.
|
|
||||||
pub fn with_group<T>(&mut self, name: impl Into<String>, f: impl FnOnce(&mut Self) -> T) -> T {
|
|
||||||
let mut group = self.group(name.into());
|
|
||||||
f(group.harness())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// End an ongoing test.
|
|
||||||
fn end_test(&mut self, reason: TestResult) {
|
|
||||||
self.test_count += 1;
|
|
||||||
match &reason {
|
|
||||||
TestResult::Passed => self.test_passed += 1,
|
|
||||||
TestResult::Failed(..) => self.test_fails += 1,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.send_event(TestEventType::TestEnded { result: reason });
|
|
||||||
|
|
||||||
let count = match mem::replace(&mut self.state, State::Default) {
|
|
||||||
State::InTest { past_groups } => past_groups,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.state = match count {
|
|
||||||
None => State::Default,
|
|
||||||
Some(count) => State::InGroups(count),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// End the current group.
|
|
||||||
fn end_group(&mut self) {
|
|
||||||
self.send_event(TestEventType::GroupEnded);
|
|
||||||
|
|
||||||
let count = match mem::replace(&mut self.state, State::Default) {
|
|
||||||
State::InGroups(groups) => groups,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.state = match NonZeroUsize::new(count.get() - 1) {
|
|
||||||
None => State::Default,
|
|
||||||
Some(groups) => State::InGroups(groups),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a test event of the provided type.
|
|
||||||
fn send_event(&mut self, ty: TestEventType) {
|
|
||||||
let event = TestEvent {
|
|
||||||
runner: self.name.clone(),
|
|
||||||
ty,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.handler.handle_test(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Harness {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.send_event(TestEventType::Complete {
|
|
||||||
total: self.test_count,
|
|
||||||
fail: self.test_fails,
|
|
||||||
pass: self.test_passed,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An in-progress test.
|
|
||||||
pub struct Testing<'a> {
|
|
||||||
harness: Option<&'a mut Harness>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Testing<'_> {
|
|
||||||
/// Skip this test.
|
|
||||||
pub fn skip(mut self) {
|
|
||||||
// Send the "skipped" event.
|
|
||||||
self.harness.take().unwrap().end_test(TestResult::Skipped);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fail this test.
|
|
||||||
fn fail(mut self, panic: String) {
|
|
||||||
self.harness
|
|
||||||
.take()
|
|
||||||
.unwrap()
|
|
||||||
.end_test(TestResult::Failed(panic));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Testing<'_> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Some(harness) = self.harness.take() {
|
|
||||||
let result = if std::thread::panicking() {
|
|
||||||
TestResult::Failed("thread panicked".into())
|
|
||||||
} else {
|
|
||||||
TestResult::Passed
|
|
||||||
};
|
|
||||||
|
|
||||||
harness.end_test(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// We are running a test group.
|
|
||||||
pub struct Grouping<'a> {
|
|
||||||
harness: &'a mut Harness,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Grouping<'_> {
|
|
||||||
/// Get the underlying test harness.
|
|
||||||
pub fn harness(&mut self) -> &mut Harness {
|
|
||||||
&mut self.harness
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Grouping<'_> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.harness.end_group();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Current testing state.
|
|
||||||
enum State {
|
|
||||||
/// We are in the middle of this many groups.
|
|
||||||
InGroups(NonZeroUsize),
|
|
||||||
|
|
||||||
/// We are in the middle of a test.
|
|
||||||
InTest { past_groups: Option<NonZeroUsize> },
|
|
||||||
|
|
||||||
/// We are in the default state.
|
|
||||||
Default,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A handler for incoming test events.
|
|
||||||
pub trait TestHandler {
|
|
||||||
/// Handle a test.
|
|
||||||
fn handle_test(&mut self, event: TestEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: TestHandler + ?Sized> TestHandler for Box<T> {
|
|
||||||
fn handle_test(&mut self, event: TestEvent) {
|
|
||||||
(**self).handle_test(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An event produced by the test harness.
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct TestEvent {
|
|
||||||
/// The name of the runner associated with the event.
|
|
||||||
pub runner: String,
|
|
||||||
|
|
||||||
/// The type of the event.
|
|
||||||
pub ty: TestEventType,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The type of the event.
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub enum TestEventType {
|
|
||||||
/// The tests are complete and the harness can be disconnected.
|
|
||||||
Complete {
|
|
||||||
/// Total number of tests.
|
|
||||||
total: usize,
|
|
||||||
|
|
||||||
/// Total number of passing tests.
|
|
||||||
pass: usize,
|
|
||||||
|
|
||||||
/// Total number of failed tests.
|
|
||||||
fail: usize,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A test has started.
|
|
||||||
TestStarted { name: String },
|
|
||||||
|
|
||||||
/// A test has completed.
|
|
||||||
TestEnded { result: TestResult },
|
|
||||||
|
|
||||||
/// A test group has started.
|
|
||||||
GroupStarted { name: String },
|
|
||||||
|
|
||||||
/// A test group has ended.
|
|
||||||
GroupEnded,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result of a test.
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub enum TestResult {
|
|
||||||
/// The test passed.
|
|
||||||
Passed,
|
|
||||||
|
|
||||||
/// The test failed with the provided error.
|
|
||||||
Failed(String),
|
|
||||||
|
|
||||||
/// The test was skipped.
|
|
||||||
Skipped,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Entry point of the test.
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn __entry<H: TestHandler + Send + 'static>(handler: impl FnOnce() -> H) {
|
|
||||||
// Look for the test name environment variable.
|
|
||||||
if let Some(test_name) = env::var(GUI_TEST_CURRENT_TEST_NAME)
|
|
||||||
.ok()
|
|
||||||
.filter(|test_name| !test_name.is_empty())
|
|
||||||
{
|
|
||||||
// Find the provided test.
|
|
||||||
let test_to_run = inventory::iter::<__TestStart>
|
|
||||||
.into_iter()
|
|
||||||
.find(|test| test.name == test_name)
|
|
||||||
.unwrap_or_else(|| panic!("unable to find test '{test_name}'"));
|
|
||||||
|
|
||||||
// Create a harness.
|
|
||||||
let mut harness = Harness::new(test_to_run.name, handler());
|
|
||||||
|
|
||||||
// Run the test.
|
|
||||||
panic::catch_unwind(panic::AssertUnwindSafe(move || {
|
|
||||||
(test_to_run.func)(&mut harness)
|
|
||||||
}))
|
|
||||||
.ok();
|
|
||||||
} else {
|
|
||||||
// Run a subprocess for every test.
|
|
||||||
let limit = env::var(GUI_TEST_SUBPROCESS_LIMIT)
|
|
||||||
.ok()
|
|
||||||
.and_then(|limit| limit.parse::<usize>().ok())
|
|
||||||
.unwrap_or(DEFAULT_LIMIT);
|
|
||||||
let process_name = env::args_os().next().unwrap();
|
|
||||||
|
|
||||||
let sema = async_lock::Semaphore::new(limit);
|
|
||||||
let ex = async_executor::Executor::new();
|
|
||||||
|
|
||||||
async_io::block_on(ex.run(async {
|
|
||||||
let mut tasks = vec![];
|
|
||||||
|
|
||||||
// Set up an environment variable for this.
|
|
||||||
for test in inventory::iter::<__TestStart> {
|
|
||||||
// Acquire a guard.
|
|
||||||
let guard = sema.acquire().await;
|
|
||||||
|
|
||||||
// Spawn a subprocess.
|
|
||||||
let mut process = async_process::Command::new(&process_name)
|
|
||||||
.envs(env::vars_os().chain(Some({
|
|
||||||
(path(&GUI_TEST_CURRENT_TEST_NAME), path(&test.name))
|
|
||||||
})))
|
|
||||||
.spawn()
|
|
||||||
.expect("failed to spawn child process");
|
|
||||||
|
|
||||||
// Spawn a task to poll that subprocess.
|
|
||||||
let task = ex.spawn(async move {
|
|
||||||
let _guard = guard;
|
|
||||||
process.status().await.unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
tasks.push(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish all of the tasks.
|
|
||||||
for task in tasks {
|
|
||||||
task.await;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path<A: AsRef<OsStr>>(s: &A) -> OsString {
|
|
||||||
s.as_ref().into()
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
//! Create a test handler that can be run remotely.
|
|
||||||
|
|
||||||
use crate::stream::WriteHandler;
|
|
||||||
use crate::user::UserHandler;
|
|
||||||
use crate::TestHandler;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::net::TcpStream;
|
|
||||||
|
|
||||||
/// Create a test handler adjusted for the current environment.
|
|
||||||
pub fn handler() -> Box<dyn TestHandler + Send + 'static> {
|
|
||||||
// If GUI_TEST_UNIX_STREAM is enabled, use that as a Unix stream.
|
|
||||||
#[cfg(unix)]
|
|
||||||
if let Some(stream_path) = env::var_os("GUI_TEST_UNIX_STREAM").filter(|s| !s.is_empty()) {
|
|
||||||
let stream = std::os::unix::net::UnixStream::connect(stream_path)
|
|
||||||
.expect("unable to connect to gui-test handler");
|
|
||||||
return Box::new(WriteHandler::new(stream));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If GUI_TEST_TCP_STREAM is enabled, use that as a TCP stream.
|
|
||||||
if let Some(tcp_ip) = env::var("GUI_TEST_TCP_STREAM")
|
|
||||||
.ok()
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
{
|
|
||||||
let stream = TcpStream::connect(tcp_ip).unwrap();
|
|
||||||
return Box::new(WriteHandler::new(stream));
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default, use the user handler.
|
|
||||||
Box::new(UserHandler::new())
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
//! Write events to an output stream.
|
|
||||||
//!
|
|
||||||
//! The format is as follows:
|
|
||||||
//! - First 8 bytes: big-endian length of payload.
|
|
||||||
//! - Next {len} bytes: JSON payload to deserialize from.
|
|
||||||
|
|
||||||
use crate::{TestEvent, TestHandler};
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
/// A wrapper around a writer that sends data down a stream.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct WriteHandler<W: Write> {
|
|
||||||
/// The inner writer.
|
|
||||||
writer: W,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> WriteHandler<W> {
|
|
||||||
/// Create a new write handler.
|
|
||||||
pub fn new(writer: W) -> Self {
|
|
||||||
Self { writer }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> TestHandler for WriteHandler<W> {
|
|
||||||
fn handle_test(&mut self, event: TestEvent) {
|
|
||||||
let payload = serde_json::to_vec(&event).unwrap();
|
|
||||||
let length = u64::to_be_bytes(payload.len() as u64);
|
|
||||||
|
|
||||||
// Write the payload to the stream.
|
|
||||||
self.writer.write_all(&length).unwrap();
|
|
||||||
self.writer.write_all(&payload).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> Drop for WriteHandler<W> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.writer.flush().ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
//! User-facing reporter.
|
|
||||||
|
|
||||||
use crate::{TestEvent, TestEventType, TestHandler, TestResult};
|
|
||||||
use owo_colors::OwoColorize;
|
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::io::{self, prelude::*};
|
|
||||||
|
|
||||||
const TABSIZE: usize = 2;
|
|
||||||
|
|
||||||
/// User-facing reporter.
|
|
||||||
///
|
|
||||||
/// This reporter dumps events to the console in a user-readable format.
|
|
||||||
pub struct UserHandler {
|
|
||||||
/// Current indent.
|
|
||||||
indent: usize,
|
|
||||||
|
|
||||||
/// The test set we're currently displaying.
|
|
||||||
current_start: Option<String>,
|
|
||||||
|
|
||||||
/// Test name we are running, if any.
|
|
||||||
test_name: Option<String>,
|
|
||||||
|
|
||||||
/// Cached events.
|
|
||||||
cache: BTreeMap<String, Vec<TestEventType>>,
|
|
||||||
|
|
||||||
/// Failures we had.
|
|
||||||
failures: Vec<(String, String)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserHandler {
|
|
||||||
/// Create a new handler.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
indent: 0,
|
|
||||||
current_start: None,
|
|
||||||
test_name: None,
|
|
||||||
cache: BTreeMap::new(),
|
|
||||||
failures: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Process the provided events.
|
|
||||||
fn process_events(&mut self, events: impl IntoIterator<Item = TestEvent>) {
|
|
||||||
for event in events {
|
|
||||||
// Tell if this is an end event.
|
|
||||||
let mut ender = matches!(event.ty, TestEventType::Complete { .. });
|
|
||||||
|
|
||||||
// If there is no test name set, run the current one.
|
|
||||||
match self.current_start.as_ref() {
|
|
||||||
None => {
|
|
||||||
let TestEvent { runner, ty } = event;
|
|
||||||
self.current_start = Some(runner);
|
|
||||||
self.dump_events(Some(ty));
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(test_name) => {
|
|
||||||
// If there is a test name set and it's ours, post it immediately.
|
|
||||||
if test_name == &event.runner {
|
|
||||||
self.dump_events(Some(event.ty));
|
|
||||||
} else {
|
|
||||||
// Add it to the back of another one of the events.
|
|
||||||
self.cache
|
|
||||||
.entry(test_name.clone())
|
|
||||||
.or_default()
|
|
||||||
.push(event.ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is the end, dump other events.
|
|
||||||
while ender {
|
|
||||||
ender = false;
|
|
||||||
assert!(self.current_start.take().is_some());
|
|
||||||
|
|
||||||
// Pick one set.
|
|
||||||
if let Some(entry) = self.cache.first_entry() {
|
|
||||||
let (test_name, entries) = entry.remove_entry();
|
|
||||||
self.current_start = Some(test_name);
|
|
||||||
|
|
||||||
// Dump events and look for a conclusion.
|
|
||||||
self.dump_events(entries.into_iter().inspect(|ty| {
|
|
||||||
ender |= matches!(ty, TestEventType::Complete { .. });
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dump the provided events to the console.
|
|
||||||
fn dump_events(&mut self, events: impl IntoIterator<Item = TestEventType>) {
|
|
||||||
let mut stdout = io::stdout().lock();
|
|
||||||
|
|
||||||
for event in events {
|
|
||||||
// Write the indent.
|
|
||||||
for _ in 0..(self.indent * TABSIZE) {
|
|
||||||
stdout.write_all(b" ").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
match event {
|
|
||||||
TestEventType::GroupStarted { name } => {
|
|
||||||
assert!(self.test_name.is_none());
|
|
||||||
|
|
||||||
// Write the group name and bump the indent.
|
|
||||||
writeln!(stdout, "{}", name.yellow().italic()).unwrap();
|
|
||||||
|
|
||||||
// Add to the indent.
|
|
||||||
self.indent += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
TestEventType::GroupEnded => {
|
|
||||||
assert!(self.test_name.is_none());
|
|
||||||
|
|
||||||
// Drop the indent.
|
|
||||||
self.indent = self.indent.checked_sub(1).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
TestEventType::TestStarted { name } => {
|
|
||||||
assert!(self.test_name.is_none());
|
|
||||||
|
|
||||||
// Write the line.
|
|
||||||
write!(stdout, "{} ", name.white().italic()).unwrap();
|
|
||||||
self.test_name = Some(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
TestEventType::TestEnded { result } => {
|
|
||||||
let test_name = self.test_name.take().unwrap();
|
|
||||||
|
|
||||||
// Write the result.
|
|
||||||
match result {
|
|
||||||
TestResult::Passed => {
|
|
||||||
writeln!(stdout, "{}", "ok".green().bold()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
TestResult::Failed(failure) => {
|
|
||||||
self.failures.push((test_name, failure));
|
|
||||||
writeln!(stdout, "{}", "FAIL".red().bold()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
TestResult::Skipped => {
|
|
||||||
writeln!(stdout, "{}", "skipped".yellow().bold()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
// Completion.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestHandler for UserHandler {
|
|
||||||
fn handle_test(&mut self, event: TestEvent) {
|
|
||||||
self.process_events(Some(event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for UserHandler {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
assert!(self.cache.is_empty());
|
|
||||||
|
|
||||||
// Write the final bit to the stdout.
|
|
||||||
let mut stdout = io::stdout().lock();
|
|
||||||
|
|
||||||
if !self.failures.is_empty() {
|
|
||||||
writeln!(stdout, "Test Failures:").ok();
|
|
||||||
|
|
||||||
for (test_name, panic) in &self.failures {
|
|
||||||
writeln!(stdout, " {}", test_name).ok();
|
|
||||||
writeln!(stdout, "-------------").ok();
|
|
||||||
writeln!(stdout, "{}", panic).ok();
|
|
||||||
writeln!(stdout, "-------------").ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "run-wasm"
|
|
||||||
version = "0.1.0"
|
|
||||||
rust-version.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cargo-run-wasm = "0.2.0"
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }");
|
|
||||||
}
|
|
||||||
23
rustfmt.toml
23
rustfmt.toml
@@ -1,3 +1,20 @@
|
|||||||
force_explicit_abi=true
|
comment_width = 100
|
||||||
use_field_init_shorthand=true
|
condense_wildcard_suffixes = true
|
||||||
# merge_imports=true
|
error_on_unformatted = true
|
||||||
|
format_code_in_doc_comments = true
|
||||||
|
format_macro_bodies = true
|
||||||
|
format_macro_matchers = true
|
||||||
|
format_strings = true
|
||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
hex_literal_case = "Lower"
|
||||||
|
imports_granularity = "Module"
|
||||||
|
match_block_trailing_comma = true
|
||||||
|
newline_style = "Unix"
|
||||||
|
normalize_comments = true
|
||||||
|
normalize_doc_attributes = true
|
||||||
|
overflow_delimited_expr = true
|
||||||
|
# Some macros break with this.
|
||||||
|
reorder_impl_items = false
|
||||||
|
use_field_init_shorthand = true
|
||||||
|
use_small_heuristics = "Max"
|
||||||
|
wrap_comments = true
|
||||||
|
|||||||
@@ -1,221 +0,0 @@
|
|||||||
//! End user application handling.
|
|
||||||
|
|
||||||
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
|
|
||||||
use crate::event_loop::ActiveEventLoop;
|
|
||||||
use crate::window::WindowId;
|
|
||||||
|
|
||||||
/// The handler of the application events.
|
|
||||||
pub trait ApplicationHandler<T: 'static = ()> {
|
|
||||||
/// Emitted when new events arrive from the OS to be processed.
|
|
||||||
///
|
|
||||||
/// This is a useful place to put code that should be done before you start processing
|
|
||||||
/// events, such as updating frame timing information for benchmarking or checking the
|
|
||||||
/// [`StartCause`] to see if a timer set by
|
|
||||||
/// [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil] has elapsed.
|
|
||||||
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
|
|
||||||
let _ = (event_loop, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emitted when the application has been resumed.
|
|
||||||
///
|
|
||||||
/// For consistency, all platforms emit a `Resumed` event even if they don't themselves have a
|
|
||||||
/// formal suspend/resume lifecycle. For systems without a formal suspend/resume lifecycle
|
|
||||||
/// the `Resumed` event is always emitted after the [`NewEvents(StartCause::Init)`][StartCause::Init]
|
|
||||||
/// event.
|
|
||||||
///
|
|
||||||
/// # Portability
|
|
||||||
///
|
|
||||||
/// It's recommended that applications should only initialize their graphics context and create
|
|
||||||
/// a window after they have received their first `Resumed` event. Some systems
|
|
||||||
/// (specifically Android) won't allow applications to create a render surface until they are
|
|
||||||
/// resumed.
|
|
||||||
///
|
|
||||||
/// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally
|
|
||||||
/// driven by multiple platform-specific events, and that there may be subtle differences across
|
|
||||||
/// platforms with how these internal events are delivered, it's recommended that applications
|
|
||||||
/// be able to gracefully handle redundant (i.e. back-to-back) [`Suspended`] or `Resumed` events.
|
|
||||||
///
|
|
||||||
/// Also see [`Suspended`] notes.
|
|
||||||
///
|
|
||||||
/// ## Android
|
|
||||||
///
|
|
||||||
/// On Android, the `Resumed` event is sent when a new [`SurfaceView`] has been created. This is
|
|
||||||
/// expected to closely correlate with the [`onResume`] lifecycle event but there may technically
|
|
||||||
/// be a discrepancy.
|
|
||||||
///
|
|
||||||
/// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume()
|
|
||||||
///
|
|
||||||
/// Applications that need to run on Android must wait until they have been `Resumed`
|
|
||||||
/// before they will be able to create a render surface (such as an `EGLSurface`,
|
|
||||||
/// [`VkSurfaceKHR`] or [`wgpu::Surface`]) which depend on having a
|
|
||||||
/// [`SurfaceView`]. Applications must also assume that if they are [`Suspended`], then their
|
|
||||||
/// render surfaces are invalid and should be dropped.
|
|
||||||
///
|
|
||||||
/// Also see [`Suspended`] notes.
|
|
||||||
///
|
|
||||||
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
|
|
||||||
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
|
|
||||||
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
|
|
||||||
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
|
|
||||||
///
|
|
||||||
/// ## iOS
|
|
||||||
///
|
|
||||||
/// On iOS, the `Resumed` event is emitted in response to an [`applicationDidBecomeActive`]
|
|
||||||
/// callback which means the application is "active" (according to the
|
|
||||||
/// [iOS application lifecycle]).
|
|
||||||
///
|
|
||||||
/// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive
|
|
||||||
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
|
|
||||||
///
|
|
||||||
/// ## Web
|
|
||||||
///
|
|
||||||
/// On Web, the `Resumed` event is emitted in response to a [`pageshow`] event
|
|
||||||
/// with the property [`persisted`] being true, which means that the page is being
|
|
||||||
/// restored from the [`bfcache`] (back/forward cache) - an in-memory cache that
|
|
||||||
/// stores a complete snapshot of a page (including the JavaScript heap) as the
|
|
||||||
/// user is navigating away.
|
|
||||||
///
|
|
||||||
/// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event
|
|
||||||
/// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted
|
|
||||||
/// [`bfcache`]: https://web.dev/bfcache/
|
|
||||||
/// [`Suspended`]: Self::suspended
|
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop);
|
|
||||||
|
|
||||||
/// Emitted when an event is sent from [`EventLoopProxy::send_event`].
|
|
||||||
///
|
|
||||||
/// [`EventLoopProxy::send_event`]: crate::event_loop::EventLoopProxy::send_event
|
|
||||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
|
|
||||||
let _ = (event_loop, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emitted when the OS sends an event to a winit window.
|
|
||||||
fn window_event(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &ActiveEventLoop,
|
|
||||||
window_id: WindowId,
|
|
||||||
event: WindowEvent,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Emitted when the OS sends an event to a device.
|
|
||||||
fn device_event(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &ActiveEventLoop,
|
|
||||||
device_id: DeviceId,
|
|
||||||
event: DeviceEvent,
|
|
||||||
) {
|
|
||||||
let _ = (event_loop, device_id, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emitted when the event loop is about to block and wait for new events.
|
|
||||||
///
|
|
||||||
/// Most applications shouldn't need to hook into this event since there is no real relationship
|
|
||||||
/// between how often the event loop needs to wake up and the dispatching of any specific events.
|
|
||||||
///
|
|
||||||
/// High frequency event sources, such as input devices could potentially lead to lots of wake
|
|
||||||
/// 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 [`WindowEvent::RedrawRequested`] events.
|
|
||||||
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
let _ = event_loop;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emitted when the application has been suspended.
|
|
||||||
///
|
|
||||||
/// # Portability
|
|
||||||
///
|
|
||||||
/// Not all platforms support the notion of suspending applications, and there may be no
|
|
||||||
/// technical way to guarantee being able to emit a `Suspended` event if the OS has
|
|
||||||
/// no formal application lifecycle (currently only Android, iOS, and Web do). For this reason,
|
|
||||||
/// Winit does not currently try to emit pseudo `Suspended` events before the application
|
|
||||||
/// quits on platforms without an application lifecycle.
|
|
||||||
///
|
|
||||||
/// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally
|
|
||||||
/// driven by multiple platform-specific events, and that there may be subtle differences across
|
|
||||||
/// platforms with how these internal events are delivered, it's recommended that applications
|
|
||||||
/// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`] events.
|
|
||||||
///
|
|
||||||
/// Also see [`Resumed`] notes.
|
|
||||||
///
|
|
||||||
/// ## Android
|
|
||||||
///
|
|
||||||
/// On Android, the `Suspended` event is only sent when the application's associated
|
|
||||||
/// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`]
|
|
||||||
/// lifecycle event but there may technically be a discrepancy.
|
|
||||||
///
|
|
||||||
/// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause()
|
|
||||||
///
|
|
||||||
/// Applications that need to run on Android should assume their [`SurfaceView`] has been
|
|
||||||
/// destroyed, which indirectly invalidates any existing render surfaces that may have been
|
|
||||||
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
|
|
||||||
///
|
|
||||||
/// After being `Suspended` on Android applications must drop all render surfaces before
|
|
||||||
/// the event callback completes, which may be re-created when the application is next [`Resumed`].
|
|
||||||
///
|
|
||||||
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
|
|
||||||
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
|
|
||||||
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
|
|
||||||
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
|
|
||||||
///
|
|
||||||
/// ## iOS
|
|
||||||
///
|
|
||||||
/// On iOS, the `Suspended` event is currently emitted in response to an
|
|
||||||
/// [`applicationWillResignActive`] callback which means that the application is
|
|
||||||
/// about to transition from the active to inactive state (according to the
|
|
||||||
/// [iOS application lifecycle]).
|
|
||||||
///
|
|
||||||
/// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive
|
|
||||||
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
|
|
||||||
///
|
|
||||||
/// ## Web
|
|
||||||
///
|
|
||||||
/// On Web, the `Suspended` event is emitted in response to a [`pagehide`] event
|
|
||||||
/// with the property [`persisted`] being true, which means that the page is being
|
|
||||||
/// put in the [`bfcache`] (back/forward cache) - an in-memory cache that stores a
|
|
||||||
/// complete snapshot of a page (including the JavaScript heap) as the user is
|
|
||||||
/// navigating away.
|
|
||||||
///
|
|
||||||
/// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
|
|
||||||
/// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted
|
|
||||||
/// [`bfcache`]: https://web.dev/bfcache/
|
|
||||||
/// [`Resumed`]: Self::resumed
|
|
||||||
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
let _ = event_loop;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emitted when the event loop is being shut down.
|
|
||||||
///
|
|
||||||
/// This is irreversible - if this method is called, it is guaranteed that the event loop
|
|
||||||
/// will exist right after.
|
|
||||||
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
let _ = event_loop;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 / Orbital / Wayland / Web / Windows:** Unsupported.
|
|
||||||
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
let _ = event_loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
## Unreleased
|
|
||||||
|
|
||||||
- Deprecate `EventLoop::run` in favor of `EventLoop::run_app`.
|
|
||||||
- Deprecate `EventLoopExtRunOnDemand::run_on_demand` in favor of `EventLoop::run_app_on_demand`.
|
|
||||||
- Deprecate `EventLoopExtPumpEvents::pump_events` in favor of `EventLoopExtPumpEvents::pump_app_events`.
|
|
||||||
- Add `ApplicationHandler<T>` trait which mimics `Event<T>`.
|
|
||||||
- Move `dpi` types to its own crate, and re-export it from the root crate.
|
|
||||||
- Implement `Sync` for `EventLoopProxy<T: Send>`.
|
|
||||||
- **Breaking:** Move `Window::new` to `ActiveEventLoop::create_window` and `EventLoop::create_window` (with the latter being deprecated).
|
|
||||||
- **Breaking:** Rename `EventLoopWindowTarget` to `ActiveEventLoop`.
|
|
||||||
- **Breaking:** Remove `Deref` implementation for `EventLoop` that gave `EventLoopWindowTarget`.
|
|
||||||
- **Breaking**: Remove `WindowBuilder` in favor of `WindowAttributes`.
|
|
||||||
- **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`.
|
|
||||||
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
|
|
||||||
- **Breaking:** Remove `Window::set_cursor_icon`
|
|
||||||
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`
|
|
||||||
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
|
|
||||||
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
|
|
||||||
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s.
|
|
||||||
- Add `{Active,}EventLoop::create_custom_cursor` to load custom cursor image sources.
|
|
||||||
- On macOS, add services menu.
|
|
||||||
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
|
|
||||||
- On Web, fix setting cursor icon overriding cursor visibility.
|
|
||||||
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
|
|
||||||
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
|
|
||||||
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
|
|
||||||
- On Web, add the ability to toggle calling `Event.preventDefault()` on `Window`.
|
|
||||||
- **Breaking:** Remove `WindowAttributes::fullscreen()` and expose as field directly.
|
|
||||||
- **Breaking:** Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold static data.
|
|
||||||
- **Breaking:** No longer export `platform::x11::XNotSupported`.
|
|
||||||
- **Breaking:** Renamed `platform::x11::XWindowType` to `platform::x11::WindowType`.
|
|
||||||
- Add the `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases.
|
|
||||||
- **Breaking:** Rename `TouchpadMagnify` to `PinchGesture`, `SmartMagnify` to `DoubleTapGesture` and `TouchpadRotate` to `RotationGesture` to represent the action rather than the intent.
|
|
||||||
- on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`.
|
|
||||||
- on Windows: add `with_system_backdrop`, `with_border_color`, `with_title_background_color`, `with_title_text_color` and `with_corner_preference`
|
|
||||||
- On Windows, Remove `WS_CAPTION`, `WS_BORDER` and `WS_EX_WINDOWEDGE` styles for child windows without decorations.
|
|
||||||
- **Breaking:** Removed `EventLoopError::AlreadyRunning`, which can't happen as it is already prevented by the type system.
|
|
||||||
- Added `EventLoop::builder`, which is intended to replace the (now deprecated) `EventLoopBuilder::new`.
|
|
||||||
- **Breaking:** Changed the signature of `EventLoop::with_user_event` to return a builder.
|
|
||||||
- **Breaking:** Removed `EventLoopBuilder::with_user_event`, the functionality is now available in `EventLoop::with_user_event`.
|
|
||||||
- Add `Window::default_attributes` to get default `WindowAttributes`.
|
|
||||||
- `log` has been replaced with `tracing`. The old behavior can be emulated by setting the `log` feature on the `tracing` crate.
|
|
||||||
290
src/cursor.rs
290
src/cursor.rs
@@ -1,290 +0,0 @@
|
|||||||
use core::fmt;
|
|
||||||
use std::hash::Hasher;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::{error::Error, hash::Hash};
|
|
||||||
|
|
||||||
use cursor_icon::CursorIcon;
|
|
||||||
|
|
||||||
use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource};
|
|
||||||
|
|
||||||
/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
|
|
||||||
pub const MAX_CURSOR_SIZE: u16 = 2048;
|
|
||||||
|
|
||||||
const PIXEL_SIZE: usize = 4;
|
|
||||||
|
|
||||||
/// See [`Window::set_cursor()`][crate::window::Window::set_cursor] for more details.
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub enum Cursor {
|
|
||||||
Icon(CursorIcon),
|
|
||||||
Custom(CustomCursor),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Cursor {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Icon(CursorIcon::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CursorIcon> for Cursor {
|
|
||||||
fn from(icon: CursorIcon) -> Self {
|
|
||||||
Self::Icon(icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CustomCursor> for Cursor {
|
|
||||||
fn from(custom: CustomCursor) -> Self {
|
|
||||||
Self::Custom(custom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Use a custom image as a cursor (mouse pointer).
|
|
||||||
///
|
|
||||||
/// Is guaranteed to be cheap to clone.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// **Web**: Some browsers have limits on cursor sizes usually at 128x128.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// # use winit::event_loop::ActiveEventLoop;
|
|
||||||
/// # use winit::window::Window;
|
|
||||||
/// # fn scope(event_loop: &ActiveEventLoop, window: &Window) {
|
|
||||||
/// use winit::window::CustomCursor;
|
|
||||||
///
|
|
||||||
/// let w = 10;
|
|
||||||
/// let h = 10;
|
|
||||||
/// let rgba = vec![255; (w * h * 4) as usize];
|
|
||||||
///
|
|
||||||
/// #[cfg(not(target_family = "wasm"))]
|
|
||||||
/// let source = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
|
|
||||||
///
|
|
||||||
/// #[cfg(target_family = "wasm")]
|
|
||||||
/// let source = {
|
|
||||||
/// use winit::platform::web::CustomCursorExtWebSys;
|
|
||||||
/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// let custom_cursor = event_loop.create_custom_cursor(source);
|
|
||||||
///
|
|
||||||
/// window.set_cursor(custom_cursor.clone());
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub struct CustomCursor {
|
|
||||||
/// Platforms should make sure this is cheap to clone.
|
|
||||||
pub(crate) inner: PlatformCustomCursor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomCursor {
|
|
||||||
/// Creates a new cursor from an rgba buffer.
|
|
||||||
///
|
|
||||||
/// The alpha channel is assumed to be **not** premultiplied.
|
|
||||||
pub fn from_rgba(
|
|
||||||
rgba: impl Into<Vec<u8>>,
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
hotspot_x: u16,
|
|
||||||
hotspot_y: u16,
|
|
||||||
) -> Result<CustomCursorSource, BadImage> {
|
|
||||||
let _span = tracing::debug_span!(
|
|
||||||
"winit::Cursor::from_rgba",
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y
|
|
||||||
)
|
|
||||||
.entered();
|
|
||||||
|
|
||||||
Ok(CustomCursorSource {
|
|
||||||
inner: PlatformCustomCursorSource::from_rgba(
|
|
||||||
rgba.into(),
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y,
|
|
||||||
)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Source for [`CustomCursor`].
|
|
||||||
///
|
|
||||||
/// See [`CustomCursor`] for more details.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CustomCursorSource {
|
|
||||||
pub(crate) inner: PlatformCustomCursorSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum BadImage {
|
|
||||||
/// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't
|
|
||||||
/// guarantee that the cursor will work, but should avoid many platform and device specific
|
|
||||||
/// limits.
|
|
||||||
TooLarge { width: u16, height: u16 },
|
|
||||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
|
||||||
/// safely interpreted as 32bpp RGBA pixels.
|
|
||||||
ByteCountNotDivisibleBy4 { byte_count: usize },
|
|
||||||
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
|
|
||||||
/// At least one of your arguments is incorrect.
|
|
||||||
DimensionsVsPixelCount {
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
width_x_height: u64,
|
|
||||||
pixel_count: u64,
|
|
||||||
},
|
|
||||||
/// Produced when the hotspot is outside the image bounds
|
|
||||||
HotspotOutOfBounds {
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
hotspot_x: u16,
|
|
||||||
hotspot_y: u16,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for BadImage {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
BadImage::TooLarge { width, height } => write!(f,
|
|
||||||
"The specified dimensions ({width:?}x{height:?}) are too large. The maximum is {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
|
|
||||||
),
|
|
||||||
BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
|
|
||||||
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
|
|
||||||
),
|
|
||||||
BadImage::DimensionsVsPixelCount {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
width_x_height,
|
|
||||||
pixel_count,
|
|
||||||
} => write!(f,
|
|
||||||
"The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.",
|
|
||||||
),
|
|
||||||
BadImage::HotspotOutOfBounds {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y,
|
|
||||||
} => write!(f,
|
|
||||||
"The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds ({width:?}x{height:?}).",
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for BadImage {}
|
|
||||||
|
|
||||||
/// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with
|
|
||||||
/// images.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl OnlyCursorImageSource {
|
|
||||||
pub(crate) fn from_rgba(
|
|
||||||
rgba: Vec<u8>,
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
hotspot_x: u16,
|
|
||||||
hotspot_y: u16,
|
|
||||||
) -> Result<Self, BadImage> {
|
|
||||||
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
|
|
||||||
|
|
||||||
impl Hash for OnlyCursorImage {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
Arc::as_ptr(&self.0).hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for OnlyCursorImage {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
Arc::ptr_eq(&self.0, &other.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for OnlyCursorImage {}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) struct CursorImage {
|
|
||||||
pub(crate) rgba: Vec<u8>,
|
|
||||||
pub(crate) width: u16,
|
|
||||||
pub(crate) height: u16,
|
|
||||||
pub(crate) hotspot_x: u16,
|
|
||||||
pub(crate) hotspot_y: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CursorImage {
|
|
||||||
pub(crate) fn from_rgba(
|
|
||||||
rgba: Vec<u8>,
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
hotspot_x: u16,
|
|
||||||
hotspot_y: u16,
|
|
||||||
) -> Result<Self, BadImage> {
|
|
||||||
if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
|
|
||||||
return Err(BadImage::TooLarge { width, height });
|
|
||||||
}
|
|
||||||
|
|
||||||
if rgba.len() % PIXEL_SIZE != 0 {
|
|
||||||
return Err(BadImage::ByteCountNotDivisibleBy4 {
|
|
||||||
byte_count: rgba.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
|
|
||||||
let width_x_height = width as u64 * height as u64;
|
|
||||||
if pixel_count != width_x_height {
|
|
||||||
return Err(BadImage::DimensionsVsPixelCount {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
width_x_height,
|
|
||||||
pixel_count,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if hotspot_x >= width || hotspot_y >= height {
|
|
||||||
return Err(BadImage::HotspotOutOfBounds {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(CursorImage {
|
|
||||||
rgba,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
|
||||||
pub(crate) struct NoCustomCursor;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl NoCustomCursor {
|
|
||||||
pub(crate) fn from_rgba(
|
|
||||||
rgba: Vec<u8>,
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
hotspot_x: u16,
|
|
||||||
hotspot_y: u16,
|
|
||||||
) -> Result<Self, BadImage> {
|
|
||||||
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
|
|
||||||
Ok(Self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
139
src/error.rs
139
src/error.rs
@@ -1,139 +0,0 @@
|
|||||||
use std::{error, fmt};
|
|
||||||
|
|
||||||
use crate::platform_impl;
|
|
||||||
|
|
||||||
// TODO: Rename
|
|
||||||
/// An error that may be generated when requesting Winit state
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ExternalError {
|
|
||||||
/// The operation is not supported by the backend.
|
|
||||||
NotSupported(NotSupportedError),
|
|
||||||
/// The operation was ignored.
|
|
||||||
Ignored,
|
|
||||||
/// The OS cannot perform the operation.
|
|
||||||
Os(OsError),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error type for when the requested operation is not supported by the backend.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct NotSupportedError {
|
|
||||||
_marker: (),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error type for when the OS cannot perform the requested operation.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct OsError {
|
|
||||||
line: u32,
|
|
||||||
file: &'static str,
|
|
||||||
error: platform_impl::OsError,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A general error that may occur while running the Winit event loop
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum EventLoopError {
|
|
||||||
/// The operation is not supported by the backend.
|
|
||||||
NotSupported(NotSupportedError),
|
|
||||||
/// The OS cannot perform the operation.
|
|
||||||
Os(OsError),
|
|
||||||
/// The event loop can't be re-created.
|
|
||||||
RecreationAttempt,
|
|
||||||
/// Application has exit with an error status.
|
|
||||||
ExitFailure(i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<OsError> for EventLoopError {
|
|
||||||
fn from(value: OsError) -> Self {
|
|
||||||
Self::Os(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NotSupportedError {
|
|
||||||
#[inline]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) fn new() -> NotSupportedError {
|
|
||||||
NotSupportedError { _marker: () }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OsError {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError {
|
|
||||||
OsError { line, file, error }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_macros)]
|
|
||||||
macro_rules! os_error {
|
|
||||||
($error:expr) => {{
|
|
||||||
crate::error::OsError::new(line!(), file!(), $error)
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for OsError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
f.pad(&format!(
|
|
||||||
"os error at {}:{}: {}",
|
|
||||||
self.file, self.line, self.error
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ExternalError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
match self {
|
|
||||||
ExternalError::NotSupported(e) => e.fmt(f),
|
|
||||||
ExternalError::Ignored => write!(f, "Operation was ignored"),
|
|
||||||
ExternalError::Os(e) => e.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for NotSupportedError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
f.debug_struct("NotSupportedError").finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for NotSupportedError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
f.pad("the requested operation is not supported by Winit")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for EventLoopError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
match self {
|
|
||||||
EventLoopError::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
|
|
||||||
EventLoopError::NotSupported(e) => e.fmt(f),
|
|
||||||
EventLoopError::Os(e) => e.fmt(f),
|
|
||||||
EventLoopError::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for OsError {}
|
|
||||||
impl error::Error for ExternalError {}
|
|
||||||
impl error::Error for NotSupportedError {}
|
|
||||||
impl error::Error for EventLoopError {}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#![allow(clippy::redundant_clone)]
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
// Eat attributes for testing
|
|
||||||
#[test]
|
|
||||||
fn ensure_fmt_does_not_panic() {
|
|
||||||
let _ = format!(
|
|
||||||
"{:?}, {}",
|
|
||||||
NotSupportedError::new(),
|
|
||||||
NotSupportedError::new().clone()
|
|
||||||
);
|
|
||||||
let _ = format!(
|
|
||||||
"{:?}, {}",
|
|
||||||
ExternalError::NotSupported(NotSupportedError::new()),
|
|
||||||
ExternalError::NotSupported(NotSupportedError::new())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1229
src/event.rs
1229
src/event.rs
File diff suppressed because it is too large
Load Diff
@@ -1,663 +0,0 @@
|
|||||||
//! The [`EventLoop`] struct and assorted supporting types, including
|
|
||||||
//! [`ControlFlow`].
|
|
||||||
//!
|
|
||||||
//! If you want to send custom events to the event loop, use
|
|
||||||
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
|
|
||||||
//! [`send_event`][EventLoopProxy::send_event] method.
|
|
||||||
//!
|
|
||||||
//! See the root-level documentation for information on how to create and use an event loop to
|
|
||||||
//! handle events.
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
#[cfg(any(x11_platform, wayland_platform))]
|
|
||||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
|
||||||
use std::{error, fmt};
|
|
||||||
|
|
||||||
#[cfg(not(web_platform))]
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
#[cfg(web_platform)]
|
|
||||||
use web_time::{Duration, Instant};
|
|
||||||
|
|
||||||
use crate::application::ApplicationHandler;
|
|
||||||
use crate::error::{EventLoopError, OsError};
|
|
||||||
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
|
|
||||||
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
|
|
||||||
|
|
||||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
|
||||||
/// the events loop.
|
|
||||||
///
|
|
||||||
/// An `EventLoop` can be seen more or less as a "context". Calling [`EventLoop::new`]
|
|
||||||
/// initializes everything that will be required to create windows. For example on Linux creating
|
|
||||||
/// an event loop opens a connection to the X or Wayland server.
|
|
||||||
///
|
|
||||||
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
|
|
||||||
///
|
|
||||||
/// Note that this cannot be shared across threads (due to platform-dependant logic
|
|
||||||
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access, the
|
|
||||||
/// [`Window`] created from this _can_ be sent to an other thread, and the
|
|
||||||
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
|
|
||||||
///
|
|
||||||
/// [`Window`]: crate::window::Window
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
pub(crate) event_loop: platform_impl::EventLoop<T>,
|
|
||||||
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Target that associates windows with an [`EventLoop`].
|
|
||||||
///
|
|
||||||
/// This type exists to allow you to create new windows while Winit executes
|
|
||||||
/// your callback.
|
|
||||||
pub struct ActiveEventLoop {
|
|
||||||
pub(crate) p: platform_impl::ActiveEventLoop,
|
|
||||||
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Object that allows building the event loop.
|
|
||||||
///
|
|
||||||
/// This is used to make specifying options that affect the whole application
|
|
||||||
/// easier. But note that constructing multiple event loops is not supported.
|
|
||||||
///
|
|
||||||
/// This can be created using [`EventLoop::new`] or [`EventLoop::with_user_event`].
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct EventLoopBuilder<T: 'static> {
|
|
||||||
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
|
|
||||||
_p: PhantomData<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
|
|
||||||
|
|
||||||
impl EventLoopBuilder<()> {
|
|
||||||
/// Start building a new event loop.
|
|
||||||
#[inline]
|
|
||||||
#[deprecated = "use `EventLoop::builder` instead"]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
EventLoop::builder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopBuilder<T> {
|
|
||||||
/// Builds a new event loop.
|
|
||||||
///
|
|
||||||
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
|
|
||||||
/// and only once per application.***
|
|
||||||
///
|
|
||||||
/// Calling this function will result in display backend initialisation.
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
///
|
|
||||||
/// Attempting to create the event loop off the main thread will panic. This
|
|
||||||
/// restriction isn't strictly necessary on all platforms, but is imposed to
|
|
||||||
/// eliminate any nasty surprises when porting to platforms that require it.
|
|
||||||
/// `EventLoopBuilderExt::any_thread` functions are exposed in the relevant
|
|
||||||
/// [`platform`] module if the target platform supports creating an event
|
|
||||||
/// loop on any thread.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY`
|
|
||||||
/// or `DISPLAY` respectively when building the event loop.
|
|
||||||
/// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling
|
|
||||||
/// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic.
|
|
||||||
///
|
|
||||||
/// [`platform`]: crate::platform
|
|
||||||
#[cfg_attr(
|
|
||||||
android,
|
|
||||||
doc = "[`.with_android_app(app)`]: crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
|
|
||||||
)]
|
|
||||||
#[cfg_attr(
|
|
||||||
not(android),
|
|
||||||
doc = "[`.with_android_app(app)`]: #only-available-on-android"
|
|
||||||
)]
|
|
||||||
#[inline]
|
|
||||||
pub fn build(&mut self) -> Result<EventLoop<T>, EventLoopError> {
|
|
||||||
let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
|
|
||||||
|
|
||||||
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
|
|
||||||
return Err(EventLoopError::RecreationAttempt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certain platforms accept a mutable reference in their API.
|
|
||||||
#[allow(clippy::unnecessary_mut_passed)]
|
|
||||||
Ok(EventLoop {
|
|
||||||
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific)?,
|
|
||||||
_marker: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(web_platform)]
|
|
||||||
pub(crate) fn allow_event_loop_recreation() {
|
|
||||||
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> fmt::Debug for EventLoop<T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.pad("EventLoop { .. }")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for ActiveEventLoop {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.pad("ActiveEventLoop { .. }")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set through [`ActiveEventLoop::set_control_flow()`].
|
|
||||||
///
|
|
||||||
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
|
|
||||||
///
|
|
||||||
/// Defaults to [`Wait`].
|
|
||||||
///
|
|
||||||
/// [`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
|
|
||||||
/// arrives or the given time is reached.
|
|
||||||
///
|
|
||||||
/// Useful for implementing efficient timers. Applications which want to render at the display's
|
|
||||||
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API
|
|
||||||
/// to reduce odds of missed frames.
|
|
||||||
///
|
|
||||||
/// [`Poll`]: Self::Poll
|
|
||||||
WaitUntil(Instant),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ControlFlow {
|
|
||||||
/// 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 wait_duration(timeout: Duration) -> Self {
|
|
||||||
match Instant::now().checked_add(timeout) {
|
|
||||||
Some(instant) => Self::WaitUntil(instant),
|
|
||||||
None => Self::Wait,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventLoop<()> {
|
|
||||||
/// Create the event loop.
|
|
||||||
///
|
|
||||||
/// This is an alias of `EventLoop::builder().build()`.
|
|
||||||
#[inline]
|
|
||||||
pub fn new() -> Result<EventLoop<()>, EventLoopError> {
|
|
||||||
Self::builder().build()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start building a new event loop.
|
|
||||||
///
|
|
||||||
/// This returns an [`EventLoopBuilder`], to allow configuring the event loop before creation.
|
|
||||||
///
|
|
||||||
/// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that.
|
|
||||||
#[inline]
|
|
||||||
pub fn builder() -> EventLoopBuilder<()> {
|
|
||||||
Self::with_user_event()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoop<T> {
|
|
||||||
/// Start building a new event loop, with the given type as the user event
|
|
||||||
/// type.
|
|
||||||
pub fn with_user_event() -> EventLoopBuilder<T> {
|
|
||||||
EventLoopBuilder {
|
|
||||||
platform_specific: Default::default(),
|
|
||||||
_p: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`run_app`].
|
|
||||||
///
|
|
||||||
/// [`run_app`]: Self::run_app
|
|
||||||
#[inline]
|
|
||||||
#[deprecated = "use `EventLoop::run_app` instead"]
|
|
||||||
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
|
|
||||||
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
|
|
||||||
where
|
|
||||||
F: FnMut(Event<T>, &ActiveEventLoop),
|
|
||||||
{
|
|
||||||
let _span = tracing::debug_span!("winit::EventLoop::run").entered();
|
|
||||||
|
|
||||||
self.event_loop.run(event_handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run the application with the event loop on the calling thread.
|
|
||||||
///
|
|
||||||
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **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
|
|
||||||
#[cfg_attr(
|
|
||||||
web_platform,
|
|
||||||
doc = "[`EventLoopExtWebSys::spawn_app()`][crate::platform::web::EventLoopExtWebSys::spawn_app()]"
|
|
||||||
)]
|
|
||||||
#[cfg_attr(not(web_platform), doc = "`EventLoopExtWebSys::spawn()`")]
|
|
||||||
/// [^1] instead of [`run_app()`] to avoid the need
|
|
||||||
/// for the Javascript exception trick, and to 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"`.
|
|
||||||
///
|
|
||||||
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
|
|
||||||
/// [`run_app()`]: Self::run_app()
|
|
||||||
/// [^1]: `EventLoopExtWebSys::spawn_app()` is only available on Web.
|
|
||||||
#[inline]
|
|
||||||
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
|
|
||||||
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
|
|
||||||
self.event_loop
|
|
||||||
.run(|event, event_loop| dispatch_event_for_app(app, event_loop, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
|
|
||||||
/// to the main event loop, possibly from another thread.
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy {
|
|
||||||
event_loop_proxy: self.event_loop.create_proxy(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a persistent reference to the underlying platform display.
|
|
||||||
///
|
|
||||||
/// See the [`OwnedDisplayHandle`] type for more information.
|
|
||||||
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
|
|
||||||
OwnedDisplayHandle {
|
|
||||||
platform: self.event_loop.window_target().p.owned_display_handle(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change if or when [`DeviceEvent`]s are captured.
|
|
||||||
///
|
|
||||||
/// See [`ActiveEventLoop::listen_device_events`] for details.
|
|
||||||
///
|
|
||||||
/// [`DeviceEvent`]: crate::event::DeviceEvent
|
|
||||||
pub fn listen_device_events(&self, allowed: DeviceEvents) {
|
|
||||||
let _span = tracing::debug_span!(
|
|
||||||
"winit::EventLoop::listen_device_events",
|
|
||||||
allowed = ?allowed
|
|
||||||
)
|
|
||||||
.entered();
|
|
||||||
|
|
||||||
self.event_loop
|
|
||||||
.window_target()
|
|
||||||
.p
|
|
||||||
.listen_device_events(allowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the [`ControlFlow`].
|
|
||||||
pub fn set_control_flow(&self, control_flow: ControlFlow) {
|
|
||||||
self.event_loop
|
|
||||||
.window_target()
|
|
||||||
.p
|
|
||||||
.set_control_flow(control_flow)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a window.
|
|
||||||
///
|
|
||||||
/// Creating window without event loop running often leads to improper window creation;
|
|
||||||
/// use [`ActiveEventLoop::create_window`] instead.
|
|
||||||
#[deprecated = "use `ActiveEventLoop::create_window` instead"]
|
|
||||||
#[inline]
|
|
||||||
pub fn create_window(&self, window_attributes: WindowAttributes) -> Result<Window, OsError> {
|
|
||||||
let _span = tracing::debug_span!(
|
|
||||||
"winit::EventLoop::create_window",
|
|
||||||
window_attributes = ?window_attributes
|
|
||||||
)
|
|
||||||
.entered();
|
|
||||||
|
|
||||||
let window =
|
|
||||||
platform_impl::Window::new(&self.event_loop.window_target().p, window_attributes)?;
|
|
||||||
Ok(Window { window })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create custom cursor.
|
|
||||||
pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor {
|
|
||||||
self.event_loop
|
|
||||||
.window_target()
|
|
||||||
.p
|
|
||||||
.create_custom_cursor(custom_cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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.event_loop.window_target())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_05")]
|
|
||||||
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
|
|
||||||
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
|
|
||||||
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
|
|
||||||
rwh_05::HasRawDisplayHandle::raw_display_handle(self.event_loop.window_target())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(x11_platform, wayland_platform))]
|
|
||||||
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_app_events`] API.
|
|
||||||
///
|
|
||||||
/// [`calloop`]: https://crates.io/crates/calloop
|
|
||||||
/// [`mio`]: https://crates.io/crates/mio
|
|
||||||
/// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_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_app_events`] API.
|
|
||||||
///
|
|
||||||
/// [`calloop`]: https://crates.io/crates/calloop
|
|
||||||
/// [`mio`]: https://crates.io/crates/mio
|
|
||||||
/// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.event_loop.as_raw_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveEventLoop {
|
|
||||||
/// Create the window.
|
|
||||||
///
|
|
||||||
/// Possible causes of error include denied permission, incompatible system, and lack of memory.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **Web:** The window is created but not inserted into the web page automatically. Please
|
|
||||||
/// see the web platform module for more information.
|
|
||||||
#[inline]
|
|
||||||
pub fn create_window(&self, window_attributes: WindowAttributes) -> Result<Window, OsError> {
|
|
||||||
let _span = tracing::debug_span!(
|
|
||||||
"winit::ActiveEventLoop::create_window",
|
|
||||||
window_attributes = ?window_attributes
|
|
||||||
)
|
|
||||||
.entered();
|
|
||||||
|
|
||||||
let window = platform_impl::Window::new(&self.p, window_attributes)?;
|
|
||||||
Ok(Window { window })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create custom cursor.
|
|
||||||
pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor {
|
|
||||||
let _span = tracing::debug_span!("winit::ActiveEventLoop::create_custom_cursor",).entered();
|
|
||||||
|
|
||||||
self.p.create_custom_cursor(custom_cursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the list of all the monitors available on the system.
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
|
||||||
let _span = tracing::debug_span!("winit::ActiveEventLoop::available_monitors",).entered();
|
|
||||||
|
|
||||||
#[allow(clippy::useless_conversion)] // false positive on some platforms
|
|
||||||
self.p
|
|
||||||
.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.map(|inner| MonitorHandle { inner })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the primary monitor of the system.
|
|
||||||
///
|
|
||||||
/// Returns `None` if it can't identify any monitor as a primary one.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// **Wayland / Web:** Always returns `None`.
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
|
||||||
let _span = tracing::debug_span!("winit::ActiveEventLoop::primary_monitor",).entered();
|
|
||||||
|
|
||||||
self.p
|
|
||||||
.primary_monitor()
|
|
||||||
.map(|inner| MonitorHandle { inner })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change if or when [`DeviceEvent`]s are captured.
|
|
||||||
///
|
|
||||||
/// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit
|
|
||||||
/// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
|
|
||||||
/// this at runtime to explicitly capture them again.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
|
|
||||||
///
|
|
||||||
/// [`DeviceEvent`]: crate::event::DeviceEvent
|
|
||||||
pub fn listen_device_events(&self, allowed: DeviceEvents) {
|
|
||||||
let _span = tracing::debug_span!(
|
|
||||||
"winit::ActiveEventLoop::listen_device_events",
|
|
||||||
allowed = ?allowed
|
|
||||||
)
|
|
||||||
.entered();
|
|
||||||
|
|
||||||
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) {
|
|
||||||
let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered();
|
|
||||||
|
|
||||||
self.p.exit()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns if the [`EventLoop`] is about to stop.
|
|
||||||
///
|
|
||||||
/// See [`exit()`][Self::exit].
|
|
||||||
pub fn exiting(&self) -> bool {
|
|
||||||
self.p.exiting()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a persistent reference to the underlying platform display.
|
|
||||||
///
|
|
||||||
/// See the [`OwnedDisplayHandle`] type for more information.
|
|
||||||
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
|
|
||||||
OwnedDisplayHandle {
|
|
||||||
platform: self.p.owned_display_handle(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_06")]
|
|
||||||
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
|
|
||||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
|
||||||
let raw = self.p.raw_display_handle_rwh_06()?;
|
|
||||||
// SAFETY: The display will never be deallocated while the event loop is alive.
|
|
||||||
Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_05")]
|
|
||||||
unsafe impl rwh_05::HasRawDisplayHandle for ActiveEventLoop {
|
|
||||||
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
|
|
||||||
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
|
|
||||||
self.p.raw_display_handle_rwh_05()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A proxy for the underlying display handle.
|
|
||||||
///
|
|
||||||
/// The purpose of this type is to provide a cheaply clonable handle to the underlying
|
|
||||||
/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
|
|
||||||
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`]
|
|
||||||
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as
|
|
||||||
/// needed.
|
|
||||||
///
|
|
||||||
/// For all platforms, this is one of the following:
|
|
||||||
///
|
|
||||||
/// - A zero-sized type that is likely optimized out.
|
|
||||||
/// - A reference-counted pointer to the underlying type.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct OwnedDisplayHandle {
|
|
||||||
#[cfg_attr(not(any(feature = "rwh_05", feature = "rwh_06")), allow(dead_code))]
|
|
||||||
platform: platform_impl::OwnedDisplayHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for OwnedDisplayHandle {
|
|
||||||
#[inline]
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_06")]
|
|
||||||
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
|
|
||||||
#[inline]
|
|
||||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
|
||||||
let raw = self.platform.raw_display_handle_rwh_06()?;
|
|
||||||
|
|
||||||
// SAFETY: The underlying display handle should be safe.
|
|
||||||
let handle = unsafe { rwh_06::DisplayHandle::borrow_raw(raw) };
|
|
||||||
|
|
||||||
Ok(handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_05")]
|
|
||||||
unsafe impl rwh_05::HasRawDisplayHandle for OwnedDisplayHandle {
|
|
||||||
#[inline]
|
|
||||||
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
|
|
||||||
self.platform.raw_display_handle_rwh_05()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used to send custom events to [`EventLoop`].
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
|
||||||
event_loop_proxy: platform_impl::EventLoopProxy<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
event_loop_proxy: self.event_loop_proxy.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
|
||||||
/// Send an event to the [`EventLoop`] from which this proxy was created. This emits a
|
|
||||||
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
|
|
||||||
/// function.
|
|
||||||
///
|
|
||||||
/// Returns an `Err` if the associated [`EventLoop`] no longer exists.
|
|
||||||
///
|
|
||||||
/// [`UserEvent(event)`]: Event::UserEvent
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
|
||||||
let _span = tracing::debug_span!("winit::EventLoopProxy::send_event",).entered();
|
|
||||||
|
|
||||||
self.event_loop_proxy.send_event(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.pad("EventLoopProxy { .. }")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that
|
|
||||||
/// no longer exists.
|
|
||||||
///
|
|
||||||
/// Contains the original event given to [`EventLoopProxy::send_event`].
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub struct EventLoopClosed<T>(pub T);
|
|
||||||
|
|
||||||
impl<T> fmt::Display for EventLoopClosed<T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str("Tried to wake up a closed `EventLoop`")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
|
|
||||||
|
|
||||||
/// Control when device events are captured.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
|
||||||
pub enum DeviceEvents {
|
|
||||||
/// Report device events regardless of window focus.
|
|
||||||
Always,
|
|
||||||
/// Only capture device events while the window is focused.
|
|
||||||
#[default]
|
|
||||||
WhenFocused,
|
|
||||||
/// Never capture device events.
|
|
||||||
Never,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A unique identifier of the winit's async request.
|
|
||||||
///
|
|
||||||
/// This could be used to identify the async request once it's done
|
|
||||||
/// and a specific action must be taken.
|
|
||||||
///
|
|
||||||
/// One of the handling scenarios could be to maintain a working list
|
|
||||||
/// containing [`AsyncRequestSerial`] and some closure associated with it.
|
|
||||||
/// Then once event is arriving the working list is being traversed and a job
|
|
||||||
/// executed and removed from the list.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub struct AsyncRequestSerial {
|
|
||||||
serial: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncRequestSerial {
|
|
||||||
// TODO(kchibisov): Remove `cfg` when the clipboard will be added.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) fn get() -> Self {
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shim for various run APIs.
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn dispatch_event_for_app<T: 'static, A: ApplicationHandler<T>>(
|
|
||||||
app: &mut A,
|
|
||||||
event_loop: &ActiveEventLoop,
|
|
||||||
event: Event<T>,
|
|
||||||
) {
|
|
||||||
match event {
|
|
||||||
Event::NewEvents(cause) => app.new_events(event_loop, cause),
|
|
||||||
Event::WindowEvent { window_id, event } => app.window_event(event_loop, window_id, event),
|
|
||||||
Event::DeviceEvent { device_id, event } => app.device_event(event_loop, device_id, event),
|
|
||||||
Event::UserEvent(event) => app.user_event(event_loop, event),
|
|
||||||
Event::Suspended => app.suspended(event_loop),
|
|
||||||
Event::Resumed => app.resumed(event_loop),
|
|
||||||
Event::AboutToWait => app.about_to_wait(event_loop),
|
|
||||||
Event::LoopExiting => app.exiting(event_loop),
|
|
||||||
Event::MemoryWarning => app.memory_warning(event_loop),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
127
src/icon.rs
127
src/icon.rs
@@ -1,127 +0,0 @@
|
|||||||
use crate::platform_impl::PlatformIcon;
|
|
||||||
use std::{error::Error, fmt, io, mem};
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct Pixel {
|
|
||||||
pub(crate) r: u8,
|
|
||||||
pub(crate) g: u8,
|
|
||||||
pub(crate) b: u8,
|
|
||||||
pub(crate) a: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
|
|
||||||
pub enum BadIcon {
|
|
||||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
|
||||||
/// safely interpreted as 32bpp RGBA pixels.
|
|
||||||
ByteCountNotDivisibleBy4 { byte_count: usize },
|
|
||||||
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
|
|
||||||
/// At least one of your arguments is incorrect.
|
|
||||||
DimensionsVsPixelCount {
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
width_x_height: usize,
|
|
||||||
pixel_count: usize,
|
|
||||||
},
|
|
||||||
/// Produced when underlying OS functionality failed to create the icon
|
|
||||||
OsError(io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for BadIcon {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
|
|
||||||
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
|
|
||||||
),
|
|
||||||
BadIcon::DimensionsVsPixelCount {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
width_x_height,
|
|
||||||
pixel_count,
|
|
||||||
} => write!(f,
|
|
||||||
"The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.",
|
|
||||||
),
|
|
||||||
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {e:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for BadIcon {}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub(crate) struct RgbaIcon {
|
|
||||||
pub(crate) rgba: Vec<u8>,
|
|
||||||
pub(crate) width: u32,
|
|
||||||
pub(crate) height: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// For platforms which don't have window icons (e.g. web)
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub(crate) struct NoIcon;
|
|
||||||
|
|
||||||
#[allow(dead_code)] // These are not used on every platform
|
|
||||||
mod constructors {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
impl RgbaIcon {
|
|
||||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
|
||||||
if rgba.len() % PIXEL_SIZE != 0 {
|
|
||||||
return Err(BadIcon::ByteCountNotDivisibleBy4 {
|
|
||||||
byte_count: rgba.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
|
||||||
if pixel_count != (width * height) as usize {
|
|
||||||
Err(BadIcon::DimensionsVsPixelCount {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
width_x_height: (width * height) as usize,
|
|
||||||
pixel_count,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(RgbaIcon {
|
|
||||||
rgba,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NoIcon {
|
|
||||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
|
||||||
// Create the rgba icon anyway to validate the input
|
|
||||||
let _ = RgbaIcon::from_rgba(rgba, width, height)?;
|
|
||||||
Ok(NoIcon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An icon used for the window titlebar, taskbar, etc.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Icon {
|
|
||||||
pub(crate) inner: PlatformIcon,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Icon {
|
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
fmt::Debug::fmt(&self.inner, formatter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Icon {
|
|
||||||
/// Creates an icon from 32bpp RGBA data.
|
|
||||||
///
|
|
||||||
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
|
|
||||||
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
|
|
||||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
|
||||||
let _span = tracing::debug_span!("winit::Icon::from_rgba", width, height).entered();
|
|
||||||
|
|
||||||
Ok(Icon {
|
|
||||||
inner: PlatformIcon::from_rgba(rgba, width, height)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1801
src/keyboard.rs
1801
src/keyboard.rs
File diff suppressed because it is too large
Load Diff
219
src/lib.rs
219
src/lib.rs
@@ -1,219 +0,0 @@
|
|||||||
//! Winit is a cross-platform window creation and event loop management library.
|
|
||||||
//!
|
|
||||||
//! # Building windows
|
|
||||||
//!
|
|
||||||
//! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with
|
|
||||||
//! the [`EventLoop::new()`] function.
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use winit::event_loop::EventLoop;
|
|
||||||
//! let event_loop = EventLoop::new().unwrap();
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Then you create a [`Window`] with [`create_window`].
|
|
||||||
//!
|
|
||||||
//! # Event handling
|
|
||||||
//!
|
|
||||||
//! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can
|
|
||||||
//! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the
|
|
||||||
//! window or a key getting pressed while the window is focused. Devices can generate
|
|
||||||
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
|
|
||||||
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
|
|
||||||
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if desired.
|
|
||||||
//!
|
|
||||||
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
|
|
||||||
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
|
|
||||||
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
|
|
||||||
//!
|
|
||||||
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
|
||||||
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
|
|
||||||
//! most other platforms. However, this model can be re-implemented to an extent with
|
|
||||||
#![cfg_attr(
|
|
||||||
any(
|
|
||||||
windows_platform,
|
|
||||||
macos_platform,
|
|
||||||
android_platform,
|
|
||||||
x11_platform,
|
|
||||||
wayland_platform
|
|
||||||
),
|
|
||||||
doc = "[`EventLoopExtPumpEvents::pump_app_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_app_events()]"
|
|
||||||
)]
|
|
||||||
#![cfg_attr(
|
|
||||||
not(any(
|
|
||||||
windows_platform,
|
|
||||||
macos_platform,
|
|
||||||
android_platform,
|
|
||||||
x11_platform,
|
|
||||||
wayland_platform
|
|
||||||
)),
|
|
||||||
doc = "`EventLoopExtPumpEvents::pump_app_events()`"
|
|
||||||
)]
|
|
||||||
//! [^1]. See that method's documentation for more reasons about why
|
|
||||||
//! it's discouraged beyond compatibility reasons.
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use winit::application::ApplicationHandler;
|
|
||||||
//! use winit::event::WindowEvent;
|
|
||||||
//! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
|
|
||||||
//! use winit::window::{Window, WindowId};
|
|
||||||
//!
|
|
||||||
//! #[derive(Default)]
|
|
||||||
//! struct App {
|
|
||||||
//! window: Option<Window>,
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl ApplicationHandler for App {
|
|
||||||
//! fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
//! self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
|
|
||||||
//! match event {
|
|
||||||
//! WindowEvent::CloseRequested => {
|
|
||||||
//! println!("The close button was pressed; stopping");
|
|
||||||
//! event_loop.exit();
|
|
||||||
//! },
|
|
||||||
//! WindowEvent::RedrawRequested => {
|
|
||||||
//! // Redraw the application.
|
|
||||||
//! //
|
|
||||||
//! // It's preferable for applications that do not render continuously to render in
|
|
||||||
//! // this event rather than in AboutToWait, since rendering in here allows
|
|
||||||
//! // the program to gracefully handle redraws requested by the OS.
|
|
||||||
//!
|
|
||||||
//! // Draw.
|
|
||||||
//!
|
|
||||||
//! // Queue a RedrawRequested event.
|
|
||||||
//! //
|
|
||||||
//! // 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 render here instead.
|
|
||||||
//! self.window.as_ref().unwrap().request_redraw();
|
|
||||||
//! }
|
|
||||||
//! _ => (),
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! let event_loop = EventLoop::new().unwrap();
|
|
||||||
//!
|
|
||||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
|
||||||
//! // dispatched any events. This is ideal for games and similar applications.
|
|
||||||
//! event_loop.set_control_flow(ControlFlow::Poll);
|
|
||||||
//!
|
|
||||||
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
|
|
||||||
//! // This is ideal for non-game applications that only update in response to user
|
|
||||||
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
|
||||||
//! event_loop.set_control_flow(ControlFlow::Wait);
|
|
||||||
//!
|
|
||||||
//! let mut app = App::default();
|
|
||||||
//! event_loop.run_app(&mut app);
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! [`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
|
|
||||||
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the
|
|
||||||
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
|
|
||||||
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
|
||||||
//!
|
|
||||||
//! Note that many platforms will display garbage data in the window's client area if the
|
|
||||||
//! application doesn't render anything to the window by the time the desktop compositor is ready to
|
|
||||||
//! display the window to the user. If you notice this happening, you should create the window with
|
|
||||||
//! [`visible` set to `false`][crate::window::WindowAttributes::with_visible] and explicitly make the
|
|
||||||
//! window visible only once you're ready to render into it.
|
|
||||||
//!
|
|
||||||
//! # UI scaling
|
|
||||||
//!
|
|
||||||
//! UI scaling is important, go read the docs for the [`dpi`] crate for an
|
|
||||||
//! introduction.
|
|
||||||
//!
|
|
||||||
//! All of Winit's functions return physical types, but can take either logical or physical
|
|
||||||
//! coordinates as input, allowing you to use the most convenient coordinate system for your
|
|
||||||
//! particular application.
|
|
||||||
//!
|
|
||||||
//! 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 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()`].
|
|
||||||
//!
|
|
||||||
//! [`ScaleFactorChanged`]: event::WindowEvent::ScaleFactorChanged
|
|
||||||
//! [`window.scale_factor()`]: window::Window::scale_factor
|
|
||||||
//!
|
|
||||||
//! # Cargo Features
|
|
||||||
//!
|
|
||||||
//! Winit provides the following Cargo features:
|
|
||||||
//!
|
|
||||||
//! * `x11` (enabled by default): On Unix platforms, enables the X11 backend.
|
|
||||||
//! * `wayland` (enabled by default): On Unix platforms, enables the Wayland
|
|
||||||
//! backend.
|
|
||||||
//! * `rwh_04`: Implement `raw-window-handle v0.4` traits.
|
|
||||||
//! * `rwh_05`: Implement `raw-window-handle v0.5` traits.
|
|
||||||
//! * `rwh_06`: Implement `raw-window-handle v0.6` traits.
|
|
||||||
//! * `serde`: Enables serialization/deserialization of certain types with
|
|
||||||
//! [Serde](https://crates.io/crates/serde).
|
|
||||||
//! * `mint`: Enables mint (math interoperability standard types) conversions.
|
|
||||||
//!
|
|
||||||
//! See the [`platform`] module for documentation on platform-specific cargo
|
|
||||||
//! features.
|
|
||||||
//!
|
|
||||||
//! [`EventLoop`]: event_loop::EventLoop
|
|
||||||
//! [`EventLoop::new()`]: event_loop::EventLoop::new
|
|
||||||
//! [`EventLoop::run_app()`]: event_loop::EventLoop::run_app
|
|
||||||
//! [`exit()`]: event_loop::ActiveEventLoop::exit
|
|
||||||
//! [`Window`]: window::Window
|
|
||||||
//! [`WindowId`]: window::WindowId
|
|
||||||
//! [`WindowAttributes`]: window::WindowAttributes
|
|
||||||
//! [window_new]: window::Window::new
|
|
||||||
//! [`create_window`]: event_loop::ActiveEventLoop::create_window
|
|
||||||
//! [`Window::id()`]: window::Window::id
|
|
||||||
//! [`WindowEvent`]: event::WindowEvent
|
|
||||||
//! [`DeviceEvent`]: event::DeviceEvent
|
|
||||||
//! [`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_app_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(clippy, deny(warnings))]
|
|
||||||
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc
|
|
||||||
#![cfg_attr(
|
|
||||||
docsrs,
|
|
||||||
feature(doc_auto_cfg, doc_cfg_hide),
|
|
||||||
doc(cfg_hide(doc, docsrs))
|
|
||||||
)]
|
|
||||||
#![allow(clippy::missing_safety_doc)]
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_06")]
|
|
||||||
pub use rwh_06 as raw_window_handle;
|
|
||||||
|
|
||||||
// Re-export DPI types so that users don't have to put it in Cargo.toml.
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use dpi;
|
|
||||||
|
|
||||||
pub mod application;
|
|
||||||
#[cfg(any(doc, doctest, test))]
|
|
||||||
pub mod changelog;
|
|
||||||
#[macro_use]
|
|
||||||
pub mod error;
|
|
||||||
mod cursor;
|
|
||||||
pub mod event;
|
|
||||||
pub mod event_loop;
|
|
||||||
mod icon;
|
|
||||||
pub mod keyboard;
|
|
||||||
pub mod monitor;
|
|
||||||
mod platform_impl;
|
|
||||||
mod utils;
|
|
||||||
pub mod window;
|
|
||||||
|
|
||||||
pub mod platform;
|
|
||||||
173
src/monitor.rs
173
src/monitor.rs
@@ -1,173 +0,0 @@
|
|||||||
//! Types useful for interacting with a user's monitors.
|
|
||||||
//!
|
|
||||||
//! If you want to get basic information about a monitor, you can use the
|
|
||||||
//! [`MonitorHandle`] type. This is retrieved from one of the following
|
|
||||||
//! methods, which return an iterator of [`MonitorHandle`]:
|
|
||||||
//! - [`ActiveEventLoop::available_monitors`][crate::event_loop::ActiveEventLoop::available_monitors].
|
|
||||||
//! - [`Window::available_monitors`][crate::window::Window::available_monitors].
|
|
||||||
use crate::{
|
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
|
||||||
platform_impl,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Deprecated! Use `VideoModeHandle` instead.
|
|
||||||
#[deprecated = "Renamed to `VideoModeHandle`"]
|
|
||||||
pub type VideoMode = VideoModeHandle;
|
|
||||||
|
|
||||||
/// Describes a fullscreen video mode of a monitor.
|
|
||||||
///
|
|
||||||
/// Can be acquired with [`MonitorHandle::video_modes`].
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct VideoModeHandle {
|
|
||||||
pub(crate) video_mode: platform_impl::VideoModeHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for VideoModeHandle {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
self.video_mode.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for VideoModeHandle {
|
|
||||||
fn partial_cmp(&self, other: &VideoModeHandle) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for VideoModeHandle {
|
|
||||||
fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering {
|
|
||||||
self.monitor().cmp(&other.monitor()).then(
|
|
||||||
self.size()
|
|
||||||
.cmp(&other.size())
|
|
||||||
.then(
|
|
||||||
self.refresh_rate_millihertz()
|
|
||||||
.cmp(&other.refresh_rate_millihertz())
|
|
||||||
.then(self.bit_depth().cmp(&other.bit_depth())),
|
|
||||||
)
|
|
||||||
.reverse(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VideoModeHandle {
|
|
||||||
/// Returns the resolution of this video mode.
|
|
||||||
#[inline]
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.video_mode.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the bit depth of this video mode, as in how many bits you have
|
|
||||||
/// available per color. This is generally 24 bits or 32 bits on modern
|
|
||||||
/// systems, depending on whether the alpha channel is counted or not.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **Wayland / Orbital:** Always returns 32.
|
|
||||||
/// - **iOS:** Always returns 32.
|
|
||||||
#[inline]
|
|
||||||
pub fn bit_depth(&self) -> u16 {
|
|
||||||
self.video_mode.bit_depth()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the refresh rate of this video mode in mHz.
|
|
||||||
#[inline]
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
|
||||||
self.video_mode.refresh_rate_millihertz()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the monitor that this video mode is valid for. Each monitor has
|
|
||||||
/// a separate set of valid video modes.
|
|
||||||
#[inline]
|
|
||||||
pub fn monitor(&self) -> MonitorHandle {
|
|
||||||
MonitorHandle {
|
|
||||||
inner: self.video_mode.monitor(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for VideoModeHandle {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}x{} @ {} mHz ({} bpp)",
|
|
||||||
self.size().width,
|
|
||||||
self.size().height,
|
|
||||||
self.refresh_rate_millihertz(),
|
|
||||||
self.bit_depth()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle to a monitor.
|
|
||||||
///
|
|
||||||
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
|
|
||||||
///
|
|
||||||
/// [`Window`]: crate::window::Window
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct MonitorHandle {
|
|
||||||
pub(crate) inner: platform_impl::MonitorHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandle {
|
|
||||||
/// Returns a human-readable name of the monitor.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the monitor doesn't exist anymore.
|
|
||||||
#[inline]
|
|
||||||
pub fn name(&self) -> Option<String> {
|
|
||||||
self.inner.name()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the monitor's resolution.
|
|
||||||
#[inline]
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.inner.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the top-left corner position of the monitor relative to the larger full
|
|
||||||
/// screen area.
|
|
||||||
#[inline]
|
|
||||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
|
||||||
self.inner.position()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The monitor refresh rate used by the system.
|
|
||||||
///
|
|
||||||
/// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor
|
|
||||||
/// the window is on is removed.
|
|
||||||
///
|
|
||||||
/// When using exclusive fullscreen, the refresh rate of the [`VideoModeHandle`] that was
|
|
||||||
/// used to enter fullscreen should be used instead.
|
|
||||||
#[inline]
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
|
||||||
self.inner.refresh_rate_millihertz()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
|
|
||||||
/// pixels and vice versa, use [`Window::scale_factor`].
|
|
||||||
///
|
|
||||||
/// See the [`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.
|
|
||||||
///
|
|
||||||
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
self.inner.scale_factor()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns all fullscreen video modes supported by this monitor.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **Web:** Always returns an empty iterator
|
|
||||||
#[inline]
|
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
|
|
||||||
self.inner
|
|
||||||
.video_modes()
|
|
||||||
.map(|video_mode| VideoModeHandle { video_mode })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,404 +0,0 @@
|
|||||||
//! # iOS / UIKit
|
|
||||||
//!
|
|
||||||
//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
|
|
||||||
//! iOS 9.3.
|
|
||||||
//!
|
|
||||||
//! iOS's main `UIApplicationMain` does some init work that's required by all
|
|
||||||
//! UI-related code (see issue [#1705]). It is best to create your windows
|
|
||||||
//! inside `Event::Resumed`.
|
|
||||||
//!
|
|
||||||
//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
|
|
||||||
//!
|
|
||||||
//! ## Building app
|
|
||||||
//!
|
|
||||||
//! To build ios app you will need rustc built for this targets:
|
|
||||||
//!
|
|
||||||
//! - armv7-apple-ios
|
|
||||||
//! - armv7s-apple-ios
|
|
||||||
//! - i386-apple-ios
|
|
||||||
//! - aarch64-apple-ios
|
|
||||||
//! - x86_64-apple-ios
|
|
||||||
//!
|
|
||||||
//! Then
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! cargo build --target=...
|
|
||||||
//! ```
|
|
||||||
//! The simplest way to integrate your app into xcode environment is to build it
|
|
||||||
//! as a static library. Wrap your main function and export it.
|
|
||||||
//!
|
|
||||||
//! ```rust, ignore
|
|
||||||
//! #[no_mangle]
|
|
||||||
//! pub extern fn start_winit_app() {
|
|
||||||
//! start_inner()
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! fn start_inner() {
|
|
||||||
//! ...
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
|
|
||||||
//!
|
|
||||||
//! ```ignore
|
|
||||||
//! void start_winit_app();
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Use start_winit_app inside your xcode's main function.
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//! ## App lifecycle and events
|
|
||||||
//!
|
|
||||||
//! iOS environment is very different from other platforms and you must be very
|
|
||||||
//! careful with it's events. Familiarize yourself with
|
|
||||||
//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/).
|
|
||||||
//!
|
|
||||||
//! This is how those event are represented in winit:
|
|
||||||
//!
|
|
||||||
//! - applicationDidBecomeActive is Resumed
|
|
||||||
//! - applicationWillResignActive is Suspended
|
|
||||||
//! - applicationWillTerminate is LoopExiting
|
|
||||||
//!
|
|
||||||
//! Keep in mind that after LoopExiting event is received every attempt to draw with
|
|
||||||
//! opengl will result in segfault.
|
|
||||||
//!
|
|
||||||
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
|
|
||||||
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
event_loop::EventLoop,
|
|
||||||
monitor::{MonitorHandle, VideoModeHandle},
|
|
||||||
window::{Window, WindowAttributes},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Additional methods on [`EventLoop`] that are specific to iOS.
|
|
||||||
pub trait EventLoopExtIOS {
|
|
||||||
/// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
|
|
||||||
fn idiom(&self) -> Idiom;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
|
|
||||||
fn idiom(&self) -> Idiom {
|
|
||||||
self.event_loop.idiom()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`Window`] that are specific to iOS.
|
|
||||||
pub trait WindowExtIOS {
|
|
||||||
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
|
|
||||||
///
|
|
||||||
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
|
||||||
/// this to [`MonitorHandle::scale_factor()`].
|
|
||||||
///
|
|
||||||
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
|
||||||
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
|
|
||||||
fn set_scale_factor(&self, scale_factor: f64);
|
|
||||||
|
|
||||||
/// Sets the valid orientations for the [`Window`].
|
|
||||||
///
|
|
||||||
/// The default value is [`ValidOrientations::LandscapeAndPortrait`].
|
|
||||||
///
|
|
||||||
/// This changes the value returned by
|
|
||||||
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc),
|
|
||||||
/// and then calls
|
|
||||||
/// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc).
|
|
||||||
fn set_valid_orientations(&self, valid_orientations: ValidOrientations);
|
|
||||||
|
|
||||||
/// Sets whether the [`Window`] prefers the home indicator hidden.
|
|
||||||
///
|
|
||||||
/// The default is to prefer showing the home indicator.
|
|
||||||
///
|
|
||||||
/// This changes the value returned by
|
|
||||||
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc),
|
|
||||||
/// and then calls
|
|
||||||
/// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc).
|
|
||||||
///
|
|
||||||
/// This only has an effect on iOS 11.0+.
|
|
||||||
fn set_prefers_home_indicator_hidden(&self, hidden: bool);
|
|
||||||
|
|
||||||
/// Sets the screen edges for which the system gestures will take a lower priority than the
|
|
||||||
/// application's touch handling.
|
|
||||||
///
|
|
||||||
/// This changes the value returned by
|
|
||||||
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc),
|
|
||||||
/// and then calls
|
|
||||||
/// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc).
|
|
||||||
///
|
|
||||||
/// This only has an effect on iOS 11.0+.
|
|
||||||
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge);
|
|
||||||
|
|
||||||
/// Sets whether the [`Window`] prefers the status bar hidden.
|
|
||||||
///
|
|
||||||
/// The default is to prefer showing the status bar.
|
|
||||||
///
|
|
||||||
/// 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);
|
|
||||||
|
|
||||||
/// Sets whether the [`Window`] should recognize pinch gestures.
|
|
||||||
///
|
|
||||||
/// The default is to not recognize gestures.
|
|
||||||
fn recognize_pinch_gesture(&self, should_recognize: bool);
|
|
||||||
|
|
||||||
/// Sets whether the [`Window`] should recognize double tap gestures.
|
|
||||||
///
|
|
||||||
/// The default is to not recognize gestures.
|
|
||||||
fn recognize_doubletap_gesture(&self, should_recognize: bool);
|
|
||||||
|
|
||||||
/// Sets whether the [`Window`] should recognize rotation gestures.
|
|
||||||
///
|
|
||||||
/// The default is to not recognize gestures.
|
|
||||||
fn recognize_rotation_gesture(&self, should_recognize: bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowExtIOS for Window {
|
|
||||||
#[inline]
|
|
||||||
fn set_scale_factor(&self, scale_factor: f64) {
|
|
||||||
self.window
|
|
||||||
.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
|
||||||
self.window
|
|
||||||
.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
|
||||||
self.window
|
|
||||||
.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
|
||||||
self.window.maybe_queue_on_main(move |w| {
|
|
||||||
w.set_preferred_screen_edges_deferring_system_gestures(edges)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
|
||||||
self.window
|
|
||||||
.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn recognize_pinch_gesture(&self, should_recognize: bool) {
|
|
||||||
self.window
|
|
||||||
.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn recognize_doubletap_gesture(&self, should_recognize: bool) {
|
|
||||||
self.window
|
|
||||||
.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn recognize_rotation_gesture(&self, should_recognize: bool) {
|
|
||||||
self.window
|
|
||||||
.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`WindowAttributes`] that are specific to iOS.
|
|
||||||
pub trait WindowAttributesExtIOS {
|
|
||||||
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
|
|
||||||
///
|
|
||||||
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
|
||||||
/// this to [`MonitorHandle::scale_factor()`].
|
|
||||||
///
|
|
||||||
/// [`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) -> Self;
|
|
||||||
|
|
||||||
/// Sets the valid orientations for the [`Window`].
|
|
||||||
///
|
|
||||||
/// The default value is [`ValidOrientations::LandscapeAndPortrait`].
|
|
||||||
///
|
|
||||||
/// 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) -> Self;
|
|
||||||
|
|
||||||
/// Sets whether the [`Window`] prefers the home indicator hidden.
|
|
||||||
///
|
|
||||||
/// The default is to prefer showing the home indicator.
|
|
||||||
///
|
|
||||||
/// This sets the initial value returned by
|
|
||||||
/// [`-[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) -> Self;
|
|
||||||
|
|
||||||
/// Sets the screen edges for which the system gestures will take a lower priority than the
|
|
||||||
/// application's touch handling.
|
|
||||||
///
|
|
||||||
/// This sets the initial value returned by
|
|
||||||
/// [`-[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) -> Self;
|
|
||||||
|
|
||||||
/// Sets whether the [`Window`] prefers the status bar hidden.
|
|
||||||
///
|
|
||||||
/// The default is to prefer showing the status bar.
|
|
||||||
///
|
|
||||||
/// 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) -> 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 WindowAttributesExtIOS for WindowAttributes {
|
|
||||||
#[inline]
|
|
||||||
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) -> Self {
|
|
||||||
self.platform_specific.valid_orientations = valid_orientations;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
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) -> Self {
|
|
||||||
self.platform_specific
|
|
||||||
.preferred_screen_edges_deferring_system_gestures = edges;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
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.
|
|
||||||
pub trait MonitorHandleExtIOS {
|
|
||||||
/// Returns a pointer to the [`UIScreen`] that is used by this monitor.
|
|
||||||
///
|
|
||||||
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
|
|
||||||
fn ui_screen(&self) -> *mut c_void;
|
|
||||||
|
|
||||||
/// Returns the preferred [`VideoModeHandle`] for this monitor.
|
|
||||||
///
|
|
||||||
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
|
|
||||||
fn preferred_video_mode(&self) -> VideoModeHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandleExtIOS for MonitorHandle {
|
|
||||||
#[inline]
|
|
||||||
fn ui_screen(&self) -> *mut c_void {
|
|
||||||
// SAFETY: The marker is only used to get the pointer of the screen
|
|
||||||
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() };
|
|
||||||
objc2::rc::Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn preferred_video_mode(&self) -> VideoModeHandle {
|
|
||||||
VideoModeHandle {
|
|
||||||
video_mode: self.inner.preferred_video_mode(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Valid orientations for a particular [`Window`].
|
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub enum ValidOrientations {
|
|
||||||
/// Excludes `PortraitUpsideDown` on iphone
|
|
||||||
#[default]
|
|
||||||
LandscapeAndPortrait,
|
|
||||||
|
|
||||||
Landscape,
|
|
||||||
|
|
||||||
/// Excludes `PortraitUpsideDown` on iphone
|
|
||||||
Portrait,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The device [idiom].
|
|
||||||
///
|
|
||||||
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub enum Idiom {
|
|
||||||
Unspecified,
|
|
||||||
|
|
||||||
/// iPhone and iPod touch.
|
|
||||||
Phone,
|
|
||||||
|
|
||||||
/// iPad.
|
|
||||||
Pad,
|
|
||||||
|
|
||||||
/// tvOS and Apple TV.
|
|
||||||
TV,
|
|
||||||
CarPlay,
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags::bitflags! {
|
|
||||||
/// The [edges] of a screen.
|
|
||||||
///
|
|
||||||
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
|
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct ScreenEdge: u8 {
|
|
||||||
const NONE = 0;
|
|
||||||
const TOP = 1 << 0;
|
|
||||||
const LEFT = 1 << 1;
|
|
||||||
const BOTTOM = 1 << 2;
|
|
||||||
const RIGHT = 1 << 3;
|
|
||||||
const ALL = ScreenEdge::TOP.bits() | ScreenEdge::LEFT.bits()
|
|
||||||
| ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub enum StatusBarStyle {
|
|
||||||
#[default]
|
|
||||||
Default,
|
|
||||||
LightContent,
|
|
||||||
DarkContent,
|
|
||||||
}
|
|
||||||
@@ -1,443 +0,0 @@
|
|||||||
//! # macOS / AppKit
|
|
||||||
//!
|
|
||||||
//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
|
|
||||||
//! itself), and is regularly tested on macOS 10.14.
|
|
||||||
//!
|
|
||||||
//! A lot of functionality expects the application to be ready before you
|
|
||||||
//! start doing anything; this includes creating windows, fetching monitors,
|
|
||||||
//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
|
|
||||||
//!
|
|
||||||
//! If you encounter problems, you should try doing your initialization inside
|
|
||||||
//! `Event::Resumed`.
|
|
||||||
//!
|
|
||||||
//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
|
|
||||||
//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
|
|
||||||
//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
|
|
||||||
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
event_loop::{ActiveEventLoop, EventLoopBuilder},
|
|
||||||
monitor::MonitorHandle,
|
|
||||||
window::{Window, WindowAttributes},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Additional methods on [`Window`] that are specific to MacOS.
|
|
||||||
pub trait WindowExtMacOS {
|
|
||||||
/// Returns whether or not the window is in simple fullscreen mode.
|
|
||||||
fn simple_fullscreen(&self) -> bool;
|
|
||||||
|
|
||||||
/// Toggles a fullscreen mode that doesn't require a new macOS space.
|
|
||||||
/// Returns a boolean indicating whether the transition was successful (this
|
|
||||||
/// won't work if the window was already in the native fullscreen).
|
|
||||||
///
|
|
||||||
/// This is how fullscreen used to work on macOS in versions before Lion.
|
|
||||||
/// And allows the user to have a fullscreen window without using another
|
|
||||||
/// space or taking control over the entire monitor.
|
|
||||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
|
|
||||||
|
|
||||||
/// Returns whether or not the window has shadow.
|
|
||||||
fn has_shadow(&self) -> bool;
|
|
||||||
|
|
||||||
/// Sets whether or not the window has shadow.
|
|
||||||
fn set_has_shadow(&self, has_shadow: bool);
|
|
||||||
|
|
||||||
/// Group windows together by using the same tabbing identifier.
|
|
||||||
///
|
|
||||||
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
|
|
||||||
fn set_tabbing_identifier(&self, identifier: &str);
|
|
||||||
|
|
||||||
/// Returns the window's tabbing identifier.
|
|
||||||
fn tabbing_identifier(&self) -> String;
|
|
||||||
|
|
||||||
/// Select next tab.
|
|
||||||
fn select_next_tab(&self);
|
|
||||||
|
|
||||||
/// Select previous tab.
|
|
||||||
fn select_previous_tab(&self);
|
|
||||||
|
|
||||||
/// Select the tab with the given index.
|
|
||||||
///
|
|
||||||
/// Will no-op when the index is out of bounds.
|
|
||||||
fn select_tab_at_index(&self, index: usize);
|
|
||||||
|
|
||||||
/// Get the number of tabs in the window tab group.
|
|
||||||
fn num_tabs(&self) -> usize;
|
|
||||||
|
|
||||||
/// Get the window's edit state.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// WindowEvent::CloseRequested => {
|
|
||||||
/// if window.is_document_edited() {
|
|
||||||
/// // Show the user a save pop-up or similar
|
|
||||||
/// } else {
|
|
||||||
/// // Close the window
|
|
||||||
/// drop(window);
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
fn is_document_edited(&self) -> bool;
|
|
||||||
|
|
||||||
/// Put the window in a state which indicates a file save is required.
|
|
||||||
fn set_document_edited(&self, edited: bool);
|
|
||||||
|
|
||||||
/// Set option as alt behavior as described in [`OptionAsAlt`].
|
|
||||||
///
|
|
||||||
/// This will ignore diacritical marks and accent characters from
|
|
||||||
/// being processed as received characters. Instead, the input
|
|
||||||
/// device's raw character will be placed in event queues with the
|
|
||||||
/// Alt modifier set.
|
|
||||||
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt);
|
|
||||||
|
|
||||||
/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
|
|
||||||
fn option_as_alt(&self) -> OptionAsAlt;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowExtMacOS for Window {
|
|
||||||
#[inline]
|
|
||||||
fn simple_fullscreen(&self) -> bool {
|
|
||||||
self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
|
||||||
self.window
|
|
||||||
.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn has_shadow(&self) -> bool {
|
|
||||||
self.window.maybe_wait_on_main(|w| w.has_shadow())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_has_shadow(&self, has_shadow: bool) {
|
|
||||||
self.window
|
|
||||||
.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_tabbing_identifier(&self, identifier: &str) {
|
|
||||||
self.window
|
|
||||||
.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn tabbing_identifier(&self) -> String {
|
|
||||||
self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn select_next_tab(&self) {
|
|
||||||
self.window.maybe_queue_on_main(|w| w.select_next_tab())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn select_previous_tab(&self) {
|
|
||||||
self.window.maybe_queue_on_main(|w| w.select_previous_tab())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn select_tab_at_index(&self, index: usize) {
|
|
||||||
self.window
|
|
||||||
.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn num_tabs(&self) -> usize {
|
|
||||||
self.window.maybe_wait_on_main(|w| w.num_tabs())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_document_edited(&self) -> bool {
|
|
||||||
self.window.maybe_wait_on_main(|w| w.is_document_edited())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_document_edited(&self, edited: bool) {
|
|
||||||
self.window
|
|
||||||
.maybe_queue_on_main(move |w| w.set_document_edited(edited))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
|
|
||||||
self.window
|
|
||||||
.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn option_as_alt(&self) -> OptionAsAlt {
|
|
||||||
self.window.maybe_wait_on_main(|w| w.option_as_alt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub enum ActivationPolicy {
|
|
||||||
/// Corresponds to `NSApplicationActivationPolicyRegular`.
|
|
||||||
#[default]
|
|
||||||
Regular,
|
|
||||||
|
|
||||||
/// Corresponds to `NSApplicationActivationPolicyAccessory`.
|
|
||||||
Accessory,
|
|
||||||
|
|
||||||
/// Corresponds to `NSApplicationActivationPolicyProhibited`.
|
|
||||||
Prohibited,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`WindowAttributes`] that are specific to MacOS.
|
|
||||||
///
|
|
||||||
/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowAttributes::with_decorations`] method:
|
|
||||||
/// - `with_titlebar_transparent`
|
|
||||||
/// - `with_title_hidden`
|
|
||||||
/// - `with_titlebar_hidden`
|
|
||||||
/// - `with_titlebar_buttons_hidden`
|
|
||||||
/// - `with_fullsize_content_view`
|
|
||||||
pub trait WindowAttributesExtMacOS {
|
|
||||||
/// 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) -> Self;
|
|
||||||
/// Makes the titlebar transparent and allows the content to appear behind it.
|
|
||||||
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
|
|
||||||
/// Hides the window title.
|
|
||||||
fn with_title_hidden(self, title_hidden: bool) -> Self;
|
|
||||||
/// Hides the window titlebar.
|
|
||||||
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
|
|
||||||
/// Hides the window titlebar buttons.
|
|
||||||
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) -> 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) -> Self;
|
|
||||||
/// Defines the window tabbing identifier.
|
|
||||||
///
|
|
||||||
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
|
|
||||||
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) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowAttributesExtMacOS for WindowAttributes {
|
|
||||||
#[inline]
|
|
||||||
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) -> Self {
|
|
||||||
self.platform_specific.titlebar_transparent = titlebar_transparent;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
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) -> Self {
|
|
||||||
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
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) -> Self {
|
|
||||||
self.platform_specific.fullsize_content_view = fullsize_content_view;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
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) -> Self {
|
|
||||||
self.platform_specific.has_shadow = has_shadow;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
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) -> Self {
|
|
||||||
self.platform_specific
|
|
||||||
.tabbing_identifier
|
|
||||||
.replace(tabbing_identifier.to_string());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
|
|
||||||
self.platform_specific.option_as_alt = option_as_alt;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait EventLoopBuilderExtMacOS {
|
|
||||||
/// Sets the activation policy for the application.
|
|
||||||
///
|
|
||||||
/// It is set to [`ActivationPolicy::Regular`] by default.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// Set the activation policy to "accessory".
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use winit::event_loop::EventLoopBuilder;
|
|
||||||
/// #[cfg(target_os = "macos")]
|
|
||||||
/// use winit::platform::macos::{EventLoopBuilderExtMacOS, ActivationPolicy};
|
|
||||||
///
|
|
||||||
/// let mut builder = EventLoopBuilder::new();
|
|
||||||
/// #[cfg(target_os = "macos")]
|
|
||||||
/// builder.with_activation_policy(ActivationPolicy::Accessory);
|
|
||||||
/// # if false { // We can't test this part
|
|
||||||
/// let event_loop = builder.build();
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self;
|
|
||||||
|
|
||||||
/// Used to control whether a default menubar menu is created.
|
|
||||||
///
|
|
||||||
/// Menu creation is enabled by default.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// Disable creating a default menubar.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use winit::event_loop::EventLoopBuilder;
|
|
||||||
/// #[cfg(target_os = "macos")]
|
|
||||||
/// use winit::platform::macos::EventLoopBuilderExtMacOS;
|
|
||||||
///
|
|
||||||
/// let mut builder = EventLoopBuilder::new();
|
|
||||||
/// #[cfg(target_os = "macos")]
|
|
||||||
/// builder.with_default_menu(false);
|
|
||||||
/// # if false { // We can't test this part
|
|
||||||
/// let event_loop = builder.build();
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
fn with_default_menu(&mut self, enable: bool) -> &mut Self;
|
|
||||||
|
|
||||||
/// Used to prevent the application from automatically activating when launched if
|
|
||||||
/// another application is already active.
|
|
||||||
///
|
|
||||||
/// The default behavior is to ignore other applications and activate when launched.
|
|
||||||
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
|
|
||||||
#[inline]
|
|
||||||
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
|
|
||||||
self.platform_specific.activation_policy = activation_policy;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_default_menu(&mut self, enable: bool) -> &mut Self {
|
|
||||||
self.platform_specific.default_menu = enable;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
|
|
||||||
self.platform_specific.activate_ignoring_other_apps = ignore;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
|
|
||||||
pub trait MonitorHandleExtMacOS {
|
|
||||||
/// Returns the identifier of the monitor for Cocoa.
|
|
||||||
fn native_id(&self) -> u32;
|
|
||||||
/// Returns a pointer to the NSScreen representing this monitor.
|
|
||||||
fn ns_screen(&self) -> Option<*mut c_void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandleExtMacOS for MonitorHandle {
|
|
||||||
#[inline]
|
|
||||||
fn native_id(&self) -> u32 {
|
|
||||||
self.inner.native_identifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ns_screen(&self) -> Option<*mut c_void> {
|
|
||||||
// SAFETY: We only use the marker to get a pointer
|
|
||||||
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() };
|
|
||||||
self.inner
|
|
||||||
.ns_screen(mtm)
|
|
||||||
.map(|s| objc2::rc::Id::as_ptr(&s) as _)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`ActiveEventLoop`] that are specific to macOS.
|
|
||||||
pub trait ActiveEventLoopExtMacOS {
|
|
||||||
/// Hide the entire application. In most applications this is typically triggered with Command-H.
|
|
||||||
fn hide_application(&self);
|
|
||||||
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
|
|
||||||
fn hide_other_applications(&self);
|
|
||||||
/// Set whether the system can automatically organize windows into tabs.
|
|
||||||
///
|
|
||||||
/// <https://developer.apple.com/documentation/appkit/nswindow/1646657-allowsautomaticwindowtabbing>
|
|
||||||
fn set_allows_automatic_window_tabbing(&self, enabled: bool);
|
|
||||||
/// Returns whether the system can automatically organize windows into tabs.
|
|
||||||
fn allows_automatic_window_tabbing(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveEventLoopExtMacOS for ActiveEventLoop {
|
|
||||||
fn hide_application(&self) {
|
|
||||||
self.p.hide_application()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hide_other_applications(&self) {
|
|
||||||
self.p.hide_other_applications()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
|
|
||||||
self.p.set_allows_automatic_window_tabbing(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allows_automatic_window_tabbing(&self) -> bool {
|
|
||||||
self.p.allows_automatic_window_tabbing()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Option as alt behavior.
|
|
||||||
///
|
|
||||||
/// The default is `None`.
|
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum OptionAsAlt {
|
|
||||||
/// The left `Option` key is treated as `Alt`.
|
|
||||||
OnlyLeft,
|
|
||||||
|
|
||||||
/// The right `Option` key is treated as `Alt`.
|
|
||||||
OnlyRight,
|
|
||||||
|
|
||||||
/// Both `Option` keys are treated as `Alt`.
|
|
||||||
Both,
|
|
||||||
|
|
||||||
/// No special handling is applied for `Option` key.
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
//! Contains traits with platform-specific methods in them.
|
|
||||||
//!
|
|
||||||
//! Only the modules corresponding to the platform you're compiling to will be available.
|
|
||||||
|
|
||||||
#[cfg(any(android_platform, docsrs))]
|
|
||||||
pub mod android;
|
|
||||||
#[cfg(any(ios_platform, docsrs))]
|
|
||||||
pub mod ios;
|
|
||||||
#[cfg(any(macos_platform, docsrs))]
|
|
||||||
pub mod macos;
|
|
||||||
#[cfg(any(orbital_platform, docsrs))]
|
|
||||||
pub mod orbital;
|
|
||||||
#[cfg(any(x11_platform, wayland_platform, docsrs))]
|
|
||||||
pub mod startup_notify;
|
|
||||||
#[cfg(any(wayland_platform, docsrs))]
|
|
||||||
pub mod wayland;
|
|
||||||
#[cfg(any(web_platform, docsrs))]
|
|
||||||
pub mod web;
|
|
||||||
#[cfg(any(windows_platform, docsrs))]
|
|
||||||
pub mod windows;
|
|
||||||
#[cfg(any(x11_platform, docsrs))]
|
|
||||||
pub mod x11;
|
|
||||||
|
|
||||||
#[cfg(any(
|
|
||||||
windows_platform,
|
|
||||||
macos_platform,
|
|
||||||
android_platform,
|
|
||||||
x11_platform,
|
|
||||||
wayland_platform,
|
|
||||||
docsrs,
|
|
||||||
))]
|
|
||||||
pub mod run_on_demand;
|
|
||||||
|
|
||||||
#[cfg(any(
|
|
||||||
windows_platform,
|
|
||||||
macos_platform,
|
|
||||||
android_platform,
|
|
||||||
x11_platform,
|
|
||||||
wayland_platform,
|
|
||||||
docsrs,
|
|
||||||
))]
|
|
||||||
pub mod pump_events;
|
|
||||||
|
|
||||||
#[cfg(any(
|
|
||||||
windows_platform,
|
|
||||||
macos_platform,
|
|
||||||
x11_platform,
|
|
||||||
wayland_platform,
|
|
||||||
orbital_platform,
|
|
||||||
docsrs
|
|
||||||
))]
|
|
||||||
pub mod modifier_supplement;
|
|
||||||
|
|
||||||
#[cfg(any(
|
|
||||||
windows_platform,
|
|
||||||
macos_platform,
|
|
||||||
x11_platform,
|
|
||||||
wayland_platform,
|
|
||||||
docsrs
|
|
||||||
))]
|
|
||||||
pub mod scancode;
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
use crate::event::KeyEvent;
|
|
||||||
use crate::keyboard::Key;
|
|
||||||
|
|
||||||
/// Additional methods for the `KeyEvent` which cannot be implemented on all
|
|
||||||
/// platforms.
|
|
||||||
pub trait KeyEventExtModifierSupplement {
|
|
||||||
/// Identical to `KeyEvent::text` but this is affected by <kbd>Ctrl</kbd>.
|
|
||||||
///
|
|
||||||
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
|
|
||||||
fn text_with_all_modifiers(&self) -> Option<&str>;
|
|
||||||
|
|
||||||
/// This value ignores all modifiers including,
|
|
||||||
/// but not limited to <kbd>Shift</kbd>, <kbd>Caps Lock</kbd>,
|
|
||||||
/// and <kbd>Ctrl</kbd>. In most cases this means that the
|
|
||||||
/// unicode character in the resulting string is lowercase.
|
|
||||||
///
|
|
||||||
/// This is useful for key-bindings / shortcut key combinations.
|
|
||||||
///
|
|
||||||
/// In case `logical_key` reports `Dead`, this will still report the
|
|
||||||
/// key as `Character` according to the current keyboard layout. This value
|
|
||||||
/// cannot be `Dead`.
|
|
||||||
fn key_without_modifiers(&self) -> Key;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyEventExtModifierSupplement for KeyEvent {
|
|
||||||
#[inline]
|
|
||||||
fn text_with_all_modifiers(&self) -> Option<&str> {
|
|
||||||
self.platform_specific
|
|
||||||
.text_with_all_modifiers
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn key_without_modifiers(&self) -> Key {
|
|
||||||
self.platform_specific.key_without_modifiers.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
//! # Orbital / Redox OS
|
|
||||||
//!
|
|
||||||
//! Redox OS has some functionality not yet present that will be implemented
|
|
||||||
//! when its orbital display server provides it.
|
|
||||||
|
|
||||||
// There are no Orbital specific traits yet.
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
use crate::application::ApplicationHandler;
|
|
||||||
use crate::error::EventLoopError;
|
|
||||||
use crate::event::Event;
|
|
||||||
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
|
|
||||||
|
|
||||||
#[cfg(doc)]
|
|
||||||
use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
|
|
||||||
|
|
||||||
/// Additional methods on [`EventLoop`] to return control flow to the caller.
|
|
||||||
pub trait EventLoopExtRunOnDemand {
|
|
||||||
/// A type provided by the user that can be passed through [`Event::UserEvent`].
|
|
||||||
type UserEvent: 'static;
|
|
||||||
|
|
||||||
/// See [`run_app_on_demand`].
|
|
||||||
///
|
|
||||||
/// [`run_app_on_demand`]: Self::run_app_on_demand
|
|
||||||
#[deprecated = "use EventLoopExtRunOnDemand::run_app_on_demand"]
|
|
||||||
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
|
||||||
where
|
|
||||||
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
|
|
||||||
|
|
||||||
/// Run the application with the event loop on the calling thread.
|
|
||||||
///
|
|
||||||
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`) closures
|
|
||||||
/// and it is possible to return control back to the caller without
|
|
||||||
/// consuming the `EventLoop` (by using [`exit()`]) and
|
|
||||||
/// so the event loop can be re-run after it has exit.
|
|
||||||
///
|
|
||||||
/// It's expected that each run of the loop will be for orthogonal instantiations of your
|
|
||||||
/// Winit application, but internally each instantiation may re-use some common window
|
|
||||||
/// system resources, such as a display server connection.
|
|
||||||
///
|
|
||||||
/// This API is not designed to run an event loop in bursts that you can exit from and return
|
|
||||||
/// to while maintaining the full state of your application. (If you need something like this
|
|
||||||
/// you can look at the [`EventLoopExtPumpEvents::pump_app_events()`] API)
|
|
||||||
///
|
|
||||||
/// Each time `run_app_on_demand` is called the startup sequence of `init`, followed by
|
|
||||||
/// `resume` is being preserved.
|
|
||||||
///
|
|
||||||
/// 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 `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_app()`] for portability, unless you
|
|
||||||
/// specifically need the ability to re-run a single event loop more than once
|
|
||||||
///
|
|
||||||
/// # Supported Platforms
|
|
||||||
/// - Windows
|
|
||||||
/// - Linux
|
|
||||||
/// - macOS
|
|
||||||
/// - Android
|
|
||||||
///
|
|
||||||
/// # Unsupported Platforms
|
|
||||||
/// - **Web:** This API is fundamentally incompatible with the event-based way in which
|
|
||||||
/// Web browsers work because it's not possible to have a long-running external
|
|
||||||
/// loop that would block the browser and there is nothing that can be
|
|
||||||
/// polled to ask for new events. Events are delivered via callbacks based
|
|
||||||
/// on an event loop that is internal to the browser itself.
|
|
||||||
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
|
|
||||||
///
|
|
||||||
#[cfg_attr(
|
|
||||||
not(web_platform),
|
|
||||||
doc = "[^1]: `spawn()` is only available on `wasm` platforms."
|
|
||||||
)]
|
|
||||||
///
|
|
||||||
/// [`exit()`]: ActiveEventLoop::exit()
|
|
||||||
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
|
|
||||||
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
|
|
||||||
&mut self,
|
|
||||||
app: &mut A,
|
|
||||||
) -> Result<(), EventLoopError> {
|
|
||||||
#[allow(deprecated)]
|
|
||||||
self.run_on_demand(|event, event_loop| {
|
|
||||||
event_loop::dispatch_event_for_app(app, event_loop, event)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
|
|
||||||
type UserEvent = T;
|
|
||||||
|
|
||||||
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
|
||||||
where
|
|
||||||
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
|
|
||||||
{
|
|
||||||
self.event_loop.window_target().clear_exit();
|
|
||||||
self.event_loop.run_on_demand(event_handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveEventLoop {
|
|
||||||
/// Clear exit status.
|
|
||||||
pub(crate) fn clear_exit(&self) {
|
|
||||||
self.p.clear_exit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```compile_fail
|
|
||||||
/// use winit::event_loop::EventLoop;
|
|
||||||
/// use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
|
|
||||||
///
|
|
||||||
/// let mut event_loop = EventLoop::new().unwrap();
|
|
||||||
/// event_loop.run_on_demand(|_, _| {
|
|
||||||
/// // Attempt to run the event loop re-entrantly; this must fail.
|
|
||||||
/// event_loop.run_on_demand(|_, _| {});
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn test_run_on_demand_cannot_access_event_loop() {}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
//! # Wayland
|
|
||||||
//!
|
|
||||||
//! **Note:** Windows don't appear on Wayland until you draw/present to them.
|
|
||||||
//!
|
|
||||||
//! By default, Winit loads system libraries using `dlopen`. This can be
|
|
||||||
//! disabled by disabling the `"wayland-dlopen"` cargo feature.
|
|
||||||
//!
|
|
||||||
//! ## Client-side decorations
|
|
||||||
//!
|
|
||||||
//! Winit provides client-side decorations by default, but the behaviour can
|
|
||||||
//! be controlled with the following feature flags:
|
|
||||||
//!
|
|
||||||
//! * `wayland-csd-adwaita` (default).
|
|
||||||
//! * `wayland-csd-adwaita-crossfont`.
|
|
||||||
//! * `wayland-csd-adwaita-notitle`.
|
|
||||||
use crate::{
|
|
||||||
event_loop::{ActiveEventLoop, EventLoopBuilder},
|
|
||||||
monitor::MonitorHandle,
|
|
||||||
window::{Window, WindowAttributes},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use crate::window::Theme;
|
|
||||||
|
|
||||||
/// Additional methods on [`ActiveEventLoop`] that are specific to Wayland.
|
|
||||||
pub trait ActiveEventLoopExtWayland {
|
|
||||||
/// True if the [`ActiveEventLoop`] uses Wayland.
|
|
||||||
fn is_wayland(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveEventLoopExtWayland for ActiveEventLoop {
|
|
||||||
#[inline]
|
|
||||||
fn is_wayland(&self) -> bool {
|
|
||||||
self.p.is_wayland()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
|
|
||||||
pub trait EventLoopBuilderExtWayland {
|
|
||||||
/// Force using Wayland.
|
|
||||||
fn with_wayland(&mut self) -> &mut Self;
|
|
||||||
|
|
||||||
/// Whether to allow the event loop to be created off of the main thread.
|
|
||||||
///
|
|
||||||
/// By default, the window is only allowed to be created on the main
|
|
||||||
/// thread, to make platform compatibility easier.
|
|
||||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
|
|
||||||
#[inline]
|
|
||||||
fn with_wayland(&mut self) -> &mut Self {
|
|
||||||
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
|
|
||||||
self.platform_specific.any_thread = any_thread;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`Window`] that are specific to Wayland.
|
|
||||||
pub trait WindowExtWayland {}
|
|
||||||
|
|
||||||
impl WindowExtWayland for Window {}
|
|
||||||
|
|
||||||
/// Additional methods on [`WindowAttributes`] that are specific to Wayland.
|
|
||||||
pub trait WindowAttributesExtWayland {
|
|
||||||
/// Build window with the given name.
|
|
||||||
///
|
|
||||||
/// The `general` name sets an application ID, which should match the `.desktop`
|
|
||||||
/// file distributed with your program. The `instance` is a `no-op`.
|
|
||||||
///
|
|
||||||
/// 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)
|
|
||||||
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowAttributesExtWayland for WindowAttributes {
|
|
||||||
#[inline]
|
|
||||||
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
|
|
||||||
self.platform_specific.name = Some(crate::platform_impl::ApplicationName::new(
|
|
||||||
general.into(),
|
|
||||||
instance.into(),
|
|
||||||
));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `MonitorHandle` that are specific to Wayland.
|
|
||||||
pub trait MonitorHandleExtWayland {
|
|
||||||
/// Returns the inner identifier of the monitor.
|
|
||||||
fn native_id(&self) -> u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandleExtWayland for MonitorHandle {
|
|
||||||
#[inline]
|
|
||||||
fn native_id(&self) -> u32 {
|
|
||||||
self.inner.native_identifier()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,387 +0,0 @@
|
|||||||
//! # Web
|
|
||||||
//!
|
|
||||||
//! The officially supported browsers are Chrome, Firefox and Safari 13.1+,
|
|
||||||
//! though forks of these should work fine.
|
|
||||||
//!
|
|
||||||
//! Winit supports compiling to the `wasm32-unknown-unknown` target with
|
|
||||||
//! `web-sys`.
|
|
||||||
//!
|
|
||||||
//! On the web platform, a Winit window is backed by a `<canvas>` element. You
|
|
||||||
//! can either [provide Winit with a `<canvas>` element][with_canvas], or
|
|
||||||
//! [let Winit create a `<canvas>` element which you can then retrieve][get]
|
|
||||||
//! and insert it into the DOM yourself.
|
|
||||||
//!
|
|
||||||
//! Currently, there is no example code using Winit on Web, see [#3473]. For
|
|
||||||
//! information on using Rust on WebAssembly, check out the [Rust and
|
|
||||||
//! WebAssembly book].
|
|
||||||
//!
|
|
||||||
//! [with_canvas]: WindowAttributesExtWebSys::with_canvas
|
|
||||||
//! [get]: WindowExtWebSys::canvas
|
|
||||||
//! [#3473]: https://github.com/rust-windowing/winit/issues/3473
|
|
||||||
//! [Rust and WebAssembly book]: https://rustwasm.github.io/book/
|
|
||||||
//!
|
|
||||||
//! ## CSS properties
|
|
||||||
//!
|
|
||||||
//! It is recommended **not** to apply certain CSS properties to the canvas:
|
|
||||||
//! - [`transform`](https://developer.mozilla.org/en-US/docs/Web/CSS/transform)
|
|
||||||
//! - [`border`](https://developer.mozilla.org/en-US/docs/Web/CSS/border)
|
|
||||||
//! - [`padding`](https://developer.mozilla.org/en-US/docs/Web/CSS/padding)
|
|
||||||
//!
|
|
||||||
//! The following APIs can't take them into account and will therefore provide inaccurate results:
|
|
||||||
//! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`]
|
|
||||||
//! - [`WindowEvent::Occluded`]
|
|
||||||
//! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`],
|
|
||||||
//! and [`WindowEvent::Touch`].
|
|
||||||
//! - [`Window::set_outer_position()`]
|
|
||||||
//!
|
|
||||||
//! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized
|
|
||||||
//! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size
|
|
||||||
//! [`WindowEvent::Occluded`]: crate::event::WindowEvent::Occluded
|
|
||||||
//! [`WindowEvent::CursorMoved`]: crate::event::WindowEvent::CursorMoved
|
|
||||||
//! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered
|
|
||||||
//! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft
|
|
||||||
//! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch
|
|
||||||
//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position
|
|
||||||
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[cfg(web_platform)]
|
|
||||||
use web_sys::HtmlCanvasElement;
|
|
||||||
|
|
||||||
use crate::application::ApplicationHandler;
|
|
||||||
use crate::cursor::CustomCursorSource;
|
|
||||||
use crate::event::Event;
|
|
||||||
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
|
|
||||||
#[cfg(web_platform)]
|
|
||||||
use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture;
|
|
||||||
use crate::platform_impl::PlatformCustomCursorSource;
|
|
||||||
use crate::window::{CustomCursor, Window, WindowAttributes};
|
|
||||||
|
|
||||||
#[cfg(not(web_platform))]
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct HtmlCanvasElement;
|
|
||||||
|
|
||||||
pub trait WindowExtWebSys {
|
|
||||||
/// Only returns the canvas if called from inside the window context (the
|
|
||||||
/// main thread).
|
|
||||||
fn canvas(&self) -> Option<HtmlCanvasElement>;
|
|
||||||
|
|
||||||
/// Returns [`true`] if calling `event.preventDefault()` is enabled.
|
|
||||||
///
|
|
||||||
/// See [`Window::set_prevent_default()`] for more details.
|
|
||||||
fn prevent_default(&self) -> bool;
|
|
||||||
|
|
||||||
/// Sets whether `event.preventDefault()` should be called on events on the
|
|
||||||
/// canvas that have side effects.
|
|
||||||
///
|
|
||||||
/// For example, by default using the mouse wheel would cause the page to scroll, enabling this
|
|
||||||
/// would prevent that.
|
|
||||||
///
|
|
||||||
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
|
|
||||||
/// context menu with Shift+Rightclick.
|
|
||||||
fn set_prevent_default(&self, prevent_default: bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowExtWebSys for Window {
|
|
||||||
#[inline]
|
|
||||||
fn canvas(&self) -> Option<HtmlCanvasElement> {
|
|
||||||
self.window.canvas()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prevent_default(&self) -> bool {
|
|
||||||
self.window.prevent_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_prevent_default(&self, prevent_default: bool) {
|
|
||||||
self.window.set_prevent_default(prevent_default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait WindowAttributesExtWebSys {
|
|
||||||
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`],
|
|
||||||
/// [`WindowAttributes::default()`] will create one.
|
|
||||||
///
|
|
||||||
/// In any case, the canvas won't be automatically inserted into the web page.
|
|
||||||
///
|
|
||||||
/// [`None`] by default.
|
|
||||||
#[cfg_attr(
|
|
||||||
not(web_platform),
|
|
||||||
doc = "",
|
|
||||||
doc = "[`HtmlCanvasElement`]: #only-available-on-wasm"
|
|
||||||
)]
|
|
||||||
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
|
|
||||||
|
|
||||||
/// Sets whether `event.preventDefault()` should be called on events on the
|
|
||||||
/// canvas that have side effects.
|
|
||||||
///
|
|
||||||
/// See [`Window::set_prevent_default()`] for more details.
|
|
||||||
///
|
|
||||||
/// Enabled by default.
|
|
||||||
fn with_prevent_default(self, prevent_default: bool) -> Self;
|
|
||||||
|
|
||||||
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
|
|
||||||
/// canvas keyboard events.
|
|
||||||
///
|
|
||||||
/// Enabled by default.
|
|
||||||
fn with_focusable(self, focusable: bool) -> Self;
|
|
||||||
|
|
||||||
/// On window creation, append the canvas element to the web page if it isn't already.
|
|
||||||
///
|
|
||||||
/// Disabled by default.
|
|
||||||
fn with_append(self, append: bool) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowAttributesExtWebSys for WindowAttributes {
|
|
||||||
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
|
|
||||||
self.platform_specific.set_canvas(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `EventLoop` that are specific to the web.
|
|
||||||
pub trait EventLoopExtWebSys {
|
|
||||||
/// A type provided by the user that can be passed through `Event::UserEvent`.
|
|
||||||
type UserEvent: 'static;
|
|
||||||
|
|
||||||
/// Initializes the winit event loop.
|
|
||||||
///
|
|
||||||
/// Unlike
|
|
||||||
#[cfg_attr(
|
|
||||||
all(web_platform, target_feature = "exception-handling"),
|
|
||||||
doc = "`run_app()`"
|
|
||||||
)]
|
|
||||||
#[cfg_attr(
|
|
||||||
not(all(web_platform, target_feature = "exception-handling")),
|
|
||||||
doc = "[`run_app()`]"
|
|
||||||
)]
|
|
||||||
/// [^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(web_platform, target_feature = "exception-handling")),
|
|
||||||
doc = "[`run_app()`]: EventLoop::run_app()"
|
|
||||||
)]
|
|
||||||
/// [^1]: `run_app()` is _not_ available on WASM when the target supports `exception-handling`.
|
|
||||||
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A);
|
|
||||||
|
|
||||||
/// See [`spawn_app`].
|
|
||||||
///
|
|
||||||
/// [`spawn_app`]: Self::spawn_app
|
|
||||||
#[deprecated = "use EventLoopExtWebSys::spawn_app"]
|
|
||||||
fn spawn<F>(self, event_handler: F)
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopExtWebSys for EventLoop<T> {
|
|
||||||
type UserEvent = T;
|
|
||||||
|
|
||||||
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, mut app: A) {
|
|
||||||
self.event_loop.spawn(move |event, event_loop| {
|
|
||||||
event_loop::dispatch_event_for_app(&mut app, event_loop, event)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn<F>(self, event_handler: F)
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
|
|
||||||
{
|
|
||||||
self.event_loop.spawn(event_handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ActiveEventLoopExtWebSys {
|
|
||||||
/// 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;
|
|
||||||
|
|
||||||
/// Async version of [`ActiveEventLoop::create_custom_cursor()`] which waits until the
|
|
||||||
/// cursor has completely finished loading.
|
|
||||||
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveEventLoopExtWebSys for ActiveEventLoop {
|
|
||||||
#[inline]
|
|
||||||
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture {
|
|
||||||
self.p.create_custom_cursor_async(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait CustomCursorExtWebSys {
|
|
||||||
/// Returns if this cursor is an animation.
|
|
||||||
fn is_animation(&self) -> bool;
|
|
||||||
|
|
||||||
/// Creates a new cursor from a URL pointing to an image.
|
|
||||||
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
|
|
||||||
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
|
|
||||||
///
|
|
||||||
/// [PNG]: https://en.wikipedia.org/wiki/PNG
|
|
||||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource;
|
|
||||||
|
|
||||||
/// Crates a new animated cursor from multiple [`CustomCursor`]s.
|
|
||||||
/// Supplied `cursors` can't be empty or other animations.
|
|
||||||
fn from_animation(
|
|
||||||
duration: Duration,
|
|
||||||
cursors: Vec<CustomCursor>,
|
|
||||||
) -> Result<CustomCursorSource, BadAnimation>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomCursorExtWebSys for CustomCursor {
|
|
||||||
fn is_animation(&self) -> bool {
|
|
||||||
self.inner.animation
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource {
|
|
||||||
CustomCursorSource {
|
|
||||||
inner: PlatformCustomCursorSource::Url {
|
|
||||||
url,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_animation(
|
|
||||||
duration: Duration,
|
|
||||||
cursors: Vec<CustomCursor>,
|
|
||||||
) -> Result<CustomCursorSource, BadAnimation> {
|
|
||||||
if cursors.is_empty() {
|
|
||||||
return Err(BadAnimation::Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
if cursors.iter().any(CustomCursor::is_animation) {
|
|
||||||
return Err(BadAnimation::Animation);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(CustomCursorSource {
|
|
||||||
inner: PlatformCustomCursorSource::Animation { duration, cursors },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error produced when using [`CustomCursor::from_animation`] with invalid arguments.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum BadAnimation {
|
|
||||||
/// Produced when no cursors were supplied.
|
|
||||||
Empty,
|
|
||||||
/// Produced when a supplied cursor is an animation.
|
|
||||||
Animation,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for BadAnimation {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Empty => write!(f, "No cursors supplied"),
|
|
||||||
Self::Animation => write!(f, "A supplied cursor is an animtion"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for BadAnimation {}
|
|
||||||
|
|
||||||
#[cfg(not(web_platform))]
|
|
||||||
struct PlatformCustomCursorFuture;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CustomCursorFuture(pub(crate) PlatformCustomCursorFuture);
|
|
||||||
|
|
||||||
impl Future for CustomCursorFuture {
|
|
||||||
type Output = Result<CustomCursor, CustomCursorError>;
|
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
Pin::new(&mut self.0)
|
|
||||||
.poll(cx)
|
|
||||||
.map_ok(|cursor| CustomCursor { inner: cursor })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum CustomCursorError {
|
|
||||||
Blob,
|
|
||||||
Decode(String),
|
|
||||||
Animation,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for CustomCursorError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Blob => write!(f, "failed to create `Blob`"),
|
|
||||||
Self::Decode(error) => write!(f, "failed to decode image: {error}"),
|
|
||||||
Self::Animation => write!(
|
|
||||||
f,
|
|
||||||
"found `CustomCursor` that is an animation when building an animation"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,550 +0,0 @@
|
|||||||
//! # Windows
|
|
||||||
//!
|
|
||||||
//! The supported OS version is Windows 7 or higher, though Windows 10 is
|
|
||||||
//! tested regularly.
|
|
||||||
use std::{ffi::c_void, path::Path};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::PhysicalSize,
|
|
||||||
event::DeviceId,
|
|
||||||
event_loop::EventLoopBuilder,
|
|
||||||
monitor::MonitorHandle,
|
|
||||||
window::{BadIcon, Icon, Window, WindowAttributes},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Window Handle type used by Win32 API
|
|
||||||
pub type HWND = isize;
|
|
||||||
/// Menu Handle type used by Win32 API
|
|
||||||
pub type HMENU = isize;
|
|
||||||
/// Monitor Handle type used by Win32 API
|
|
||||||
pub type HMONITOR = isize;
|
|
||||||
|
|
||||||
/// Describes a system-drawn backdrop material of a window.
|
|
||||||
///
|
|
||||||
/// For a detailed explanation, see [`DWM_SYSTEMBACKDROP_TYPE docs`].
|
|
||||||
///
|
|
||||||
/// [`DWM_SYSTEMBACKDROP_TYPE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub enum BackdropType {
|
|
||||||
/// Corresponds to `DWMSBT_AUTO`.
|
|
||||||
///
|
|
||||||
/// Usually draws a default backdrop effect on the title bar.
|
|
||||||
#[default]
|
|
||||||
Auto = 0,
|
|
||||||
|
|
||||||
/// Corresponds to `DWMSBT_NONE`.
|
|
||||||
None = 1,
|
|
||||||
|
|
||||||
/// Corresponds to `DWMSBT_MAINWINDOW`.
|
|
||||||
///
|
|
||||||
/// Draws the Mica backdrop material.
|
|
||||||
MainWindow = 2,
|
|
||||||
|
|
||||||
/// Corresponds to `DWMSBT_TRANSIENTWINDOW`.
|
|
||||||
///
|
|
||||||
/// Draws the Background Acrylic backdrop material.
|
|
||||||
TransientWindow = 3,
|
|
||||||
|
|
||||||
/// Corresponds to `DWMSBT_TABBEDWINDOW`.
|
|
||||||
///
|
|
||||||
/// Draws the Alt Mica backdrop material.
|
|
||||||
TabbedWindow = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes a color used by Windows
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub struct Color(u32);
|
|
||||||
|
|
||||||
impl Color {
|
|
||||||
/// Use the system's default color
|
|
||||||
pub const SYSTEM_DEFAULT: Color = Color(0xFFFFFFFF);
|
|
||||||
|
|
||||||
//Special constant only valid for the window border and therefore modeled using Option<Color> for user facing code
|
|
||||||
const NONE: Color = Color(0xFFFFFFFE);
|
|
||||||
|
|
||||||
/// Create a new color from the given RGB values
|
|
||||||
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
|
|
||||||
Self((r as u32) | ((g as u32) << 8) | ((b as u32) << 16))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Color {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::SYSTEM_DEFAULT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes how the corners of a window should look like.
|
|
||||||
///
|
|
||||||
/// For a detailed explanation, see [`DWM_WINDOW_CORNER_PREFERENCE docs`].
|
|
||||||
///
|
|
||||||
/// [`DWM_WINDOW_CORNER_PREFERENCE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
|
|
||||||
#[repr(i32)]
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub enum CornerPreference {
|
|
||||||
/// Corresponds to `DWMWCP_DEFAULT`.
|
|
||||||
///
|
|
||||||
/// Let the system decide when to round window corners.
|
|
||||||
#[default]
|
|
||||||
Default = 0,
|
|
||||||
|
|
||||||
/// Corresponds to `DWMWCP_DONOTROUND`.
|
|
||||||
///
|
|
||||||
/// Never round window corners.
|
|
||||||
DoNotRound = 1,
|
|
||||||
|
|
||||||
/// Corresponds to `DWMWCP_ROUND`.
|
|
||||||
///
|
|
||||||
/// Round the corners, if appropriate.
|
|
||||||
Round = 2,
|
|
||||||
|
|
||||||
/// Corresponds to `DWMWCP_ROUNDSMALL`.
|
|
||||||
///
|
|
||||||
/// Round the corners if appropriate, with a small radius.
|
|
||||||
RoundSmall = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `EventLoop` that are specific to Windows.
|
|
||||||
pub trait EventLoopBuilderExtWindows {
|
|
||||||
/// Whether to allow the event loop to be created off of the main thread.
|
|
||||||
///
|
|
||||||
/// By default, the window is only allowed to be created on the main
|
|
||||||
/// thread, to make platform compatibility easier.
|
|
||||||
///
|
|
||||||
/// # `Window` caveats
|
|
||||||
///
|
|
||||||
/// Note that any `Window` created on the new thread will be destroyed when the thread
|
|
||||||
/// terminates. Attempting to use a `Window` after its parent thread terminates has
|
|
||||||
/// unspecified, although explicitly not undefined, behavior.
|
|
||||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
|
|
||||||
|
|
||||||
/// Whether to enable process-wide DPI awareness.
|
|
||||||
///
|
|
||||||
/// By default, `winit` will attempt to enable process-wide DPI awareness. If
|
|
||||||
/// that's undesirable, you can disable it with this function.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// Disable process-wide DPI awareness.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use winit::event_loop::EventLoopBuilder;
|
|
||||||
/// #[cfg(target_os = "windows")]
|
|
||||||
/// use winit::platform::windows::EventLoopBuilderExtWindows;
|
|
||||||
///
|
|
||||||
/// let mut builder = EventLoopBuilder::new();
|
|
||||||
/// #[cfg(target_os = "windows")]
|
|
||||||
/// builder.with_dpi_aware(false);
|
|
||||||
/// # if false { // We can't test this part
|
|
||||||
/// let event_loop = builder.build();
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self;
|
|
||||||
|
|
||||||
/// A callback to be executed before dispatching a win32 message to the window procedure.
|
|
||||||
/// Return true to disable winit's internal message dispatching.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use windows_sys::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG};
|
|
||||||
/// use winit::event_loop::EventLoopBuilder;
|
|
||||||
/// #[cfg(target_os = "windows")]
|
|
||||||
/// use winit::platform::windows::EventLoopBuilderExtWindows;
|
|
||||||
///
|
|
||||||
/// let mut builder = EventLoopBuilder::new();
|
|
||||||
/// #[cfg(target_os = "windows")]
|
|
||||||
/// builder.with_msg_hook(|msg|{
|
|
||||||
/// let msg = msg as *const MSG;
|
|
||||||
/// # let accels: Vec<ACCEL> = Vec::new();
|
|
||||||
/// let translated = unsafe {
|
|
||||||
/// TranslateAcceleratorW(
|
|
||||||
/// (*msg).hwnd,
|
|
||||||
/// CreateAcceleratorTableW(accels.as_ptr() as _, 1),
|
|
||||||
/// msg,
|
|
||||||
/// ) == 1
|
|
||||||
/// };
|
|
||||||
/// translated
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
|
|
||||||
where
|
|
||||||
F: FnMut(*const c_void) -> bool + 'static;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
|
|
||||||
#[inline]
|
|
||||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
|
|
||||||
self.platform_specific.any_thread = any_thread;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self {
|
|
||||||
self.platform_specific.dpi_aware = dpi_aware;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
|
|
||||||
where
|
|
||||||
F: FnMut(*const c_void) -> bool + 'static,
|
|
||||||
{
|
|
||||||
self.platform_specific.msg_hook = Some(Box::new(callback));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `Window` that are specific to Windows.
|
|
||||||
pub trait WindowExtWindows {
|
|
||||||
/// Enables or disables mouse and keyboard input to the specified window.
|
|
||||||
///
|
|
||||||
/// A window must be enabled before it can be activated.
|
|
||||||
/// If an application has create a modal dialog box by disabling its owner window
|
|
||||||
/// (as described in [`WindowAttributesExtWindows::with_owner_window`]), the application must enable
|
|
||||||
/// the owner window before destroying the dialog box.
|
|
||||||
/// Otherwise, another window will receive the keyboard focus and be activated.
|
|
||||||
///
|
|
||||||
/// If a child window is disabled, it is ignored when the system tries to determine which
|
|
||||||
/// window should receive mouse messages.
|
|
||||||
///
|
|
||||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks>
|
|
||||||
/// and <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#disabled-windows>
|
|
||||||
fn set_enable(&self, enabled: bool);
|
|
||||||
|
|
||||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
|
||||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
|
|
||||||
|
|
||||||
/// Whether to show or hide the window icon in the taskbar.
|
|
||||||
fn set_skip_taskbar(&self, skip: bool);
|
|
||||||
|
|
||||||
/// Shows or hides the background drop shadow for undecorated windows.
|
|
||||||
///
|
|
||||||
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
|
|
||||||
fn set_undecorated_shadow(&self, shadow: bool);
|
|
||||||
|
|
||||||
/// Sets system-drawn backdrop type.
|
|
||||||
///
|
|
||||||
/// Requires Windows 11 build 22523+.
|
|
||||||
fn set_system_backdrop(&self, backdrop_type: BackdropType);
|
|
||||||
|
|
||||||
/// Sets the color of the window border.
|
|
||||||
///
|
|
||||||
/// Supported starting with Windows 11 Build 22000.
|
|
||||||
fn set_border_color(&self, color: Option<Color>);
|
|
||||||
|
|
||||||
/// Sets the background color of the title bar.
|
|
||||||
///
|
|
||||||
/// Supported starting with Windows 11 Build 22000.
|
|
||||||
fn set_title_background_color(&self, color: Option<Color>);
|
|
||||||
|
|
||||||
/// Sets the color of the window title.
|
|
||||||
///
|
|
||||||
/// Supported starting with Windows 11 Build 22000.
|
|
||||||
fn set_title_text_color(&self, color: Color);
|
|
||||||
|
|
||||||
/// Sets the preferred style of the window corners.
|
|
||||||
///
|
|
||||||
/// Supported starting with Windows 11 Build 22000.
|
|
||||||
fn set_corner_preference(&self, preference: CornerPreference);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowExtWindows for Window {
|
|
||||||
#[inline]
|
|
||||||
fn set_enable(&self, enabled: bool) {
|
|
||||||
self.window.set_enable(enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
|
|
||||||
self.window.set_taskbar_icon(taskbar_icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_skip_taskbar(&self, skip: bool) {
|
|
||||||
self.window.set_skip_taskbar(skip)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_undecorated_shadow(&self, shadow: bool) {
|
|
||||||
self.window.set_undecorated_shadow(shadow)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_system_backdrop(&self, backdrop_type: BackdropType) {
|
|
||||||
self.window.set_system_backdrop(backdrop_type)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_border_color(&self, color: Option<Color>) {
|
|
||||||
self.window.set_border_color(color.unwrap_or(Color::NONE))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_title_background_color(&self, color: Option<Color>) {
|
|
||||||
// The windows docs don't mention NONE as a valid options but it works in practice and is useful
|
|
||||||
// to circumvent the Windows option "Show accent color on title bars and window borders"
|
|
||||||
self.window
|
|
||||||
.set_title_background_color(color.unwrap_or(Color::NONE))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_title_text_color(&self, color: Color) {
|
|
||||||
self.window.set_title_text_color(color)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_corner_preference(&self, preference: CornerPreference) {
|
|
||||||
self.window.set_corner_preference(preference)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `WindowAttributes` that are specific to Windows.
|
|
||||||
#[allow(rustdoc::broken_intra_doc_links)]
|
|
||||||
pub trait WindowAttributesExtWindows {
|
|
||||||
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
|
|
||||||
/// This only works when [`WindowAttributes::with_parent_window`] isn't called or set to `None`.
|
|
||||||
/// Can be used in combination with [`WindowExtWindows::set_enable(false)`][WindowExtWindows::set_enable]
|
|
||||||
/// on the owner window to create a modal dialog box.
|
|
||||||
///
|
|
||||||
/// From MSDN:
|
|
||||||
/// - An owned window is always above its owner in the z-order.
|
|
||||||
/// - The system automatically destroys an owned window when its owner is destroyed.
|
|
||||||
/// - 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) -> Self;
|
|
||||||
|
|
||||||
/// Sets a menu on the window to be created.
|
|
||||||
///
|
|
||||||
/// Parent and menu are mutually exclusive; a child window cannot have a menu!
|
|
||||||
///
|
|
||||||
/// The menu must have been manually created beforehand with [`CreateMenu`] or similar.
|
|
||||||
///
|
|
||||||
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
|
|
||||||
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
|
|
||||||
///
|
|
||||||
#[cfg_attr(
|
|
||||||
platform_windows,
|
|
||||||
doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
|
|
||||||
)]
|
|
||||||
#[cfg_attr(
|
|
||||||
not(platform_windows),
|
|
||||||
doc = "[`CreateMenu`]: #only-available-on-windows"
|
|
||||||
)]
|
|
||||||
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>) -> Self;
|
|
||||||
|
|
||||||
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
|
|
||||||
fn with_no_redirection_bitmap(self, flag: bool) -> Self;
|
|
||||||
|
|
||||||
/// Enables or disables drag and drop support (enabled by default). Will interfere with other crates
|
|
||||||
/// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of
|
|
||||||
/// `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still attempt to initialize
|
|
||||||
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future.
|
|
||||||
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions.
|
|
||||||
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
|
|
||||||
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) -> Self;
|
|
||||||
|
|
||||||
/// Customize the window class name.
|
|
||||||
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) -> Self;
|
|
||||||
|
|
||||||
/// Sets system-drawn backdrop type.
|
|
||||||
///
|
|
||||||
/// Requires Windows 11 build 22523+.
|
|
||||||
fn with_system_backdrop(self, backdrop_type: BackdropType) -> Self;
|
|
||||||
|
|
||||||
/// This sets or removes `WS_CLIPCHILDREN` style.
|
|
||||||
fn with_clip_children(self, flag: bool) -> Self;
|
|
||||||
|
|
||||||
/// Sets the color of the window border.
|
|
||||||
///
|
|
||||||
/// Supported starting with Windows 11 Build 22000.
|
|
||||||
fn with_border_color(self, color: Option<Color>) -> Self;
|
|
||||||
|
|
||||||
/// Sets the background color of the title bar.
|
|
||||||
///
|
|
||||||
/// Supported starting with Windows 11 Build 22000.
|
|
||||||
fn with_title_background_color(self, color: Option<Color>) -> Self;
|
|
||||||
|
|
||||||
/// Sets the color of the window title.
|
|
||||||
///
|
|
||||||
/// Supported starting with Windows 11 Build 22000.
|
|
||||||
fn with_title_text_color(self, color: Color) -> Self;
|
|
||||||
|
|
||||||
/// Sets the preferred style of the window corners.
|
|
||||||
///
|
|
||||||
/// Supported starting with Windows 11 Build 22000.
|
|
||||||
fn with_corner_preference(self, corners: CornerPreference) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowAttributesExtWindows for WindowAttributes {
|
|
||||||
#[inline]
|
|
||||||
fn with_owner_window(mut self, parent: HWND) -> Self {
|
|
||||||
self.platform_specific.owner = Some(parent);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
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>) -> Self {
|
|
||||||
self.platform_specific.taskbar_icon = taskbar_icon;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
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) -> Self {
|
|
||||||
self.platform_specific.drag_and_drop = flag;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
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) -> Self {
|
|
||||||
self.platform_specific.class_name = class_name.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_undecorated_shadow(mut self, shadow: bool) -> Self {
|
|
||||||
self.platform_specific.decoration_shadow = shadow;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_system_backdrop(mut self, backdrop_type: BackdropType) -> Self {
|
|
||||||
self.platform_specific.backdrop_type = backdrop_type;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_clip_children(mut self, flag: bool) -> Self {
|
|
||||||
self.platform_specific.clip_children = flag;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_border_color(mut self, color: Option<Color>) -> Self {
|
|
||||||
self.platform_specific.border_color = Some(color.unwrap_or(Color::NONE));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_title_background_color(mut self, color: Option<Color>) -> Self {
|
|
||||||
self.platform_specific.title_background_color = Some(color.unwrap_or(Color::NONE));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_title_text_color(mut self, color: Color) -> Self {
|
|
||||||
self.platform_specific.title_text_color = Some(color);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_corner_preference(mut self, corners: CornerPreference) -> Self {
|
|
||||||
self.platform_specific.corner_preference = Some(corners);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `MonitorHandle` that are specific to Windows.
|
|
||||||
pub trait MonitorHandleExtWindows {
|
|
||||||
/// Returns the name of the monitor adapter specific to the Win32 API.
|
|
||||||
fn native_id(&self) -> String;
|
|
||||||
|
|
||||||
/// Returns the handle of the monitor - `HMONITOR`.
|
|
||||||
fn hmonitor(&self) -> HMONITOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandleExtWindows for MonitorHandle {
|
|
||||||
#[inline]
|
|
||||||
fn native_id(&self) -> String {
|
|
||||||
self.inner.native_identifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn hmonitor(&self) -> HMONITOR {
|
|
||||||
self.inner.hmonitor()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `DeviceId` that are specific to Windows.
|
|
||||||
pub trait DeviceIdExtWindows {
|
|
||||||
/// Returns an identifier that persistently refers to this specific device.
|
|
||||||
///
|
|
||||||
/// Will return `None` if the device is no longer available.
|
|
||||||
fn persistent_identifier(&self) -> Option<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeviceIdExtWindows for DeviceId {
|
|
||||||
#[inline]
|
|
||||||
fn persistent_identifier(&self) -> Option<String> {
|
|
||||||
self.0.persistent_identifier()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `Icon` that are specific to Windows.
|
|
||||||
pub trait IconExtWindows: Sized {
|
|
||||||
/// Create an icon from a file path.
|
|
||||||
///
|
|
||||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
|
||||||
/// icon size from the file.
|
|
||||||
///
|
|
||||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
|
||||||
/// to get an icon of the desired size.
|
|
||||||
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
|
|
||||||
-> Result<Self, BadIcon>;
|
|
||||||
|
|
||||||
/// Create an icon from a resource embedded in this executable or library.
|
|
||||||
///
|
|
||||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
|
||||||
/// icon size from the file.
|
|
||||||
///
|
|
||||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
|
||||||
/// to get an icon of the desired size.
|
|
||||||
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IconExtWindows for Icon {
|
|
||||||
fn from_path<P: AsRef<Path>>(
|
|
||||||
path: P,
|
|
||||||
size: Option<PhysicalSize<u32>>,
|
|
||||||
) -> Result<Self, BadIcon> {
|
|
||||||
let win_icon = crate::platform_impl::WinIcon::from_path(path, size)?;
|
|
||||||
Ok(Icon { inner: win_icon })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
|
|
||||||
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
|
|
||||||
Ok(Icon { inner: win_icon })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,103 +0,0 @@
|
|||||||
use icrate::Foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
|
|
||||||
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
|
|
||||||
|
|
||||||
use super::app_state::{self, EventWrapper};
|
|
||||||
use super::uikit::{UIApplication, UIWindow};
|
|
||||||
use super::window::WinitUIWindow;
|
|
||||||
use crate::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
window::WindowId as RootWindowId,
|
|
||||||
};
|
|
||||||
|
|
||||||
declare_class!(
|
|
||||||
pub struct AppDelegate;
|
|
||||||
|
|
||||||
unsafe impl ClassType for AppDelegate {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
const NAME: &'static str = "WinitApplicationDelegate";
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeclaredClass for AppDelegate {}
|
|
||||||
|
|
||||||
// UIApplicationDelegate protocol
|
|
||||||
unsafe impl AppDelegate {
|
|
||||||
#[method(application:didFinishLaunchingWithOptions:)]
|
|
||||||
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
|
|
||||||
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationDidBecomeActive:)]
|
|
||||||
fn did_become_active(&self, _application: &UIApplication) {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationWillResignActive:)]
|
|
||||||
fn will_resign_active(&self, _application: &UIApplication) {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationWillEnterForeground:)]
|
|
||||||
fn will_enter_foreground(&self, application: &UIApplication) {
|
|
||||||
self.send_occluded_event_for_all_windows(application, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationDidEnterBackground:)]
|
|
||||||
fn did_enter_background(&self, application: &UIApplication) {
|
|
||||||
self.send_occluded_event_for_all_windows(application, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationWillTerminate:)]
|
|
||||||
fn will_terminate(&self, application: &UIApplication) {
|
|
||||||
let mut events = Vec::new();
|
|
||||||
for window in application.windows().iter() {
|
|
||||||
if window.is_kind_of::<WinitUIWindow>() {
|
|
||||||
// SAFETY: We just checked that the window is a `winit` window
|
|
||||||
let window = unsafe {
|
|
||||||
let ptr: *const UIWindow = window;
|
|
||||||
let ptr: *const WinitUIWindow = ptr.cast();
|
|
||||||
&*ptr
|
|
||||||
};
|
|
||||||
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.id()),
|
|
||||||
event: WindowEvent::Destroyed,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_events(mtm, events);
|
|
||||||
app_state::terminated(mtm);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationDidReceiveMemoryWarning:)]
|
|
||||||
fn did_receive_memory_warning(&self, _application: &UIApplication) {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
impl AppDelegate {
|
|
||||||
fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) {
|
|
||||||
let mut events = Vec::new();
|
|
||||||
for window in application.windows().iter() {
|
|
||||||
if window.is_kind_of::<WinitUIWindow>() {
|
|
||||||
// SAFETY: We just checked that the window is a `winit` window
|
|
||||||
let window = unsafe {
|
|
||||||
let ptr: *const UIWindow = window;
|
|
||||||
let ptr: *const WinitUIWindow = ptr.cast();
|
|
||||||
&*ptr
|
|
||||||
};
|
|
||||||
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.id()),
|
|
||||||
event: WindowEvent::Occluded(occluded),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_events(mtm, events);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,977 +0,0 @@
|
|||||||
#![deny(unused_results)]
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
cell::{RefCell, RefMut},
|
|
||||||
collections::HashSet,
|
|
||||||
fmt, mem,
|
|
||||||
os::raw::c_void,
|
|
||||||
ptr,
|
|
||||||
sync::{Arc, Mutex, OnceLock},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use core_foundation::base::CFRelease;
|
|
||||||
use core_foundation::date::CFAbsoluteTimeGetCurrent;
|
|
||||||
use core_foundation::runloop::{
|
|
||||||
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
|
|
||||||
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
|
|
||||||
};
|
|
||||||
use icrate::Foundation::{
|
|
||||||
CGRect, CGSize, MainThreadMarker, NSInteger, NSOperatingSystemVersion, NSProcessInfo,
|
|
||||||
};
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::runtime::AnyObject;
|
|
||||||
use objc2::{msg_send, sel};
|
|
||||||
|
|
||||||
use super::uikit::UIView;
|
|
||||||
use super::window::WinitUIWindow;
|
|
||||||
use crate::{
|
|
||||||
dpi::PhysicalSize,
|
|
||||||
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
|
|
||||||
event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow},
|
|
||||||
window::WindowId as RootWindowId,
|
|
||||||
};
|
|
||||||
|
|
||||||
macro_rules! bug {
|
|
||||||
($($msg:tt)*) => {
|
|
||||||
panic!("winit iOS bug, file an issue: {}", format!($($msg)*))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! bug_assert {
|
|
||||||
($test:expr, $($msg:tt)*) => {
|
|
||||||
assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct HandlePendingUserEvents;
|
|
||||||
|
|
||||||
pub(crate) struct EventLoopHandler {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub(crate) handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
|
|
||||||
pub(crate) event_loop: RootActiveEventLoop,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for EventLoopHandler {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("EventLoopHandler")
|
|
||||||
.field("handler", &"...")
|
|
||||||
.field("event_loop", &self.event_loop)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventLoopHandler {
|
|
||||||
fn handle_event(&mut self, event: Event<HandlePendingUserEvents>) {
|
|
||||||
(self.handler)(event, &self.event_loop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) enum EventWrapper {
|
|
||||||
StaticEvent(Event<HandlePendingUserEvents>),
|
|
||||||
ScaleFactorChanged(ScaleFactorChanged),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ScaleFactorChanged {
|
|
||||||
pub(super) window: Id<WinitUIWindow>,
|
|
||||||
pub(super) suggested_size: PhysicalSize<u32>,
|
|
||||||
pub(super) scale_factor: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum UserCallbackTransitionResult<'a> {
|
|
||||||
Success {
|
|
||||||
handler: EventLoopHandler,
|
|
||||||
active_control_flow: ControlFlow,
|
|
||||||
processing_redraws: bool,
|
|
||||||
},
|
|
||||||
ReentrancyPrevented {
|
|
||||||
queued_events: &'a mut Vec<EventWrapper>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Event<HandlePendingUserEvents> {
|
|
||||||
fn is_redraw(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::RedrawRequested,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is the state machine for the app lifecycle
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
|
|
||||||
enum AppStateImpl {
|
|
||||||
NotLaunched {
|
|
||||||
queued_windows: Vec<Id<WinitUIWindow>>,
|
|
||||||
queued_events: Vec<EventWrapper>,
|
|
||||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
|
||||||
},
|
|
||||||
Launching {
|
|
||||||
queued_windows: Vec<Id<WinitUIWindow>>,
|
|
||||||
queued_events: Vec<EventWrapper>,
|
|
||||||
queued_handler: EventLoopHandler,
|
|
||||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
|
||||||
},
|
|
||||||
ProcessingEvents {
|
|
||||||
handler: EventLoopHandler,
|
|
||||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
|
||||||
active_control_flow: ControlFlow,
|
|
||||||
},
|
|
||||||
// special state to deal with reentrancy and prevent mutable aliasing.
|
|
||||||
InUserCallback {
|
|
||||||
queued_events: Vec<EventWrapper>,
|
|
||||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
|
||||||
},
|
|
||||||
ProcessingRedraws {
|
|
||||||
handler: EventLoopHandler,
|
|
||||||
active_control_flow: ControlFlow,
|
|
||||||
},
|
|
||||||
Waiting {
|
|
||||||
waiting_handler: EventLoopHandler,
|
|
||||||
start: Instant,
|
|
||||||
},
|
|
||||||
PollFinished {
|
|
||||||
waiting_handler: EventLoopHandler,
|
|
||||||
},
|
|
||||||
Terminated,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct AppState {
|
|
||||||
// This should never be `None`, except for briefly during a state transition.
|
|
||||||
app_state: Option<AppStateImpl>,
|
|
||||||
control_flow: ControlFlow,
|
|
||||||
waker: EventLoopWaker,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppState {
|
|
||||||
pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> {
|
|
||||||
// basically everything in UIKit requires the main thread, so it's pointless to use the
|
|
||||||
// std::sync APIs.
|
|
||||||
// must be mut because plain `static` requires `Sync`
|
|
||||||
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
|
|
||||||
|
|
||||||
let mut guard = unsafe { APP_STATE.borrow_mut() };
|
|
||||||
if guard.is_none() {
|
|
||||||
#[inline(never)]
|
|
||||||
#[cold]
|
|
||||||
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
|
|
||||||
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
|
|
||||||
**guard = Some(AppState {
|
|
||||||
app_state: Some(AppStateImpl::NotLaunched {
|
|
||||||
queued_windows: Vec::new(),
|
|
||||||
queued_events: Vec::new(),
|
|
||||||
queued_gpu_redraws: HashSet::new(),
|
|
||||||
}),
|
|
||||||
control_flow: ControlFlow::default(),
|
|
||||||
waker,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
init_guard(&mut guard);
|
|
||||||
}
|
|
||||||
RefMut::map(guard, |state| state.as_mut().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn state(&self) -> &AppStateImpl {
|
|
||||||
match &self.app_state {
|
|
||||||
Some(ref state) => state,
|
|
||||||
None => bug!("`AppState` previously failed a state transition"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn state_mut(&mut self) -> &mut AppStateImpl {
|
|
||||||
match &mut self.app_state {
|
|
||||||
Some(ref mut state) => state,
|
|
||||||
None => bug!("`AppState` previously failed a state transition"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take_state(&mut self) -> AppStateImpl {
|
|
||||||
match self.app_state.take() {
|
|
||||||
Some(state) => state,
|
|
||||||
None => bug!("`AppState` previously failed a state transition"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_state(&mut self, new_state: AppStateImpl) {
|
|
||||||
bug_assert!(
|
|
||||||
self.app_state.is_none(),
|
|
||||||
"attempted to set an `AppState` without calling `take_state` first {:?}",
|
|
||||||
self.app_state
|
|
||||||
);
|
|
||||||
self.app_state = Some(new_state)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl {
|
|
||||||
match &mut self.app_state {
|
|
||||||
Some(ref mut state) => mem::replace(state, new_state),
|
|
||||||
None => bug!("`AppState` previously failed a state transition"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_launched(&self) -> bool {
|
|
||||||
!matches!(
|
|
||||||
self.state(),
|
|
||||||
AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_terminated(&self) -> bool {
|
|
||||||
matches!(self.state(), AppStateImpl::Terminated)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn will_launch_transition(&mut self, queued_handler: EventLoopHandler) {
|
|
||||||
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
|
|
||||||
AppStateImpl::NotLaunched {
|
|
||||||
queued_windows,
|
|
||||||
queued_events,
|
|
||||||
queued_gpu_redraws,
|
|
||||||
} => (queued_windows, queued_events, queued_gpu_redraws),
|
|
||||||
s => bug!("unexpected state {:?}", s),
|
|
||||||
};
|
|
||||||
self.set_state(AppStateImpl::Launching {
|
|
||||||
queued_windows,
|
|
||||||
queued_events,
|
|
||||||
queued_handler,
|
|
||||||
queued_gpu_redraws,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) {
|
|
||||||
let (windows, events, handler, queued_gpu_redraws) = match self.take_state() {
|
|
||||||
AppStateImpl::Launching {
|
|
||||||
queued_windows,
|
|
||||||
queued_events,
|
|
||||||
queued_handler,
|
|
||||||
queued_gpu_redraws,
|
|
||||||
} => (
|
|
||||||
queued_windows,
|
|
||||||
queued_events,
|
|
||||||
queued_handler,
|
|
||||||
queued_gpu_redraws,
|
|
||||||
),
|
|
||||||
s => bug!("unexpected state {:?}", s),
|
|
||||||
};
|
|
||||||
self.set_state(AppStateImpl::ProcessingEvents {
|
|
||||||
handler,
|
|
||||||
active_control_flow: self.control_flow,
|
|
||||||
queued_gpu_redraws,
|
|
||||||
});
|
|
||||||
(windows, events)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wakeup_transition(&mut self) -> Option<EventWrapper> {
|
|
||||||
// before `AppState::did_finish_launching` is called, pretend there is no running
|
|
||||||
// event loop.
|
|
||||||
if !self.has_launched() || self.has_terminated() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (handler, event) = match (self.control_flow, self.take_state()) {
|
|
||||||
(ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => (
|
|
||||||
waiting_handler,
|
|
||||||
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
ControlFlow::Wait,
|
|
||||||
AppStateImpl::Waiting {
|
|
||||||
waiting_handler,
|
|
||||||
start,
|
|
||||||
},
|
|
||||||
) => (
|
|
||||||
waiting_handler,
|
|
||||||
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: None,
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
ControlFlow::WaitUntil(requested_resume),
|
|
||||||
AppStateImpl::Waiting {
|
|
||||||
waiting_handler,
|
|
||||||
start,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
let event = if Instant::now() >= requested_resume {
|
|
||||||
EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached {
|
|
||||||
start,
|
|
||||||
requested_resume,
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: Some(requested_resume),
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
(waiting_handler, event)
|
|
||||||
}
|
|
||||||
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.set_state(AppStateImpl::ProcessingEvents {
|
|
||||||
handler,
|
|
||||||
queued_gpu_redraws: Default::default(),
|
|
||||||
active_control_flow: self.control_flow,
|
|
||||||
});
|
|
||||||
Some(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> {
|
|
||||||
// If we're not able to process an event due to recursion or `Init` not having been sent out
|
|
||||||
// yet, then queue the events up.
|
|
||||||
match self.state_mut() {
|
|
||||||
&mut AppStateImpl::Launching {
|
|
||||||
ref mut queued_events,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| &mut AppStateImpl::NotLaunched {
|
|
||||||
ref mut queued_events,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| &mut AppStateImpl::InUserCallback {
|
|
||||||
ref mut queued_events,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
// A lifetime cast: early returns are not currently handled well with NLL, but
|
|
||||||
// polonius handles them well. This transmute is a safe workaround.
|
|
||||||
return unsafe {
|
|
||||||
mem::transmute::<
|
|
||||||
UserCallbackTransitionResult<'_>,
|
|
||||||
UserCallbackTransitionResult<'_>,
|
|
||||||
>(UserCallbackTransitionResult::ReentrancyPrevented {
|
|
||||||
queued_events,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
&mut AppStateImpl::ProcessingEvents { .. }
|
|
||||||
| &mut AppStateImpl::ProcessingRedraws { .. } => {}
|
|
||||||
|
|
||||||
s @ &mut AppStateImpl::PollFinished { .. }
|
|
||||||
| s @ &mut AppStateImpl::Waiting { .. }
|
|
||||||
| s @ &mut AppStateImpl::Terminated => {
|
|
||||||
bug!("unexpected attempted to process an event {:?}", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (handler, queued_gpu_redraws, active_control_flow, processing_redraws) =
|
|
||||||
match self.take_state() {
|
|
||||||
AppStateImpl::Launching { .. }
|
|
||||||
| AppStateImpl::NotLaunched { .. }
|
|
||||||
| AppStateImpl::InUserCallback { .. } => unreachable!(),
|
|
||||||
AppStateImpl::ProcessingEvents {
|
|
||||||
handler,
|
|
||||||
queued_gpu_redraws,
|
|
||||||
active_control_flow,
|
|
||||||
} => (handler, queued_gpu_redraws, active_control_flow, false),
|
|
||||||
AppStateImpl::ProcessingRedraws {
|
|
||||||
handler,
|
|
||||||
active_control_flow,
|
|
||||||
} => (handler, Default::default(), active_control_flow, true),
|
|
||||||
AppStateImpl::PollFinished { .. }
|
|
||||||
| AppStateImpl::Waiting { .. }
|
|
||||||
| AppStateImpl::Terminated => unreachable!(),
|
|
||||||
};
|
|
||||||
self.set_state(AppStateImpl::InUserCallback {
|
|
||||||
queued_events: Vec::new(),
|
|
||||||
queued_gpu_redraws,
|
|
||||||
});
|
|
||||||
UserCallbackTransitionResult::Success {
|
|
||||||
handler,
|
|
||||||
active_control_flow,
|
|
||||||
processing_redraws,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> {
|
|
||||||
let (handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
|
|
||||||
AppStateImpl::ProcessingEvents {
|
|
||||||
handler,
|
|
||||||
queued_gpu_redraws,
|
|
||||||
active_control_flow,
|
|
||||||
} => (handler, queued_gpu_redraws, active_control_flow),
|
|
||||||
s => bug!("unexpected state {:?}", s),
|
|
||||||
};
|
|
||||||
self.set_state(AppStateImpl::ProcessingRedraws {
|
|
||||||
handler,
|
|
||||||
active_control_flow,
|
|
||||||
});
|
|
||||||
queued_gpu_redraws
|
|
||||||
}
|
|
||||||
|
|
||||||
fn events_cleared_transition(&mut self) {
|
|
||||||
if !self.has_launched() || self.has_terminated() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let (waiting_handler, old) = match self.take_state() {
|
|
||||||
AppStateImpl::ProcessingRedraws {
|
|
||||||
handler,
|
|
||||||
active_control_flow,
|
|
||||||
} => (handler, active_control_flow),
|
|
||||||
s => bug!("unexpected state {:?}", s),
|
|
||||||
};
|
|
||||||
|
|
||||||
let new = self.control_flow;
|
|
||||||
match (old, new) {
|
|
||||||
(ControlFlow::Wait, ControlFlow::Wait) => {
|
|
||||||
let start = Instant::now();
|
|
||||||
self.set_state(AppStateImpl::Waiting {
|
|
||||||
waiting_handler,
|
|
||||||
start,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
|
|
||||||
if old_instant == new_instant =>
|
|
||||||
{
|
|
||||||
let start = Instant::now();
|
|
||||||
self.set_state(AppStateImpl::Waiting {
|
|
||||||
waiting_handler,
|
|
||||||
start,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
(_, ControlFlow::Wait) => {
|
|
||||||
let start = Instant::now();
|
|
||||||
self.set_state(AppStateImpl::Waiting {
|
|
||||||
waiting_handler,
|
|
||||||
start,
|
|
||||||
});
|
|
||||||
self.waker.stop()
|
|
||||||
}
|
|
||||||
(_, ControlFlow::WaitUntil(new_instant)) => {
|
|
||||||
let start = Instant::now();
|
|
||||||
self.set_state(AppStateImpl::Waiting {
|
|
||||||
waiting_handler,
|
|
||||||
start,
|
|
||||||
});
|
|
||||||
self.waker.start_at(new_instant)
|
|
||||||
}
|
|
||||||
// Unlike on macOS, handle Poll to Poll transition here to call the waker
|
|
||||||
(_, ControlFlow::Poll) => {
|
|
||||||
self.set_state(AppStateImpl::PollFinished { waiting_handler });
|
|
||||||
self.waker.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn terminated_transition(&mut self) -> EventLoopHandler {
|
|
||||||
match self.replace_state(AppStateImpl::Terminated) {
|
|
||||||
AppStateImpl::ProcessingEvents { handler, .. } => handler,
|
|
||||||
s => bug!("`LoopExiting` happened while not processing events {:?}", s),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) {
|
|
||||||
self.control_flow = control_flow;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn control_flow(&self) -> ControlFlow {
|
|
||||||
self.control_flow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id<WinitUIWindow>) {
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
|
||||||
match this.state_mut() {
|
|
||||||
&mut AppStateImpl::NotLaunched {
|
|
||||||
ref mut queued_windows,
|
|
||||||
..
|
|
||||||
} => return queued_windows.push(window.clone()),
|
|
||||||
&mut AppStateImpl::ProcessingEvents { .. }
|
|
||||||
| &mut AppStateImpl::InUserCallback { .. }
|
|
||||||
| &mut AppStateImpl::ProcessingRedraws { .. } => {}
|
|
||||||
s @ &mut AppStateImpl::Launching { .. }
|
|
||||||
| s @ &mut AppStateImpl::Waiting { .. }
|
|
||||||
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
|
|
||||||
&mut AppStateImpl::Terminated => {
|
|
||||||
panic!("Attempt to create a `Window` after the app has terminated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
drop(this);
|
|
||||||
window.makeKeyAndVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUIWindow>) {
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
|
||||||
match this.state_mut() {
|
|
||||||
&mut AppStateImpl::NotLaunched {
|
|
||||||
ref mut queued_gpu_redraws,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| &mut AppStateImpl::Launching {
|
|
||||||
ref mut queued_gpu_redraws,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| &mut AppStateImpl::ProcessingEvents {
|
|
||||||
ref mut queued_gpu_redraws,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| &mut AppStateImpl::InUserCallback {
|
|
||||||
ref mut queued_gpu_redraws,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let _ = queued_gpu_redraws.insert(window);
|
|
||||||
}
|
|
||||||
s @ &mut AppStateImpl::ProcessingRedraws { .. }
|
|
||||||
| s @ &mut AppStateImpl::Waiting { .. }
|
|
||||||
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
|
|
||||||
&mut AppStateImpl::Terminated => {
|
|
||||||
panic!("Attempt to create a `Window` after the app has terminated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn will_launch(mtm: MainThreadMarker, queued_handler: EventLoopHandler) {
|
|
||||||
AppState::get_mut(mtm).will_launch_transition(queued_handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn did_finish_launching(mtm: MainThreadMarker) {
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
|
||||||
let windows = match this.state_mut() {
|
|
||||||
AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows),
|
|
||||||
s => bug!("unexpected state {:?}", s),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.waker.start();
|
|
||||||
|
|
||||||
// have to drop RefMut because the window setup code below can trigger new events
|
|
||||||
drop(this);
|
|
||||||
|
|
||||||
for window in windows {
|
|
||||||
// Do a little screen dance here to account for windows being created before
|
|
||||||
// `UIApplicationMain` is called. This fixes visual issues such as being
|
|
||||||
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we
|
|
||||||
// gotta reset the `rootViewController`.
|
|
||||||
//
|
|
||||||
// relevant iOS log:
|
|
||||||
// ```
|
|
||||||
// [ApplicationLifecycle] Windows were created before application initialization
|
|
||||||
// completed. This may result in incorrect visual appearance.
|
|
||||||
// ```
|
|
||||||
let screen = window.screen();
|
|
||||||
let _: () = unsafe { msg_send![&window, setScreen: ptr::null::<AnyObject>()] };
|
|
||||||
window.setScreen(&screen);
|
|
||||||
|
|
||||||
let controller = window.rootViewController();
|
|
||||||
window.setRootViewController(None);
|
|
||||||
window.setRootViewController(controller.as_deref());
|
|
||||||
|
|
||||||
window.makeKeyAndVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition();
|
|
||||||
|
|
||||||
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(
|
|
||||||
StartCause::Init,
|
|
||||||
)))
|
|
||||||
.chain(events);
|
|
||||||
handle_nonuser_events(mtm, events);
|
|
||||||
|
|
||||||
// the above window dance hack, could possibly trigger new windows to be created.
|
|
||||||
// we can just set those windows up normally, as they were created after didFinishLaunching
|
|
||||||
for window in windows {
|
|
||||||
window.makeKeyAndVisible();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppState::did_finish_launching handles the special transition `Init`
|
|
||||||
pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
|
||||||
let wakeup_event = match this.wakeup_transition() {
|
|
||||||
None => return,
|
|
||||||
Some(wakeup_event) => wakeup_event,
|
|
||||||
};
|
|
||||||
drop(this);
|
|
||||||
|
|
||||||
handle_nonuser_event(mtm, wakeup_event)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) {
|
|
||||||
handle_nonuser_events(mtm, std::iter::once(event))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
|
|
||||||
mtm: MainThreadMarker,
|
|
||||||
events: I,
|
|
||||||
) {
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
|
||||||
if this.has_terminated() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (mut handler, active_control_flow, processing_redraws) =
|
|
||||||
match this.try_user_callback_transition() {
|
|
||||||
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
|
|
||||||
queued_events.extend(events);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UserCallbackTransitionResult::Success {
|
|
||||||
handler,
|
|
||||||
active_control_flow,
|
|
||||||
processing_redraws,
|
|
||||||
} => (handler, active_control_flow, processing_redraws),
|
|
||||||
};
|
|
||||||
drop(this);
|
|
||||||
|
|
||||||
for wrapper in events {
|
|
||||||
match wrapper {
|
|
||||||
EventWrapper::StaticEvent(event) => {
|
|
||||||
if !processing_redraws && event.is_redraw() {
|
|
||||||
tracing::info!("processing `RedrawRequested` during the main event loop");
|
|
||||||
} else if processing_redraws && !event.is_redraw() {
|
|
||||||
tracing::warn!(
|
|
||||||
"processing non `RedrawRequested` event after the main event loop: {:#?}",
|
|
||||||
event
|
|
||||||
);
|
|
||||||
}
|
|
||||||
handler.handle_event(event)
|
|
||||||
}
|
|
||||||
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
|
||||||
let queued_events = match this.state_mut() {
|
|
||||||
&mut AppStateImpl::InUserCallback {
|
|
||||||
ref mut queued_events,
|
|
||||||
queued_gpu_redraws: _,
|
|
||||||
} => mem::take(queued_events),
|
|
||||||
s => bug!("unexpected state {:?}", s),
|
|
||||||
};
|
|
||||||
if queued_events.is_empty() {
|
|
||||||
let queued_gpu_redraws = match this.take_state() {
|
|
||||||
AppStateImpl::InUserCallback {
|
|
||||||
queued_events: _,
|
|
||||||
queued_gpu_redraws,
|
|
||||||
} => queued_gpu_redraws,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
this.app_state = Some(if processing_redraws {
|
|
||||||
bug_assert!(
|
|
||||||
queued_gpu_redraws.is_empty(),
|
|
||||||
"redraw queued while processing redraws"
|
|
||||||
);
|
|
||||||
AppStateImpl::ProcessingRedraws {
|
|
||||||
handler,
|
|
||||||
active_control_flow,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
AppStateImpl::ProcessingEvents {
|
|
||||||
handler,
|
|
||||||
queued_gpu_redraws,
|
|
||||||
active_control_flow,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
drop(this);
|
|
||||||
|
|
||||||
for wrapper in queued_events {
|
|
||||||
match wrapper {
|
|
||||||
EventWrapper::StaticEvent(event) => {
|
|
||||||
if !processing_redraws && event.is_redraw() {
|
|
||||||
tracing::info!("processing `RedrawRequested` during the main event loop");
|
|
||||||
} else if processing_redraws && !event.is_redraw() {
|
|
||||||
tracing::warn!(
|
|
||||||
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
|
|
||||||
event
|
|
||||||
);
|
|
||||||
}
|
|
||||||
handler.handle_event(event)
|
|
||||||
}
|
|
||||||
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_user_events(mtm: MainThreadMarker) {
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
|
||||||
let (mut handler, active_control_flow, processing_redraws) =
|
|
||||||
match this.try_user_callback_transition() {
|
|
||||||
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
|
|
||||||
bug!("unexpected attempted to process an event")
|
|
||||||
}
|
|
||||||
UserCallbackTransitionResult::Success {
|
|
||||||
handler,
|
|
||||||
active_control_flow,
|
|
||||||
processing_redraws,
|
|
||||||
} => (handler, active_control_flow, processing_redraws),
|
|
||||||
};
|
|
||||||
if processing_redraws {
|
|
||||||
bug!("user events attempted to be sent out while `ProcessingRedraws`");
|
|
||||||
}
|
|
||||||
drop(this);
|
|
||||||
|
|
||||||
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
|
||||||
let queued_events = match this.state_mut() {
|
|
||||||
&mut AppStateImpl::InUserCallback {
|
|
||||||
ref mut queued_events,
|
|
||||||
queued_gpu_redraws: _,
|
|
||||||
} => mem::take(queued_events),
|
|
||||||
s => bug!("unexpected state {:?}", s),
|
|
||||||
};
|
|
||||||
if queued_events.is_empty() {
|
|
||||||
let queued_gpu_redraws = match this.take_state() {
|
|
||||||
AppStateImpl::InUserCallback {
|
|
||||||
queued_events: _,
|
|
||||||
queued_gpu_redraws,
|
|
||||||
} => queued_gpu_redraws,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
this.app_state = Some(AppStateImpl::ProcessingEvents {
|
|
||||||
handler,
|
|
||||||
queued_gpu_redraws,
|
|
||||||
active_control_flow,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
drop(this);
|
|
||||||
|
|
||||||
for wrapper in queued_events {
|
|
||||||
match wrapper {
|
|
||||||
EventWrapper::StaticEvent(event) => handler.handle_event(event),
|
|
||||||
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
|
||||||
if !this.has_launched() || this.has_terminated() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
match this.state_mut() {
|
|
||||||
AppStateImpl::ProcessingEvents { .. } => {}
|
|
||||||
_ => bug!("`ProcessingRedraws` happened unexpectedly"),
|
|
||||||
};
|
|
||||||
drop(this);
|
|
||||||
|
|
||||||
handle_user_events(mtm);
|
|
||||||
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
|
||||||
let redraw_events: Vec<EventWrapper> = this
|
|
||||||
.main_events_cleared_transition()
|
|
||||||
.into_iter()
|
|
||||||
.map(|window| {
|
|
||||||
EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.id()),
|
|
||||||
event: WindowEvent::RedrawRequested,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
drop(this);
|
|
||||||
|
|
||||||
handle_nonuser_events(mtm, redraw_events);
|
|
||||||
handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_events_cleared(mtm: MainThreadMarker) {
|
|
||||||
AppState::get_mut(mtm).events_cleared_transition();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn terminated(mtm: MainThreadMarker) {
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
|
||||||
let mut handler = this.terminated_transition();
|
|
||||||
drop(this);
|
|
||||||
|
|
||||||
handler.handle_event(Event::LoopExiting)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) {
|
|
||||||
let ScaleFactorChanged {
|
|
||||||
suggested_size,
|
|
||||||
scale_factor,
|
|
||||||
window,
|
|
||||||
} = event;
|
|
||||||
let new_inner_size = Arc::new(Mutex::new(suggested_size));
|
|
||||||
let event = Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.id()),
|
|
||||||
event: WindowEvent::ScaleFactorChanged {
|
|
||||||
scale_factor,
|
|
||||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
handler.handle_event(event);
|
|
||||||
let (view, screen_frame) = get_view_and_screen_frame(&window);
|
|
||||||
let physical_size = *new_inner_size.lock().unwrap();
|
|
||||||
drop(new_inner_size);
|
|
||||||
let logical_size = physical_size.to_logical(scale_factor);
|
|
||||||
let size = CGSize::new(logical_size.width, logical_size.height);
|
|
||||||
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
|
|
||||||
view.setFrame(new_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView>, CGRect) {
|
|
||||||
let view_controller = window.rootViewController().unwrap();
|
|
||||||
let view = view_controller.view().unwrap();
|
|
||||||
let bounds = window.bounds();
|
|
||||||
let screen = window.screen();
|
|
||||||
let screen_space = screen.coordinateSpace();
|
|
||||||
let screen_frame = window.convertRect_toCoordinateSpace(bounds, &screen_space);
|
|
||||||
(view, screen_frame)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EventLoopWaker {
|
|
||||||
timer: CFRunLoopTimerRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for EventLoopWaker {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
CFRunLoopTimerInvalidate(self.timer);
|
|
||||||
CFRelease(self.timer as _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventLoopWaker {
|
|
||||||
fn new(rl: CFRunLoopRef) -> EventLoopWaker {
|
|
||||||
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
|
||||||
unsafe {
|
|
||||||
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
|
|
||||||
// It is initially setup with a first fire time really far into the
|
|
||||||
// future, but that gets changed to fire immediately in did_finish_launching
|
|
||||||
let timer = CFRunLoopTimerCreate(
|
|
||||||
ptr::null_mut(),
|
|
||||||
std::f64::MAX,
|
|
||||||
0.000_000_1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
wakeup_main_loop,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
|
|
||||||
|
|
||||||
EventLoopWaker { timer }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop(&mut self) {
|
|
||||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start(&mut self) {
|
|
||||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_at(&mut self, instant: Instant) {
|
|
||||||
let now = Instant::now();
|
|
||||||
if now >= instant {
|
|
||||||
self.start();
|
|
||||||
} else {
|
|
||||||
unsafe {
|
|
||||||
let current = CFAbsoluteTimeGetCurrent();
|
|
||||||
let duration = instant - now;
|
|
||||||
let fsecs =
|
|
||||||
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
|
|
||||||
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! os_capabilities {
|
|
||||||
(
|
|
||||||
$(
|
|
||||||
$(#[$attr:meta])*
|
|
||||||
$error_name:ident: $objc_call:literal,
|
|
||||||
$name:ident: $major:literal-$minor:literal
|
|
||||||
),*
|
|
||||||
$(,)*
|
|
||||||
) => {
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct OSCapabilities {
|
|
||||||
$(
|
|
||||||
pub $name: bool,
|
|
||||||
)*
|
|
||||||
|
|
||||||
os_version: NSOperatingSystemVersion,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OSCapabilities {
|
|
||||||
fn from_os_version(os_version: NSOperatingSystemVersion) -> Self {
|
|
||||||
$(let $name = meets_requirements(os_version, $major, $minor);)*
|
|
||||||
Self { $($name,)* os_version, }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OSCapabilities {$(
|
|
||||||
$(#[$attr])*
|
|
||||||
pub fn $error_name(&self, extra_msg: &str) {
|
|
||||||
tracing::warn!(
|
|
||||||
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
|
|
||||||
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
|
|
||||||
extra_msg
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)*}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
os_capabilities! {
|
|
||||||
/// <https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc>
|
|
||||||
#[allow(unused)] // error message unused
|
|
||||||
safe_area_err_msg: "-[UIView safeAreaInsets]",
|
|
||||||
safe_area: 11-0,
|
|
||||||
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc>
|
|
||||||
home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
|
|
||||||
home_indicator_hidden: 11-0,
|
|
||||||
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc>
|
|
||||||
defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
|
|
||||||
defer_system_gestures: 11-0,
|
|
||||||
/// <https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc>
|
|
||||||
maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
|
|
||||||
maximum_frames_per_second: 10-3,
|
|
||||||
/// <https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc>
|
|
||||||
#[allow(unused)] // error message unused
|
|
||||||
force_touch_err_msg: "-[UITouch force]",
|
|
||||||
force_touch: 9-0,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn meets_requirements(
|
|
||||||
version: NSOperatingSystemVersion,
|
|
||||||
required_major: NSInteger,
|
|
||||||
required_minor: NSInteger,
|
|
||||||
) -> bool {
|
|
||||||
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_version() -> NSOperatingSystemVersion {
|
|
||||||
unsafe {
|
|
||||||
let process_info = NSProcessInfo::processInfo();
|
|
||||||
let atleast_ios_8: bool = msg_send![
|
|
||||||
&process_info,
|
|
||||||
respondsToSelector: sel!(operatingSystemVersion)
|
|
||||||
];
|
|
||||||
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
|
|
||||||
// Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
|
|
||||||
// debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
|
|
||||||
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
|
|
||||||
// has been tested to not even run on macOS 10.15 - Xcode 8 might?
|
|
||||||
//
|
|
||||||
// The minimum required iOS version is likely to grow in the future.
|
|
||||||
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
|
|
||||||
process_info.operatingSystemVersion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn os_capabilities() -> OSCapabilities {
|
|
||||||
// Cache the version lookup for efficiency
|
|
||||||
static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new();
|
|
||||||
OS_CAPABILITIES
|
|
||||||
.get_or_init(|| OSCapabilities::from_os_version(get_version()))
|
|
||||||
.clone()
|
|
||||||
}
|
|
||||||
@@ -1,396 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
ffi::c_void,
|
|
||||||
marker::PhantomData,
|
|
||||||
ptr,
|
|
||||||
sync::mpsc::{self, Receiver, Sender},
|
|
||||||
};
|
|
||||||
|
|
||||||
use core_foundation::base::{CFIndex, CFRelease};
|
|
||||||
use core_foundation::runloop::{
|
|
||||||
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
|
|
||||||
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
|
|
||||||
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
|
|
||||||
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
|
||||||
};
|
|
||||||
use icrate::Foundation::{MainThreadMarker, NSString};
|
|
||||||
use objc2::ClassType;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
error::EventLoopError,
|
|
||||||
event::Event,
|
|
||||||
event_loop::{
|
|
||||||
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed,
|
|
||||||
},
|
|
||||||
platform::ios::Idiom,
|
|
||||||
platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents},
|
|
||||||
window::{CustomCursor, CustomCursorSource},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{app_delegate::AppDelegate, uikit::UIUserInterfaceIdiom};
|
|
||||||
use super::{app_state, monitor, MonitorHandle};
|
|
||||||
use super::{
|
|
||||||
app_state::AppState,
|
|
||||||
uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ActiveEventLoop {
|
|
||||||
pub(super) mtm: MainThreadMarker,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveEventLoop {
|
|
||||||
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor {
|
|
||||||
let _ = source.inner;
|
|
||||||
CustomCursor {
|
|
||||||
inner: super::PlatformCustomCursor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
monitor::uiscreens(self.mtm)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
|
||||||
Some(MonitorHandle::new(UIScreen::main(self.mtm)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_05")]
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
|
|
||||||
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_06")]
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_display_handle_rwh_06(
|
|
||||||
&self,
|
|
||||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
|
||||||
Ok(rwh_06::RawDisplayHandle::UiKit(
|
|
||||||
rwh_06::UiKitDisplayHandle::new(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
|
|
||||||
AppState::get_mut(self.mtm).set_control_flow(control_flow)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn control_flow(&self) -> ControlFlow {
|
|
||||||
AppState::get_mut(self.mtm).control_flow()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn exit(&self) {
|
|
||||||
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
|
|
||||||
// it is not possible to quit an iOS app gracefully and programmatically
|
|
||||||
tracing::warn!("`ControlFlow::Exit` ignored on iOS");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn exiting(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
|
|
||||||
OwnedDisplayHandle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) struct OwnedDisplayHandle;
|
|
||||||
|
|
||||||
impl OwnedDisplayHandle {
|
|
||||||
#[cfg(feature = "rwh_05")]
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
|
|
||||||
rwh_05::UiKitDisplayHandle::empty().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_06")]
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_display_handle_rwh_06(
|
|
||||||
&self,
|
|
||||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
|
||||||
Ok(rwh_06::UiKitDisplayHandle::new().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_user_event<T: 'static>(
|
|
||||||
mut handler: impl FnMut(Event<T>, &RootActiveEventLoop),
|
|
||||||
receiver: mpsc::Receiver<T>,
|
|
||||||
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) {
|
|
||||||
move |event, window_target| match event.map_nonuser_event() {
|
|
||||||
Ok(event) => (handler)(event, window_target),
|
|
||||||
Err(_) => {
|
|
||||||
for event in receiver.try_iter() {
|
|
||||||
(handler)(Event::UserEvent(event), window_target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
mtm: MainThreadMarker,
|
|
||||||
sender: Sender<T>,
|
|
||||||
receiver: Receiver<T>,
|
|
||||||
window_target: RootActiveEventLoop,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct PlatformSpecificEventLoopAttributes {}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
|
||||||
pub(crate) fn new(
|
|
||||||
_: &PlatformSpecificEventLoopAttributes,
|
|
||||||
) -> Result<EventLoop<T>, EventLoopError> {
|
|
||||||
let mtm = MainThreadMarker::new()
|
|
||||||
.expect("On iOS, `EventLoop` must be created on the main thread");
|
|
||||||
|
|
||||||
static mut SINGLETON_INIT: bool = false;
|
|
||||||
unsafe {
|
|
||||||
assert!(
|
|
||||||
!SINGLETON_INIT,
|
|
||||||
"Only one `EventLoop` is supported on iOS. \
|
|
||||||
`EventLoopProxy` might be helpful"
|
|
||||||
);
|
|
||||||
SINGLETON_INIT = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (sender, receiver) = mpsc::channel();
|
|
||||||
|
|
||||||
// this line sets up the main run loop before `UIApplicationMain`
|
|
||||||
setup_control_flow_observers();
|
|
||||||
|
|
||||||
Ok(EventLoop {
|
|
||||||
mtm,
|
|
||||||
sender,
|
|
||||||
receiver,
|
|
||||||
window_target: RootActiveEventLoop {
|
|
||||||
p: ActiveEventLoop { mtm },
|
|
||||||
_marker: PhantomData,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<F>(self, handler: F) -> !
|
|
||||||
where
|
|
||||||
F: FnMut(Event<T>, &RootActiveEventLoop),
|
|
||||||
{
|
|
||||||
let application = UIApplication::shared(self.mtm);
|
|
||||||
assert!(
|
|
||||||
application.is_none(),
|
|
||||||
"\
|
|
||||||
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
|
|
||||||
Note: `EventLoop::run_app` calls `UIApplicationMain` on iOS",
|
|
||||||
);
|
|
||||||
|
|
||||||
let handler = map_user_event(handler, self.receiver);
|
|
||||||
|
|
||||||
let handler = unsafe {
|
|
||||||
std::mem::transmute::<
|
|
||||||
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
|
|
||||||
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
|
|
||||||
>(Box::new(handler))
|
|
||||||
};
|
|
||||||
|
|
||||||
let handler = EventLoopHandler {
|
|
||||||
handler,
|
|
||||||
event_loop: self.window_target,
|
|
||||||
};
|
|
||||||
|
|
||||||
app_state::will_launch(self.mtm, handler);
|
|
||||||
|
|
||||||
// Ensure application delegate is initialized
|
|
||||||
let _ = AppDelegate::class();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
UIApplicationMain(
|
|
||||||
0,
|
|
||||||
ptr::null(),
|
|
||||||
None,
|
|
||||||
Some(&NSString::from_str(AppDelegate::NAME)),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy::new(self.sender.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_target(&self) -> &RootActiveEventLoop {
|
|
||||||
&self.window_target
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventLoopExtIOS
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
|
||||||
pub fn idiom(&self) -> Idiom {
|
|
||||||
match UIDevice::current(self.mtm).userInterfaceIdiom() {
|
|
||||||
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
|
|
||||||
UIUserInterfaceIdiom::Phone => Idiom::Phone,
|
|
||||||
UIUserInterfaceIdiom::Pad => Idiom::Pad,
|
|
||||||
UIUserInterfaceIdiom::TV => Idiom::TV,
|
|
||||||
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
|
|
||||||
_ => Idiom::Unspecified,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopProxy<T> {
|
|
||||||
sender: Sender<T>,
|
|
||||||
source: CFRunLoopSourceRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
|
|
||||||
unsafe impl<T: Send> Sync for EventLoopProxy<T> {}
|
|
||||||
|
|
||||||
impl<T> Clone for EventLoopProxy<T> {
|
|
||||||
fn clone(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy::new(self.sender.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Drop for EventLoopProxy<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
CFRunLoopSourceInvalidate(self.source);
|
|
||||||
CFRelease(self.source as _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopProxy<T> {
|
|
||||||
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
|
|
||||||
unsafe {
|
|
||||||
// just wake up the eventloop
|
|
||||||
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
|
|
||||||
|
|
||||||
// adding a Source to the main CFRunLoop lets us wake it up and
|
|
||||||
// process user events through the normal OS EventLoop mechanisms.
|
|
||||||
let rl = CFRunLoopGetMain();
|
|
||||||
let mut context = CFRunLoopSourceContext {
|
|
||||||
version: 0,
|
|
||||||
info: ptr::null_mut(),
|
|
||||||
retain: None,
|
|
||||||
release: None,
|
|
||||||
copyDescription: None,
|
|
||||||
equal: None,
|
|
||||||
hash: None,
|
|
||||||
schedule: None,
|
|
||||||
cancel: None,
|
|
||||||
perform: event_loop_proxy_handler,
|
|
||||||
};
|
|
||||||
let source =
|
|
||||||
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
|
|
||||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
|
||||||
CFRunLoopWakeUp(rl);
|
|
||||||
|
|
||||||
EventLoopProxy { sender, source }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
|
||||||
self.sender
|
|
||||||
.send(event)
|
|
||||||
.map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
|
|
||||||
unsafe {
|
|
||||||
// let the main thread know there's a new event
|
|
||||||
CFRunLoopSourceSignal(self.source);
|
|
||||||
let rl = CFRunLoopGetMain();
|
|
||||||
CFRunLoopWakeUp(rl);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_control_flow_observers() {
|
|
||||||
unsafe {
|
|
||||||
// begin is queued with the highest priority to ensure it is processed before other observers
|
|
||||||
extern "C" fn control_flow_begin_handler(
|
|
||||||
_: CFRunLoopObserverRef,
|
|
||||||
activity: CFRunLoopActivity,
|
|
||||||
_: *mut c_void,
|
|
||||||
) {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
match activity {
|
|
||||||
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
|
||||||
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
|
|
||||||
// priority to be 0, in order to send AboutToWait before RedrawRequested. This value was
|
|
||||||
// chosen conservatively to guard against apple using different priorities for their redraw
|
|
||||||
// observers in different OS's or on different devices. If it so happens that it's too
|
|
||||||
// conservative, the main symptom would be non-redraw events coming in after `AboutToWait`.
|
|
||||||
//
|
|
||||||
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
|
|
||||||
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
|
||||||
//
|
|
||||||
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
|
|
||||||
extern "C" fn control_flow_main_end_handler(
|
|
||||||
_: CFRunLoopObserverRef,
|
|
||||||
activity: CFRunLoopActivity,
|
|
||||||
_: *mut c_void,
|
|
||||||
) {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
match activity {
|
|
||||||
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
|
|
||||||
kCFRunLoopExit => {} // may happen when running on macOS
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
|
||||||
extern "C" fn control_flow_end_handler(
|
|
||||||
_: CFRunLoopObserverRef,
|
|
||||||
activity: CFRunLoopActivity,
|
|
||||||
_: *mut c_void,
|
|
||||||
) {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
match activity {
|
|
||||||
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
|
|
||||||
kCFRunLoopExit => {} // may happen when running on macOS
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let main_loop = CFRunLoopGetMain();
|
|
||||||
|
|
||||||
let begin_observer = CFRunLoopObserverCreate(
|
|
||||||
ptr::null_mut(),
|
|
||||||
kCFRunLoopAfterWaiting,
|
|
||||||
1, // repeat = true
|
|
||||||
CFIndex::min_value(),
|
|
||||||
control_flow_begin_handler,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
|
|
||||||
|
|
||||||
let main_end_observer = CFRunLoopObserverCreate(
|
|
||||||
ptr::null_mut(),
|
|
||||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
|
||||||
1, // repeat = true
|
|
||||||
0, // see comment on `control_flow_main_end_handler`
|
|
||||||
control_flow_main_end_handler,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
|
|
||||||
|
|
||||||
let end_observer = CFRunLoopObserverCreate(
|
|
||||||
ptr::null_mut(),
|
|
||||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
|
||||||
1, // repeat = true
|
|
||||||
CFIndex::max_value(),
|
|
||||||
control_flow_end_handler,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#![cfg(ios_platform)]
|
|
||||||
#![allow(clippy::let_unit_value)]
|
|
||||||
|
|
||||||
mod app_delegate;
|
|
||||||
mod app_state;
|
|
||||||
mod event_loop;
|
|
||||||
mod monitor;
|
|
||||||
mod uikit;
|
|
||||||
mod view;
|
|
||||||
mod view_controller;
|
|
||||||
mod window;
|
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use crate::event::DeviceId as RootDeviceId;
|
|
||||||
|
|
||||||
pub(crate) use self::{
|
|
||||||
event_loop::{
|
|
||||||
ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle,
|
|
||||||
PlatformSpecificEventLoopAttributes,
|
|
||||||
},
|
|
||||||
monitor::{MonitorHandle, VideoModeHandle},
|
|
||||||
window::{PlatformSpecificWindowAttributes, Window, WindowId},
|
|
||||||
};
|
|
||||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
|
|
||||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorSource;
|
|
||||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
|
||||||
pub(crate) use crate::platform_impl::Fullscreen;
|
|
||||||
|
|
||||||
/// There is no way to detect which device that performed a certain event in
|
|
||||||
/// UIKit (i.e. you can't differentiate between different external keyboards,
|
|
||||||
/// or whether it was the main touchscreen, assistive technologies, or some
|
|
||||||
/// other pointer device that caused a touch event).
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId;
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub const unsafe fn dummy() -> Self {
|
|
||||||
DeviceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct KeyEventExtra {}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum OsError {}
|
|
||||||
|
|
||||||
impl fmt::Display for OsError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "os error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
#![allow(clippy::unnecessary_cast)]
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
collections::{BTreeSet, VecDeque},
|
|
||||||
fmt, hash, ptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger};
|
|
||||||
use objc2::mutability::IsRetainable;
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::Message;
|
|
||||||
|
|
||||||
use super::uikit::{UIScreen, UIScreenMode};
|
|
||||||
use crate::{
|
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
|
||||||
monitor::VideoModeHandle as RootVideoModeHandle,
|
|
||||||
platform_impl::platform::app_state,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Workaround for `MainThreadBound` implementing almost no traits
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
|
|
||||||
|
|
||||||
impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self(MainThreadMarker::run_on_main(|mtm| {
|
|
||||||
MainThreadBound::new(Id::clone(self.0.get(mtm)), mtm)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
|
||||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
|
||||||
// SAFETY: Marker only used to get the pointer
|
|
||||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
|
||||||
Id::as_ptr(self.0.get(mtm)).hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
// SAFETY: Marker only used to get the pointer
|
|
||||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
|
||||||
Id::as_ptr(self.0.get(mtm)) == Id::as_ptr(other.0.get(mtm))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
|
||||||
pub struct VideoModeHandle {
|
|
||||||
pub(crate) size: (u32, u32),
|
|
||||||
pub(crate) bit_depth: u16,
|
|
||||||
pub(crate) refresh_rate_millihertz: u32,
|
|
||||||
screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>,
|
|
||||||
pub(crate) monitor: MonitorHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VideoModeHandle {
|
|
||||||
fn new(
|
|
||||||
uiscreen: Id<UIScreen>,
|
|
||||||
screen_mode: Id<UIScreenMode>,
|
|
||||||
mtm: MainThreadMarker,
|
|
||||||
) -> VideoModeHandle {
|
|
||||||
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
|
|
||||||
let size = screen_mode.size();
|
|
||||||
VideoModeHandle {
|
|
||||||
size: (size.width as u32, size.height as u32),
|
|
||||||
bit_depth: 32,
|
|
||||||
refresh_rate_millihertz,
|
|
||||||
screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)),
|
|
||||||
monitor: MonitorHandle::new(uiscreen),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.size.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bit_depth(&self) -> u16 {
|
|
||||||
self.bit_depth
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
|
||||||
self.refresh_rate_millihertz
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn monitor(&self) -> MonitorHandle {
|
|
||||||
self.monitor.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Id<UIScreenMode> {
|
|
||||||
self.screen_mode.0.get(mtm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MonitorHandle {
|
|
||||||
ui_screen: MainThreadBound<Id<UIScreen>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for MonitorHandle {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
MainThreadMarker::run_on_main(|mtm| Self {
|
|
||||||
ui_screen: MainThreadBound::new(self.ui_screen.get(mtm).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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for MonitorHandle {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
// TODO: Make a better ordering
|
|
||||||
(self as *const Self).cmp(&(other as *const Self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for MonitorHandle {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("MonitorHandle")
|
|
||||||
.field("name", &self.name())
|
|
||||||
.field("size", &self.size())
|
|
||||||
.field("position", &self.position())
|
|
||||||
.field("scale_factor", &self.scale_factor())
|
|
||||||
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz())
|
|
||||||
.finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandle {
|
|
||||||
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 {
|
|
||||||
ui_screen: MainThreadBound::new(ui_screen, mtm),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> Option<String> {
|
|
||||||
MainThreadMarker::run_on_main(|mtm| {
|
|
||||||
let main = UIScreen::main(mtm);
|
|
||||||
if *self.ui_screen(mtm) == main {
|
|
||||||
Some("Primary".to_string())
|
|
||||||
} else if *self.ui_screen(mtm) == main.mirroredScreen() {
|
|
||||||
Some("Mirrored".to_string())
|
|
||||||
} else {
|
|
||||||
UIScreen::screens(mtm)
|
|
||||||
.iter()
|
|
||||||
.position(|rhs| rhs == &**self.ui_screen(mtm))
|
|
||||||
.map(|idx| idx.to_string())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
let bounds = self
|
|
||||||
.ui_screen
|
|
||||||
.get_on_main(|ui_screen| ui_screen.nativeBounds());
|
|
||||||
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
|
||||||
let bounds = self
|
|
||||||
.ui_screen
|
|
||||||
.get_on_main(|ui_screen| ui_screen.nativeBounds());
|
|
||||||
(bounds.origin.x as f64, bounds.origin.y as f64).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
self.ui_screen
|
|
||||||
.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
|
||||||
Some(
|
|
||||||
self.ui_screen
|
|
||||||
.get_on_main(|ui_screen| refresh_rate_millihertz(ui_screen)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
|
|
||||||
MainThreadMarker::run_on_main(|mtm| {
|
|
||||||
let ui_screen = self.ui_screen(mtm);
|
|
||||||
// Use Ord impl of RootVideoModeHandle
|
|
||||||
|
|
||||||
let modes: BTreeSet<_> = ui_screen
|
|
||||||
.availableModes()
|
|
||||||
.into_iter()
|
|
||||||
.map(|mode| RootVideoModeHandle {
|
|
||||||
video_mode: VideoModeHandle::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) -> VideoModeHandle {
|
|
||||||
MainThreadMarker::run_on_main(|mtm| {
|
|
||||||
VideoModeHandle::new(
|
|
||||||
self.ui_screen(mtm).clone(),
|
|
||||||
self.ui_screen(mtm).preferredMode().unwrap(),
|
|
||||||
mtm,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
|
|
||||||
let refresh_rate_millihertz: NSInteger = {
|
|
||||||
let os_capabilities = app_state::os_capabilities();
|
|
||||||
if os_capabilities.maximum_frames_per_second {
|
|
||||||
uiscreen.maximumFramesPerSecond()
|
|
||||||
} else {
|
|
||||||
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
|
|
||||||
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
|
|
||||||
//
|
|
||||||
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
|
|
||||||
// supported, they are all guaranteed to have 60hz refresh rates. This does not
|
|
||||||
// correctly handle external displays. ProMotion displays support 120fps, but they were
|
|
||||||
// introduced at the same time as the `maximumFramesPerSecond` API.
|
|
||||||
//
|
|
||||||
// FIXME: earlier OSs could calculate the refresh rate using
|
|
||||||
// `-[CADisplayLink duration]`.
|
|
||||||
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
|
|
||||||
60
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
refresh_rate_millihertz as u32 * 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
|
|
||||||
UIScreen::screens(mtm)
|
|
||||||
.into_iter()
|
|
||||||
.map(MonitorHandle::new)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
use icrate::Foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
|
||||||
|
|
||||||
use super::{UIResponder, UIWindow};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIApplication;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIApplication {
|
|
||||||
#[inherits(NSObject)]
|
|
||||||
type Super = UIResponder;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UIApplication {
|
|
||||||
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self>> {
|
|
||||||
unsafe { msg_send_id![Self::class(), sharedApplication] }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn windows(&self) -> Id<NSArray<UIWindow>> {
|
|
||||||
unsafe { msg_send_id![self, windows] }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(statusBarFrame)]
|
|
||||||
pub fn statusBarFrame(&self) -> CGRect;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
use icrate::Foundation::NSObject;
|
|
||||||
use objc2::{extern_class, mutability, ClassType};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UICoordinateSpace;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UICoordinateSpace {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
use icrate::Foundation::{MainThreadMarker, NSInteger, NSObject};
|
|
||||||
use objc2::encode::{Encode, Encoding};
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIDevice;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIDevice {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UIDevice {
|
|
||||||
pub fn current(_mtm: MainThreadMarker) -> Id<Self> {
|
|
||||||
unsafe { msg_send_id![Self::class(), currentDevice] }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(userInterfaceIdiom)]
|
|
||||||
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct UIUserInterfaceIdiom(NSInteger);
|
|
||||||
|
|
||||||
unsafe impl Encode for UIUserInterfaceIdiom {
|
|
||||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UIUserInterfaceIdiom {
|
|
||||||
pub const Unspecified: UIUserInterfaceIdiom = UIUserInterfaceIdiom(-1);
|
|
||||||
pub const Phone: UIUserInterfaceIdiom = UIUserInterfaceIdiom(0);
|
|
||||||
pub const Pad: UIUserInterfaceIdiom = UIUserInterfaceIdiom(1);
|
|
||||||
pub const TV: UIUserInterfaceIdiom = UIUserInterfaceIdiom(2);
|
|
||||||
pub const CarPlay: UIUserInterfaceIdiom = UIUserInterfaceIdiom(3);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
use icrate::Foundation::NSObject;
|
|
||||||
use objc2::{extern_class, mutability, ClassType};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIEvent;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIEvent {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
use icrate::Foundation::NSUInteger;
|
|
||||||
use objc2::encode::{Encode, Encoding};
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct UIRectEdge(pub NSUInteger);
|
|
||||||
|
|
||||||
impl UIRectEdge {
|
|
||||||
pub const NONE: Self = Self(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Encode for UIRectEdge {
|
|
||||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
use icrate::Foundation::{CGFloat, NSInteger, NSObject, NSUInteger};
|
|
||||||
use objc2::{
|
|
||||||
encode::{Encode, Encoding},
|
|
||||||
extern_class, extern_methods, mutability, ClassType,
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://developer.apple.com/documentation/uikit/uigesturerecognizer
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIGestureRecognizer;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIGestureRecognizer {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UIGestureRecognizer {
|
|
||||||
#[method(state)]
|
|
||||||
pub fn state(&self) -> UIGestureRecognizerState;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
unsafe impl Encode for UIGestureRecognizer {
|
|
||||||
const ENCODING: Encoding = Encoding::Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.apple.com/documentation/uikit/uigesturerecognizer/state
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct UIGestureRecognizerState(NSInteger);
|
|
||||||
|
|
||||||
unsafe impl Encode for UIGestureRecognizerState {
|
|
||||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl UIGestureRecognizerState {
|
|
||||||
pub const Possible: Self = Self(0);
|
|
||||||
pub const Began: Self = Self(1);
|
|
||||||
pub const Changed: Self = Self(2);
|
|
||||||
pub const Ended: Self = Self(3);
|
|
||||||
pub const Cancelled: Self = Self(4);
|
|
||||||
pub const Failed: Self = Self(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.apple.com/documentation/uikit/uipinchgesturerecognizer
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIPinchGestureRecognizer;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIPinchGestureRecognizer {
|
|
||||||
type Super = UIGestureRecognizer;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UIPinchGestureRecognizer {
|
|
||||||
#[method(scale)]
|
|
||||||
pub fn scale(&self) -> CGFloat;
|
|
||||||
|
|
||||||
#[method(velocity)]
|
|
||||||
pub fn velocity(&self) -> CGFloat;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
unsafe impl Encode for UIPinchGestureRecognizer {
|
|
||||||
const ENCODING: Encoding = Encoding::Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.apple.com/documentation/uikit/uirotationgesturerecognizer
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIRotationGestureRecognizer;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIRotationGestureRecognizer {
|
|
||||||
type Super = UIGestureRecognizer;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UIRotationGestureRecognizer {
|
|
||||||
#[method(rotation)]
|
|
||||||
pub fn rotation(&self) -> CGFloat;
|
|
||||||
|
|
||||||
#[method(velocity)]
|
|
||||||
pub fn velocity(&self) -> CGFloat;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
unsafe impl Encode for UIRotationGestureRecognizer {
|
|
||||||
const ENCODING: Encoding = Encoding::Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.apple.com/documentation/uikit/uitapgesturerecognizer
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UITapGestureRecognizer;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UITapGestureRecognizer {
|
|
||||||
type Super = UIGestureRecognizer;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UITapGestureRecognizer {
|
|
||||||
#[method(setNumberOfTapsRequired:)]
|
|
||||||
pub fn setNumberOfTapsRequired(&self, number_of_taps_required: NSUInteger);
|
|
||||||
|
|
||||||
#[method(setNumberOfTouchesRequired:)]
|
|
||||||
pub fn setNumberOfTouchesRequired(&self, number_of_touches_required: NSUInteger);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
unsafe impl Encode for UITapGestureRecognizer {
|
|
||||||
const ENCODING: Encoding = Encoding::Object;
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#![allow(non_snake_case)]
|
|
||||||
#![allow(non_upper_case_globals)]
|
|
||||||
|
|
||||||
use std::os::raw::{c_char, c_int};
|
|
||||||
|
|
||||||
use icrate::Foundation::NSString;
|
|
||||||
|
|
||||||
mod application;
|
|
||||||
mod coordinate_space;
|
|
||||||
mod device;
|
|
||||||
mod event;
|
|
||||||
mod geometry;
|
|
||||||
mod gesture_recognizer;
|
|
||||||
mod responder;
|
|
||||||
mod screen;
|
|
||||||
mod screen_mode;
|
|
||||||
mod status_bar_style;
|
|
||||||
mod touch;
|
|
||||||
mod trait_collection;
|
|
||||||
mod view;
|
|
||||||
mod view_controller;
|
|
||||||
mod window;
|
|
||||||
|
|
||||||
pub(crate) use self::application::UIApplication;
|
|
||||||
pub(crate) use self::coordinate_space::UICoordinateSpace;
|
|
||||||
pub(crate) use self::device::{UIDevice, UIUserInterfaceIdiom};
|
|
||||||
pub(crate) use self::event::UIEvent;
|
|
||||||
pub(crate) use self::geometry::UIRectEdge;
|
|
||||||
pub(crate) use self::gesture_recognizer::{
|
|
||||||
UIGestureRecognizer, UIGestureRecognizerState, UIPinchGestureRecognizer,
|
|
||||||
UIRotationGestureRecognizer, UITapGestureRecognizer,
|
|
||||||
};
|
|
||||||
pub(crate) use self::responder::UIResponder;
|
|
||||||
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
|
|
||||||
pub(crate) use self::screen_mode::UIScreenMode;
|
|
||||||
pub(crate) use self::status_bar_style::UIStatusBarStyle;
|
|
||||||
pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType};
|
|
||||||
pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection};
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub(crate) use self::view::{UIEdgeInsets, UIView};
|
|
||||||
pub(crate) use self::view_controller::{UIInterfaceOrientationMask, UIViewController};
|
|
||||||
pub(crate) use self::window::UIWindow;
|
|
||||||
|
|
||||||
#[link(name = "UIKit", kind = "framework")]
|
|
||||||
extern "C" {
|
|
||||||
pub fn UIApplicationMain(
|
|
||||||
argc: c_int,
|
|
||||||
argv: *const c_char,
|
|
||||||
principalClassName: Option<&NSString>,
|
|
||||||
delegateClassName: Option<&NSString>,
|
|
||||||
) -> c_int;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
use icrate::Foundation::NSObject;
|
|
||||||
use objc2::{extern_class, mutability, ClassType};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIResponder;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIResponder {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
|
|
||||||
use objc2::encode::{Encode, Encoding};
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
|
||||||
|
|
||||||
use super::{UICoordinateSpace, UIScreenMode};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIScreen;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIScreen {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UIScreen {
|
|
||||||
pub fn main(_mtm: MainThreadMarker) -> Id<Self> {
|
|
||||||
unsafe { msg_send_id![Self::class(), mainScreen] }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self>> {
|
|
||||||
unsafe { msg_send_id![Self::class(), screens] }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(bounds)]
|
|
||||||
pub fn bounds(&self) -> CGRect;
|
|
||||||
|
|
||||||
#[method(scale)]
|
|
||||||
pub fn scale(&self) -> CGFloat;
|
|
||||||
|
|
||||||
#[method(nativeBounds)]
|
|
||||||
pub fn nativeBounds(&self) -> CGRect;
|
|
||||||
|
|
||||||
#[method(nativeScale)]
|
|
||||||
pub fn nativeScale(&self) -> CGFloat;
|
|
||||||
|
|
||||||
#[method(maximumFramesPerSecond)]
|
|
||||||
pub fn maximumFramesPerSecond(&self) -> NSInteger;
|
|
||||||
|
|
||||||
pub fn mirroredScreen(&self) -> Id<Self> {
|
|
||||||
unsafe { msg_send_id![Self::class(), mirroredScreen] }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn preferredMode(&self) -> Option<Id<UIScreenMode>> {
|
|
||||||
unsafe { msg_send_id![self, preferredMode] }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(setCurrentMode:)]
|
|
||||||
pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>);
|
|
||||||
|
|
||||||
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode>> {
|
|
||||||
unsafe { msg_send_id![self, availableModes] }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(setOverscanCompensation:)]
|
|
||||||
pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation);
|
|
||||||
|
|
||||||
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace> {
|
|
||||||
unsafe { msg_send_id![self, coordinateSpace] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct UIScreenOverscanCompensation(NSInteger);
|
|
||||||
|
|
||||||
unsafe impl Encode for UIScreenOverscanCompensation {
|
|
||||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl UIScreenOverscanCompensation {
|
|
||||||
pub const Scale: Self = Self(0);
|
|
||||||
pub const InsetBounds: Self = Self(1);
|
|
||||||
pub const None: Self = Self(2);
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
use icrate::Foundation::{CGSize, NSObject};
|
|
||||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIScreenMode;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIScreenMode {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UIScreenMode {
|
|
||||||
#[method(size)]
|
|
||||||
pub fn size(&self) -> CGSize;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
use icrate::Foundation::NSInteger;
|
|
||||||
use objc2::encode::{Encode, Encoding};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[repr(isize)]
|
|
||||||
pub enum UIStatusBarStyle {
|
|
||||||
#[default]
|
|
||||||
Default = 0,
|
|
||||||
LightContent = 1,
|
|
||||||
DarkContent = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Encode for UIStatusBarStyle {
|
|
||||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
use icrate::Foundation::{CGFloat, CGPoint, NSInteger, NSObject};
|
|
||||||
use objc2::encode::{Encode, Encoding};
|
|
||||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
|
||||||
|
|
||||||
use super::UIView;
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UITouch;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UITouch {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UITouch {
|
|
||||||
#[method(locationInView:)]
|
|
||||||
pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint;
|
|
||||||
|
|
||||||
#[method(type)]
|
|
||||||
pub fn type_(&self) -> UITouchType;
|
|
||||||
|
|
||||||
#[method(force)]
|
|
||||||
pub fn force(&self) -> CGFloat;
|
|
||||||
|
|
||||||
#[method(maximumPossibleForce)]
|
|
||||||
pub fn maximumPossibleForce(&self) -> CGFloat;
|
|
||||||
|
|
||||||
#[method(altitudeAngle)]
|
|
||||||
pub fn altitudeAngle(&self) -> CGFloat;
|
|
||||||
|
|
||||||
#[method(phase)]
|
|
||||||
pub fn phase(&self) -> UITouchPhase;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[repr(isize)]
|
|
||||||
pub enum UITouchType {
|
|
||||||
Direct = 0,
|
|
||||||
Indirect,
|
|
||||||
Pencil,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Encode for UITouchType {
|
|
||||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[repr(isize)]
|
|
||||||
pub enum UITouchPhase {
|
|
||||||
Began = 0,
|
|
||||||
Moved,
|
|
||||||
Stationary,
|
|
||||||
Ended,
|
|
||||||
Cancelled,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Encode for UITouchPhase {
|
|
||||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
use icrate::Foundation::{NSInteger, NSObject};
|
|
||||||
use objc2::encode::{Encode, Encoding};
|
|
||||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UITraitCollection;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UITraitCollection {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UITraitCollection {
|
|
||||||
#[method(forceTouchCapability)]
|
|
||||||
pub fn forceTouchCapability(&self) -> UIForceTouchCapability;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[repr(isize)]
|
|
||||||
pub enum UIForceTouchCapability {
|
|
||||||
Unknown = 0,
|
|
||||||
Unavailable,
|
|
||||||
Available,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Encode for UIForceTouchCapability {
|
|
||||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
use icrate::Foundation::{CGFloat, CGRect, NSObject};
|
|
||||||
use objc2::encode::{Encode, Encoding};
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
|
||||||
|
|
||||||
use super::{UICoordinateSpace, UIGestureRecognizer, UIResponder, UIViewController};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIView;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIView {
|
|
||||||
#[inherits(NSObject)]
|
|
||||||
type Super = UIResponder;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UIView {
|
|
||||||
#[method(bounds)]
|
|
||||||
pub fn bounds(&self) -> CGRect;
|
|
||||||
|
|
||||||
#[method(setBounds:)]
|
|
||||||
pub fn setBounds(&self, value: CGRect);
|
|
||||||
|
|
||||||
#[method(frame)]
|
|
||||||
pub fn frame(&self) -> CGRect;
|
|
||||||
|
|
||||||
#[method(setFrame:)]
|
|
||||||
pub fn setFrame(&self, value: CGRect);
|
|
||||||
|
|
||||||
#[method(contentScaleFactor)]
|
|
||||||
pub fn contentScaleFactor(&self) -> CGFloat;
|
|
||||||
|
|
||||||
#[method(setContentScaleFactor:)]
|
|
||||||
pub fn setContentScaleFactor(&self, val: CGFloat);
|
|
||||||
|
|
||||||
#[method(setMultipleTouchEnabled:)]
|
|
||||||
pub fn setMultipleTouchEnabled(&self, val: bool);
|
|
||||||
|
|
||||||
pub fn rootViewController(&self) -> Option<Id<UIViewController>> {
|
|
||||||
unsafe { msg_send_id![self, rootViewController] }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(setRootViewController:)]
|
|
||||||
pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>);
|
|
||||||
|
|
||||||
#[method(convertRect:toCoordinateSpace:)]
|
|
||||||
pub fn convertRect_toCoordinateSpace(
|
|
||||||
&self,
|
|
||||||
rect: CGRect,
|
|
||||||
coordinateSpace: &UICoordinateSpace,
|
|
||||||
) -> CGRect;
|
|
||||||
|
|
||||||
#[method(convertRect:fromCoordinateSpace:)]
|
|
||||||
pub fn convertRect_fromCoordinateSpace(
|
|
||||||
&self,
|
|
||||||
rect: CGRect,
|
|
||||||
coordinateSpace: &UICoordinateSpace,
|
|
||||||
) -> CGRect;
|
|
||||||
|
|
||||||
#[method(safeAreaInsets)]
|
|
||||||
pub fn safeAreaInsets(&self) -> UIEdgeInsets;
|
|
||||||
|
|
||||||
#[method(setNeedsDisplay)]
|
|
||||||
pub fn setNeedsDisplay(&self);
|
|
||||||
|
|
||||||
#[method(addGestureRecognizer:)]
|
|
||||||
pub fn addGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
|
|
||||||
|
|
||||||
#[method(removeGestureRecognizer:)]
|
|
||||||
pub fn removeGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct UIEdgeInsets {
|
|
||||||
pub top: CGFloat,
|
|
||||||
pub left: CGFloat,
|
|
||||||
pub bottom: CGFloat,
|
|
||||||
pub right: CGFloat,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Encode for UIEdgeInsets {
|
|
||||||
const ENCODING: Encoding = Encoding::Struct(
|
|
||||||
"UIEdgeInsets",
|
|
||||||
&[
|
|
||||||
CGFloat::ENCODING,
|
|
||||||
CGFloat::ENCODING,
|
|
||||||
CGFloat::ENCODING,
|
|
||||||
CGFloat::ENCODING,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
use icrate::Foundation::{NSObject, NSUInteger};
|
|
||||||
use objc2::encode::{Encode, Encoding};
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
|
||||||
|
|
||||||
use super::{UIResponder, UIView};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIViewController;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIViewController {
|
|
||||||
#[inherits(NSObject)]
|
|
||||||
type Super = UIResponder;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UIViewController {
|
|
||||||
#[method(attemptRotationToDeviceOrientation)]
|
|
||||||
pub fn attemptRotationToDeviceOrientation();
|
|
||||||
|
|
||||||
#[method(setNeedsStatusBarAppearanceUpdate)]
|
|
||||||
pub fn setNeedsStatusBarAppearanceUpdate(&self);
|
|
||||||
|
|
||||||
#[method(setNeedsUpdateOfHomeIndicatorAutoHidden)]
|
|
||||||
pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self);
|
|
||||||
|
|
||||||
#[method(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
|
|
||||||
pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self);
|
|
||||||
|
|
||||||
pub fn view(&self) -> Option<Id<UIView>> {
|
|
||||||
unsafe { msg_send_id![self, view] }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(setView:)]
|
|
||||||
pub fn setView(&self, view: Option<&UIView>);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
bitflags::bitflags! {
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct UIInterfaceOrientationMask: NSUInteger {
|
|
||||||
const Portrait = 1 << 1;
|
|
||||||
const PortraitUpsideDown = 1 << 2;
|
|
||||||
const LandscapeRight = 1 << 3;
|
|
||||||
const LandscapeLeft = 1 << 4;
|
|
||||||
const Landscape = Self::LandscapeLeft.bits() | Self::LandscapeRight.bits();
|
|
||||||
const AllButUpsideDown = Self::Landscape.bits() | Self::Portrait.bits();
|
|
||||||
const All = Self::AllButUpsideDown.bits() | Self::PortraitUpsideDown.bits();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Encode for UIInterfaceOrientationMask {
|
|
||||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
use icrate::Foundation::NSObject;
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
|
||||||
|
|
||||||
use super::{UIResponder, UIScreen, UIView};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct UIWindow;
|
|
||||||
|
|
||||||
unsafe impl ClassType for UIWindow {
|
|
||||||
#[inherits(UIResponder, NSObject)]
|
|
||||||
type Super = UIView;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl UIWindow {
|
|
||||||
pub fn screen(&self) -> Id<UIScreen> {
|
|
||||||
unsafe { msg_send_id![self, screen] }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(setScreen:)]
|
|
||||||
pub fn setScreen(&self, screen: &UIScreen);
|
|
||||||
|
|
||||||
#[method(setHidden:)]
|
|
||||||
pub fn setHidden(&self, flag: bool);
|
|
||||||
|
|
||||||
#[method(makeKeyAndVisible)]
|
|
||||||
pub fn makeKeyAndVisible(&self);
|
|
||||||
|
|
||||||
#[method(isKeyWindow)]
|
|
||||||
pub fn isKeyWindow(&self) -> bool;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,391 +0,0 @@
|
|||||||
#![allow(clippy::unnecessary_cast)]
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet};
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::runtime::AnyClass;
|
|
||||||
use objc2::{
|
|
||||||
declare_class, extern_methods, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::app_state::{self, EventWrapper};
|
|
||||||
use super::uikit::{
|
|
||||||
UIEvent, UIForceTouchCapability, UIGestureRecognizerState, UIPinchGestureRecognizer,
|
|
||||||
UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITouch, UITouchPhase,
|
|
||||||
UITouchType, UITraitCollection, UIView,
|
|
||||||
};
|
|
||||||
use super::window::WinitUIWindow;
|
|
||||||
use crate::{
|
|
||||||
dpi::PhysicalPosition,
|
|
||||||
event::{Event, Force, Touch, TouchPhase, WindowEvent},
|
|
||||||
platform_impl::platform::DEVICE_ID,
|
|
||||||
window::{WindowAttributes, WindowId as RootWindowId},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct WinitViewState {
|
|
||||||
pinch_gesture_recognizer: RefCell<Option<Id<UIPinchGestureRecognizer>>>,
|
|
||||||
doubletap_gesture_recognizer: RefCell<Option<Id<UITapGestureRecognizer>>>,
|
|
||||||
rotation_gesture_recognizer: RefCell<Option<Id<UIRotationGestureRecognizer>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
declare_class!(
|
|
||||||
pub(crate) struct WinitView;
|
|
||||||
|
|
||||||
unsafe impl ClassType for WinitView {
|
|
||||||
#[inherits(UIResponder, NSObject)]
|
|
||||||
type Super = UIView;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
const NAME: &'static str = "WinitUIView";
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeclaredClass for WinitView {
|
|
||||||
type Ivars = WinitViewState;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl WinitView {
|
|
||||||
#[method(drawRect:)]
|
|
||||||
fn draw_rect(&self, rect: CGRect) {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
let window = self.window().unwrap();
|
|
||||||
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();
|
|
||||||
let window_bounds = window.bounds();
|
|
||||||
let screen = window.screen();
|
|
||||||
let screen_space = screen.coordinateSpace();
|
|
||||||
let screen_frame = self.convertRect_toCoordinateSpace(window_bounds, &screen_space);
|
|
||||||
let scale_factor = screen.scale();
|
|
||||||
let size = crate::dpi::LogicalSize {
|
|
||||||
width: screen_frame.size.width as f64,
|
|
||||||
height: screen_frame.size.height as f64,
|
|
||||||
}
|
|
||||||
.to_physical(scale_factor as f64);
|
|
||||||
|
|
||||||
// If the app is started in landscape, the view frame and window bounds can be mismatched.
|
|
||||||
// The view frame will be in portrait and the window bounds in landscape. So apply the
|
|
||||||
// window bounds to the view frame to make it consistent.
|
|
||||||
let view_frame = self.frame();
|
|
||||||
if view_frame != window_bounds {
|
|
||||||
self.setFrame(window_bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
app_state::handle_nonuser_event(
|
|
||||||
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] };
|
|
||||||
|
|
||||||
// `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow
|
|
||||||
// makeKeyAndVisible]` at window creation time (either manually or internally by
|
|
||||||
// UIKit when the `UIView` is first created), in which case we send no events here
|
|
||||||
let window = match self.window() {
|
|
||||||
Some(window) => window,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
// `setContentScaleFactor` may be called with a value of 0, which means "reset the
|
|
||||||
// content scale factor to a device-specific default value", so we can't use the
|
|
||||||
// parameter here. We can query the actual factor using the getter
|
|
||||||
let scale_factor = self.contentScaleFactor();
|
|
||||||
assert!(
|
|
||||||
!scale_factor.is_nan()
|
|
||||||
&& scale_factor.is_finite()
|
|
||||||
&& scale_factor.is_sign_positive()
|
|
||||||
&& scale_factor > 0.0,
|
|
||||||
"invalid scale_factor set on UIView",
|
|
||||||
);
|
|
||||||
let scale_factor = scale_factor as f64;
|
|
||||||
let bounds = self.bounds();
|
|
||||||
let screen = window.screen();
|
|
||||||
let screen_space = screen.coordinateSpace();
|
|
||||||
let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space);
|
|
||||||
let size = crate::dpi::LogicalSize {
|
|
||||||
width: screen_frame.size.width as f64,
|
|
||||||
height: screen_frame.size.height as f64,
|
|
||||||
};
|
|
||||||
let window_id = RootWindowId(window.id());
|
|
||||||
app_state::handle_nonuser_events(
|
|
||||||
mtm,
|
|
||||||
std::iter::once(EventWrapper::ScaleFactorChanged(
|
|
||||||
app_state::ScaleFactorChanged {
|
|
||||||
window,
|
|
||||||
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:)]
|
|
||||||
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
|
||||||
self.handle_touches(touches)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(touchesMoved:withEvent:)]
|
|
||||||
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
|
||||||
self.handle_touches(touches)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(touchesEnded:withEvent:)]
|
|
||||||
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
|
||||||
self.handle_touches(touches)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(touchesCancelled:withEvent:)]
|
|
||||||
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
|
||||||
self.handle_touches(touches)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(pinchGesture:)]
|
|
||||||
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
|
|
||||||
let window = self.window().unwrap();
|
|
||||||
|
|
||||||
let phase = match recognizer.state() {
|
|
||||||
UIGestureRecognizerState::Began => TouchPhase::Started,
|
|
||||||
UIGestureRecognizerState::Changed => TouchPhase::Moved,
|
|
||||||
UIGestureRecognizerState::Ended => TouchPhase::Ended,
|
|
||||||
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
|
|
||||||
TouchPhase::Cancelled
|
|
||||||
}
|
|
||||||
state => panic!("unexpected recognizer state: {:?}", state),
|
|
||||||
};
|
|
||||||
|
|
||||||
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.id()),
|
|
||||||
event: WindowEvent::PinchGesture {
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
delta: recognizer.velocity() as _,
|
|
||||||
phase,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_event(mtm, gesture_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(doubleTapGesture:)]
|
|
||||||
fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) {
|
|
||||||
let window = self.window().unwrap();
|
|
||||||
|
|
||||||
if recognizer.state() == UIGestureRecognizerState::Ended {
|
|
||||||
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.id()),
|
|
||||||
event: WindowEvent::DoubleTapGesture {
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_event(mtm, gesture_event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(rotationGesture:)]
|
|
||||||
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
|
|
||||||
let window = self.window().unwrap();
|
|
||||||
|
|
||||||
let phase = match recognizer.state() {
|
|
||||||
UIGestureRecognizerState::Began => TouchPhase::Started,
|
|
||||||
UIGestureRecognizerState::Changed => TouchPhase::Moved,
|
|
||||||
UIGestureRecognizerState::Ended => TouchPhase::Ended,
|
|
||||||
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
|
|
||||||
TouchPhase::Cancelled
|
|
||||||
}
|
|
||||||
state => panic!("unexpected recognizer state: {:?}", state),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Flip the velocity to match macOS.
|
|
||||||
let delta = -recognizer.velocity() as _;
|
|
||||||
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.id()),
|
|
||||||
event: WindowEvent::RotationGesture {
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
delta,
|
|
||||||
phase,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_event(mtm, gesture_event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
unsafe impl WinitView {
|
|
||||||
fn window(&self) -> Option<Id<WinitUIWindow>> {
|
|
||||||
unsafe { msg_send_id![self, window] }
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn traitCollection(&self) -> Id<UITraitCollection> {
|
|
||||||
msg_send_id![self, traitCollection]
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Allow the user to customize this
|
|
||||||
#[method(layerClass)]
|
|
||||||
pub(crate) fn layerClass() -> &'static AnyClass;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
impl WinitView {
|
|
||||||
pub(crate) fn new(
|
|
||||||
_mtm: MainThreadMarker,
|
|
||||||
window_attributes: &WindowAttributes,
|
|
||||||
frame: CGRect,
|
|
||||||
) -> Id<Self> {
|
|
||||||
let this = Self::alloc().set_ivars(WinitViewState {
|
|
||||||
pinch_gesture_recognizer: RefCell::new(None),
|
|
||||||
doubletap_gesture_recognizer: RefCell::new(None),
|
|
||||||
rotation_gesture_recognizer: RefCell::new(None),
|
|
||||||
});
|
|
||||||
let this: Id<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
|
|
||||||
|
|
||||||
this.setMultipleTouchEnabled(true);
|
|
||||||
|
|
||||||
if let Some(scale_factor) = window_attributes.platform_specific.scale_factor {
|
|
||||||
this.setContentScaleFactor(scale_factor as _);
|
|
||||||
}
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
|
|
||||||
if should_recognize {
|
|
||||||
if self.ivars().pinch_gesture_recognizer.borrow().is_none() {
|
|
||||||
let pinch: Id<UIPinchGestureRecognizer> = unsafe {
|
|
||||||
msg_send_id![UIPinchGestureRecognizer::alloc(), initWithTarget: self, action: sel!(pinchGesture:)]
|
|
||||||
};
|
|
||||||
self.addGestureRecognizer(&pinch);
|
|
||||||
self.ivars().pinch_gesture_recognizer.replace(Some(pinch));
|
|
||||||
}
|
|
||||||
} else if let Some(recognizer) = self.ivars().pinch_gesture_recognizer.take() {
|
|
||||||
self.removeGestureRecognizer(&recognizer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn recognize_doubletap_gesture(&self, should_recognize: bool) {
|
|
||||||
if should_recognize {
|
|
||||||
if self.ivars().doubletap_gesture_recognizer.borrow().is_none() {
|
|
||||||
let tap: Id<UITapGestureRecognizer> = unsafe {
|
|
||||||
msg_send_id![UITapGestureRecognizer::alloc(), initWithTarget: self, action: sel!(doubleTapGesture:)]
|
|
||||||
};
|
|
||||||
tap.setNumberOfTapsRequired(2);
|
|
||||||
tap.setNumberOfTouchesRequired(1);
|
|
||||||
self.addGestureRecognizer(&tap);
|
|
||||||
self.ivars().doubletap_gesture_recognizer.replace(Some(tap));
|
|
||||||
}
|
|
||||||
} else if let Some(recognizer) = self.ivars().doubletap_gesture_recognizer.take() {
|
|
||||||
self.removeGestureRecognizer(&recognizer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn recognize_rotation_gesture(&self, should_recognize: bool) {
|
|
||||||
if should_recognize {
|
|
||||||
if self.ivars().rotation_gesture_recognizer.borrow().is_none() {
|
|
||||||
let rotation: Id<UIRotationGestureRecognizer> = unsafe {
|
|
||||||
msg_send_id![UIRotationGestureRecognizer::alloc(), initWithTarget: self, action: sel!(rotationGesture:)]
|
|
||||||
};
|
|
||||||
self.addGestureRecognizer(&rotation);
|
|
||||||
self.ivars()
|
|
||||||
.rotation_gesture_recognizer
|
|
||||||
.replace(Some(rotation));
|
|
||||||
}
|
|
||||||
} else if let Some(recognizer) = self.ivars().rotation_gesture_recognizer.take() {
|
|
||||||
self.removeGestureRecognizer(&recognizer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_touches(&self, touches: &NSSet<UITouch>) {
|
|
||||||
let window = self.window().unwrap();
|
|
||||||
let mut touch_events = Vec::new();
|
|
||||||
let os_supports_force = app_state::os_capabilities().force_touch;
|
|
||||||
for touch in touches {
|
|
||||||
let logical_location = touch.locationInView(None);
|
|
||||||
let touch_type = touch.type_();
|
|
||||||
let force = if os_supports_force {
|
|
||||||
let trait_collection = unsafe { self.traitCollection() };
|
|
||||||
let touch_capability = trait_collection.forceTouchCapability();
|
|
||||||
// Both the OS _and_ the device need to be checked for force touch support.
|
|
||||||
if touch_capability == UIForceTouchCapability::Available
|
|
||||||
|| touch_type == UITouchType::Pencil
|
|
||||||
{
|
|
||||||
let force = touch.force();
|
|
||||||
let max_possible_force = touch.maximumPossibleForce();
|
|
||||||
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
|
|
||||||
let angle = touch.altitudeAngle();
|
|
||||||
Some(angle as _)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
Some(Force::Calibrated {
|
|
||||||
force: force as _,
|
|
||||||
max_possible_force: max_possible_force as _,
|
|
||||||
altitude_angle,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let touch_id = touch as *const UITouch as u64;
|
|
||||||
let phase = touch.phase();
|
|
||||||
let phase = match phase {
|
|
||||||
UITouchPhase::Began => TouchPhase::Started,
|
|
||||||
UITouchPhase::Moved => TouchPhase::Moved,
|
|
||||||
// 2 is UITouchPhase::Stationary and is not expected here
|
|
||||||
UITouchPhase::Ended => TouchPhase::Ended,
|
|
||||||
UITouchPhase::Cancelled => TouchPhase::Cancelled,
|
|
||||||
_ => panic!("unexpected touch phase: {:?}", phase as i32),
|
|
||||||
};
|
|
||||||
|
|
||||||
let physical_location = {
|
|
||||||
let scale_factor = self.contentScaleFactor();
|
|
||||||
PhysicalPosition::from_logical::<(f64, f64), f64>(
|
|
||||||
(logical_location.x as _, logical_location.y as _),
|
|
||||||
scale_factor as f64,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.id()),
|
|
||||||
event: WindowEvent::Touch(Touch {
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
id: touch_id,
|
|
||||||
location: physical_location,
|
|
||||||
force,
|
|
||||||
phase,
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_events(mtm, touch_events);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,764 +0,0 @@
|
|||||||
#![allow(clippy::unnecessary_cast)]
|
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker};
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::runtime::{AnyObject, NSObject};
|
|
||||||
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass};
|
|
||||||
use tracing::{debug, warn};
|
|
||||||
|
|
||||||
use super::app_state::EventWrapper;
|
|
||||||
use super::uikit::{
|
|
||||||
UIApplication, UIResponder, UIScreen, UIScreenOverscanCompensation, UIViewController, UIWindow,
|
|
||||||
};
|
|
||||||
use super::view::WinitView;
|
|
||||||
use super::view_controller::WinitViewController;
|
|
||||||
use crate::{
|
|
||||||
cursor::Cursor,
|
|
||||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
icon::Icon,
|
|
||||||
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
|
|
||||||
platform_impl::platform::{app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle},
|
|
||||||
window::{
|
|
||||||
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
|
|
||||||
WindowButtons, WindowId as RootWindowId, WindowLevel,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
declare_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct WinitUIWindow;
|
|
||||||
|
|
||||||
unsafe impl ClassType for WinitUIWindow {
|
|
||||||
#[inherits(UIResponder, NSObject)]
|
|
||||||
type Super = UIWindow;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
const NAME: &'static str = "WinitUIWindow";
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeclaredClass for WinitUIWindow {}
|
|
||||||
|
|
||||||
unsafe impl WinitUIWindow {
|
|
||||||
#[method(becomeKeyWindow)]
|
|
||||||
fn become_key_window(&self) {
|
|
||||||
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) {
|
|
||||||
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] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
impl WinitUIWindow {
|
|
||||||
pub(crate) fn new(
|
|
||||||
mtm: MainThreadMarker,
|
|
||||||
window_attributes: &WindowAttributes,
|
|
||||||
frame: CGRect,
|
|
||||||
view_controller: &UIViewController,
|
|
||||||
) -> Id<Self> {
|
|
||||||
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
|
|
||||||
|
|
||||||
this.setRootViewController(Some(view_controller));
|
|
||||||
|
|
||||||
match window_attributes.fullscreen.clone().map(Into::into) {
|
|
||||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
|
||||||
let monitor = video_mode.monitor();
|
|
||||||
let screen = monitor.ui_screen(mtm);
|
|
||||||
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
|
||||||
this.setScreen(screen);
|
|
||||||
}
|
|
||||||
Some(Fullscreen::Borderless(Some(ref monitor))) => {
|
|
||||||
let screen = monitor.ui_screen(mtm);
|
|
||||||
this.setScreen(screen);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn id(&self) -> WindowId {
|
|
||||||
(self as *const Self as usize as u64).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Inner {
|
|
||||||
window: Id<WinitUIWindow>,
|
|
||||||
view_controller: Id<WinitViewController>,
|
|
||||||
view: Id<WinitView>,
|
|
||||||
gl_or_metal_backed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Inner {
|
|
||||||
pub fn set_title(&self, _title: &str) {
|
|
||||||
debug!("`Window::set_title` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_transparent(&self, _transparent: bool) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_visible(&self) -> Option<bool> {
|
|
||||||
warn!("`Window::is_visible` is ignored on iOS");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_redraw(&self) {
|
|
||||||
if self.gl_or_metal_backed {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
|
|
||||||
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
|
|
||||||
// raw or gl/metal for drawing this work is completely avoided.
|
|
||||||
//
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pre_present_notify(&self) {}
|
|
||||||
|
|
||||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
||||||
let safe_area = self.safe_area_screen_space();
|
|
||||||
let position = LogicalPosition {
|
|
||||||
x: safe_area.origin.x as f64,
|
|
||||||
y: safe_area.origin.y as f64,
|
|
||||||
};
|
|
||||||
let scale_factor = self.scale_factor();
|
|
||||||
Ok(position.to_physical(scale_factor))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
||||||
let screen_frame = self.screen_frame();
|
|
||||||
let position = LogicalPosition {
|
|
||||||
x: screen_frame.origin.x as f64,
|
|
||||||
y: screen_frame.origin.y as f64,
|
|
||||||
};
|
|
||||||
let scale_factor = self.scale_factor();
|
|
||||||
Ok(position.to_physical(scale_factor))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_outer_position(&self, physical_position: Position) {
|
|
||||||
let scale_factor = self.scale_factor();
|
|
||||||
let position = physical_position.to_logical::<f64>(scale_factor);
|
|
||||||
let screen_frame = self.screen_frame();
|
|
||||||
let new_screen_frame = CGRect {
|
|
||||||
origin: CGPoint {
|
|
||||||
x: position.x as _,
|
|
||||||
y: position.y as _,
|
|
||||||
},
|
|
||||||
size: screen_frame.size,
|
|
||||||
};
|
|
||||||
let bounds = self.rect_from_screen_space(new_screen_frame);
|
|
||||||
self.window.setBounds(bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
|
||||||
let scale_factor = self.scale_factor();
|
|
||||||
let safe_area = self.safe_area_screen_space();
|
|
||||||
let size = LogicalSize {
|
|
||||||
width: safe_area.size.width as f64,
|
|
||||||
height: safe_area.size.height as f64,
|
|
||||||
};
|
|
||||||
size.to_physical(scale_factor)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
|
||||||
let scale_factor = self.scale_factor();
|
|
||||||
let screen_frame = self.screen_frame();
|
|
||||||
let size = LogicalSize {
|
|
||||||
width: screen_frame.size.width as f64,
|
|
||||||
height: screen_frame.size.height as f64,
|
|
||||||
};
|
|
||||||
size.to_physical(scale_factor)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
|
|
||||||
Some(self.inner_size())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_min_inner_size(&self, _dimensions: Option<Size>) {
|
|
||||||
warn!("`Window::set_min_inner_size` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_max_inner_size(&self, _dimensions: Option<Size>) {
|
|
||||||
warn!("`Window::set_max_inner_size` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_resize_increments(&self, _increments: Option<Size>) {
|
|
||||||
warn!("`Window::set_resize_increments` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_resizable(&self, _resizable: bool) {
|
|
||||||
warn!("`Window::set_resizable` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_resizable(&self) -> bool {
|
|
||||||
warn!("`Window::is_resizable` is ignored on iOS");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {
|
|
||||||
warn!("`Window::set_enabled_buttons` is ignored on iOS");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn enabled_buttons(&self) -> WindowButtons {
|
|
||||||
warn!("`Window::enabled_buttons` is ignored on iOS");
|
|
||||||
WindowButtons::all()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
self.view.contentScaleFactor() as _
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor(&self, _cursor: Cursor) {
|
|
||||||
debug!("`Window::set_cursor` ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
|
|
||||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), ExternalError> {
|
|
||||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_visible(&self, _visible: bool) {
|
|
||||||
debug!("`Window::set_cursor_visible` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
|
||||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
|
|
||||||
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()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_minimized(&self, _minimized: bool) {
|
|
||||||
warn!("`Window::set_minimized` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_minimized(&self) -> Option<bool> {
|
|
||||||
warn!("`Window::is_minimized` is ignored on iOS");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_maximized(&self, _maximized: bool) {
|
|
||||||
warn!("`Window::set_maximized` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_maximized(&self) -> bool {
|
|
||||||
warn!("`Window::is_maximized` is ignored on iOS");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
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(mtm);
|
|
||||||
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
|
||||||
uiscreen.clone()
|
|
||||||
}
|
|
||||||
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(),
|
|
||||||
Some(Fullscreen::Borderless(None)) => {
|
|
||||||
self.current_monitor_inner().ui_screen(mtm).clone()
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
warn!("`Window::set_fullscreen(None)` ignored on iOS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// this is pretty slow on iOS, so avoid doing it if we can
|
|
||||||
let current = self.window.screen();
|
|
||||||
if uiscreen != current {
|
|
||||||
self.window.setScreen(&uiscreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
let bounds = uiscreen.bounds();
|
|
||||||
self.window.setFrame(bounds);
|
|
||||||
|
|
||||||
// For external displays, we must disable overscan compensation or
|
|
||||||
// the displayed image will have giant black bars surrounding it on
|
|
||||||
// each side
|
|
||||||
uiscreen.setOverscanCompensation(UIScreenOverscanCompensation::None);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
let monitor = self.current_monitor_inner();
|
|
||||||
let uiscreen = monitor.ui_screen(mtm);
|
|
||||||
let screen_space_bounds = self.screen_frame();
|
|
||||||
let screen_bounds = uiscreen.bounds();
|
|
||||||
|
|
||||||
// TODO: track fullscreen instead of relying on brittle float comparisons
|
|
||||||
if screen_space_bounds.origin.x == screen_bounds.origin.x
|
|
||||||
&& screen_space_bounds.origin.y == screen_bounds.origin.y
|
|
||||||
&& screen_space_bounds.size.width == screen_bounds.size.width
|
|
||||||
&& screen_space_bounds.size.height == screen_bounds.size.height
|
|
||||||
{
|
|
||||||
Some(Fullscreen::Borderless(Some(monitor)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_decorations(&self, _decorations: bool) {}
|
|
||||||
|
|
||||||
pub fn is_decorated(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_window_level(&self, _level: WindowLevel) {
|
|
||||||
warn!("`Window::set_window_level` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_window_icon(&self, _icon: Option<Icon>) {
|
|
||||||
warn!("`Window::set_window_icon` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {
|
|
||||||
warn!("`Window::set_ime_cursor_area` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_ime_allowed(&self, _allowed: bool) {
|
|
||||||
warn!("`Window::set_ime_allowed` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {
|
|
||||||
warn!("`Window::set_ime_allowed` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focus_window(&self) {
|
|
||||||
warn!("`Window::set_focus` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_user_attention(&self, _request_type: Option<UserAttentionType>) {
|
|
||||||
warn!("`Window::request_user_attention` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow directly accessing the current monitor internally without unwrapping.
|
|
||||||
fn current_monitor_inner(&self) -> MonitorHandle {
|
|
||||||
MonitorHandle::new(self.window.screen())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
|
||||||
Some(self.current_monitor_inner())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
monitor::uiscreens(MainThreadMarker::new().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
|
||||||
Some(MonitorHandle::new(UIScreen::main(
|
|
||||||
MainThreadMarker::new().unwrap(),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> WindowId {
|
|
||||||
self.window.id()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_04")]
|
|
||||||
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
|
|
||||||
let mut window_handle = rwh_04::UiKitHandle::empty();
|
|
||||||
window_handle.ui_window = 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_04::RawWindowHandle::UiKit(window_handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_05")]
|
|
||||||
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
|
||||||
let mut window_handle = rwh_05::UiKitWindowHandle::empty();
|
|
||||||
window_handle.ui_window = Id::as_ptr(&self.window) as _;
|
|
||||||
window_handle.ui_view = Id::as_ptr(&self.view) as _;
|
|
||||||
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
|
|
||||||
rwh_05::RawWindowHandle::UiKit(window_handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_05")]
|
|
||||||
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
|
|
||||||
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_06")]
|
|
||||||
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
|
|
||||||
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 _);
|
|
||||||
rwh_06::RawWindowHandle::UiKit(window_handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn theme(&self) -> Option<Theme> {
|
|
||||||
warn!("`Window::theme` is ignored on iOS");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_content_protected(&self, _protected: bool) {}
|
|
||||||
|
|
||||||
pub fn has_focus(&self) -> bool {
|
|
||||||
self.window.isKeyWindow()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_theme(&self, _theme: Option<Theme>) {
|
|
||||||
warn!("`Window::set_theme` is ignored on iOS");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn title(&self) -> String {
|
|
||||||
warn!("`Window::title` is ignored on iOS");
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_dead_keys(&self) {
|
|
||||||
// Noop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Window {
|
|
||||||
inner: MainThreadBound<Inner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub(crate) fn new(
|
|
||||||
event_loop: &ActiveEventLoop,
|
|
||||||
window_attributes: WindowAttributes,
|
|
||||||
) -> Result<Window, RootOsError> {
|
|
||||||
let mtm = event_loop.mtm;
|
|
||||||
|
|
||||||
if window_attributes.min_inner_size.is_some() {
|
|
||||||
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
|
|
||||||
}
|
|
||||||
if window_attributes.max_inner_size.is_some() {
|
|
||||||
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: transparency, visible
|
|
||||||
|
|
||||||
let main_screen = UIScreen::main(mtm);
|
|
||||||
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
|
|
||||||
let screen = match fullscreen {
|
|
||||||
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
|
|
||||||
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
|
|
||||||
Some(Fullscreen::Borderless(None)) | None => &main_screen,
|
|
||||||
};
|
|
||||||
|
|
||||||
let screen_bounds = screen.bounds();
|
|
||||||
|
|
||||||
let frame = match window_attributes.inner_size {
|
|
||||||
Some(dim) => {
|
|
||||||
let scale_factor = screen.scale();
|
|
||||||
let size = dim.to_logical::<f64>(scale_factor as f64);
|
|
||||||
CGRect {
|
|
||||||
origin: screen_bounds.origin,
|
|
||||||
size: CGSize {
|
|
||||||
width: size.width as _,
|
|
||||||
height: size.height as _,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => screen_bounds,
|
|
||||||
};
|
|
||||||
|
|
||||||
let view = WinitView::new(mtm, &window_attributes, frame);
|
|
||||||
|
|
||||||
let gl_or_metal_backed = unsafe {
|
|
||||||
let layer_class = WinitView::layerClass();
|
|
||||||
let is_metal = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
|
|
||||||
let is_gl = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
|
|
||||||
is_metal || is_gl
|
|
||||||
};
|
|
||||||
|
|
||||||
let view_controller = WinitViewController::new(mtm, &window_attributes, &view);
|
|
||||||
let window = WinitUIWindow::new(mtm, &window_attributes, frame, &view_controller);
|
|
||||||
|
|
||||||
app_state::set_key_window(mtm, &window);
|
|
||||||
|
|
||||||
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
|
|
||||||
// event on window creation if the DPI factor != 1.0
|
|
||||||
let scale_factor = view.contentScaleFactor();
|
|
||||||
let scale_factor = scale_factor as f64;
|
|
||||||
if scale_factor != 1.0 {
|
|
||||||
let bounds = view.bounds();
|
|
||||||
let screen = window.screen();
|
|
||||||
let screen_space = screen.coordinateSpace();
|
|
||||||
let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
|
|
||||||
let size = LogicalSize {
|
|
||||||
width: screen_frame.size.width as f64,
|
|
||||||
height: screen_frame.size.height as f64,
|
|
||||||
};
|
|
||||||
let window_id = RootWindowId(window.id());
|
|
||||||
app_state::handle_nonuser_events(
|
|
||||||
mtm,
|
|
||||||
std::iter::once(EventWrapper::ScaleFactorChanged(
|
|
||||||
app_state::ScaleFactorChanged {
|
|
||||||
window: window.clone(),
|
|
||||||
scale_factor,
|
|
||||||
suggested_size: size.to_physical(scale_factor),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
|
||||||
Event::WindowEvent {
|
|
||||||
window_id,
|
|
||||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
|
||||||
},
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let inner = Inner {
|
|
||||||
window,
|
|
||||||
view_controller,
|
|
||||||
view,
|
|
||||||
gl_or_metal_backed,
|
|
||||||
};
|
|
||||||
Ok(Window {
|
|
||||||
inner: MainThreadBound::new(inner, mtm),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) {
|
|
||||||
// For now, don't actually do queuing, since it may be less predictable
|
|
||||||
self.maybe_wait_on_main(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
|
|
||||||
self.inner.get_on_main(|inner| f(inner))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_06")]
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn raw_window_handle_rwh_06(
|
|
||||||
&self,
|
|
||||||
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
|
||||||
if let Some(mtm) = MainThreadMarker::new() {
|
|
||||||
Ok(self.inner.get(mtm).raw_window_handle_rwh_06())
|
|
||||||
} else {
|
|
||||||
Err(rwh_06::HandleError::Unavailable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rwh_06")]
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn raw_display_handle_rwh_06(
|
|
||||||
&self,
|
|
||||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
|
||||||
Ok(rwh_06::RawDisplayHandle::UiKit(
|
|
||||||
rwh_06::UiKitDisplayHandle::new(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowExtIOS
|
|
||||||
impl Inner {
|
|
||||||
pub fn set_scale_factor(&self, scale_factor: f64) {
|
|
||||||
assert!(
|
|
||||||
dpi::validate_scale_factor(scale_factor),
|
|
||||||
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
|
|
||||||
);
|
|
||||||
let scale_factor = scale_factor as CGFloat;
|
|
||||||
self.view.setContentScaleFactor(scale_factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
|
||||||
self.view_controller.set_supported_interface_orientations(
|
|
||||||
MainThreadMarker::new().unwrap(),
|
|
||||||
valid_orientations,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
|
||||||
self.view_controller
|
|
||||||
.set_prefers_home_indicator_auto_hidden(hidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
|
||||||
self.view_controller
|
|
||||||
.set_preferred_screen_edges_deferring_system_gestures(edges);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recognize_pinch_gesture(&self, should_recognize: bool) {
|
|
||||||
self.view.recognize_pinch_gesture(should_recognize);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recognize_doubletap_gesture(&self, should_recognize: bool) {
|
|
||||||
self.view.recognize_doubletap_gesture(should_recognize);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recognize_rotation_gesture(&self, should_recognize: bool) {
|
|
||||||
self.view.recognize_rotation_gesture(should_recognize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Inner {
|
|
||||||
fn screen_frame(&self) -> CGRect {
|
|
||||||
self.rect_to_screen_space(self.window.bounds())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
|
|
||||||
let screen_space = self.window.screen().coordinateSpace();
|
|
||||||
self.window
|
|
||||||
.convertRect_toCoordinateSpace(rect, &screen_space)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
|
|
||||||
let screen_space = self.window.screen().coordinateSpace();
|
|
||||||
self.window
|
|
||||||
.convertRect_fromCoordinateSpace(rect, &screen_space)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn safe_area_screen_space(&self) -> CGRect {
|
|
||||||
let bounds = self.window.bounds();
|
|
||||||
if app_state::os_capabilities().safe_area {
|
|
||||||
let safe_area = self.window.safeAreaInsets();
|
|
||||||
let safe_bounds = CGRect {
|
|
||||||
origin: CGPoint {
|
|
||||||
x: bounds.origin.x + safe_area.left,
|
|
||||||
y: bounds.origin.y + safe_area.top,
|
|
||||||
},
|
|
||||||
size: CGSize {
|
|
||||||
width: bounds.size.width - safe_area.left - safe_area.right,
|
|
||||||
height: bounds.size.height - safe_area.top - safe_area.bottom,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
self.rect_to_screen_space(safe_bounds)
|
|
||||||
} else {
|
|
||||||
let screen_frame = self.rect_to_screen_space(bounds);
|
|
||||||
let status_bar_frame = {
|
|
||||||
let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect(
|
|
||||||
"`Window::get_inner_position` cannot be called before `EventLoop::run_app` on iOS",
|
|
||||||
);
|
|
||||||
app.statusBarFrame()
|
|
||||||
};
|
|
||||||
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
|
|
||||||
(screen_frame.origin.y, screen_frame.size.height)
|
|
||||||
} else {
|
|
||||||
let y = status_bar_frame.size.height;
|
|
||||||
let height = screen_frame.size.height
|
|
||||||
- (status_bar_frame.size.height - screen_frame.origin.y);
|
|
||||||
(y, height)
|
|
||||||
};
|
|
||||||
CGRect {
|
|
||||||
origin: CGPoint {
|
|
||||||
x: screen_frame.origin.x,
|
|
||||||
y,
|
|
||||||
},
|
|
||||||
size: CGSize {
|
|
||||||
width: screen_frame.size.width,
|
|
||||||
height,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct WindowId {
|
|
||||||
window: *mut WinitUIWindow,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowId {
|
|
||||||
pub const unsafe fn dummy() -> Self {
|
|
||||||
WindowId {
|
|
||||||
window: std::ptr::null_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<WindowId> for u64 {
|
|
||||||
fn from(window_id: WindowId) -> Self {
|
|
||||||
window_id.window as u64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u64> for WindowId {
|
|
||||||
fn from(raw_id: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
window: raw_id as _,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for WindowId {}
|
|
||||||
unsafe impl Sync for WindowId {}
|
|
||||||
|
|
||||||
impl From<&AnyObject> for WindowId {
|
|
||||||
fn from(window: &AnyObject) -> WindowId {
|
|
||||||
WindowId {
|
|
||||||
window: window as *const _ as _,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct PlatformSpecificWindowAttributes {
|
|
||||||
pub scale_factor: Option<f64>,
|
|
||||||
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 +0,0 @@
|
|||||||
pub mod xkb;
|
|
||||||
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