mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
572 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49bcec1d27 | ||
|
|
878c179761 | ||
|
|
bc19c04339 | ||
|
|
c7a33f926b | ||
|
|
3c38afdb47 | ||
|
|
b8828105cf | ||
|
|
007b195a5e | ||
|
|
b4c6cdf9a3 | ||
|
|
26775fa0b6 | ||
|
|
114fe9d502 | ||
|
|
54bc41f68b | ||
|
|
47ff8d61d1 | ||
|
|
849b8f5dce | ||
|
|
aabe42d252 | ||
|
|
78a62ec547 | ||
|
|
6dae994bb4 | ||
|
|
4c4d0916fd | ||
|
|
d5609729cc | ||
|
|
1f24a09570 | ||
|
|
a8e777a5df | ||
|
|
0bc58f695b | ||
|
|
28023d9f5b | ||
|
|
c2aed1979d | ||
|
|
7e04273719 | ||
|
|
0683bdcd42 | ||
|
|
29ab0bb629 | ||
|
|
7a9c17a520 | ||
|
|
b208daa271 | ||
|
|
e85a80dd65 | ||
|
|
b1d8ce24e9 | ||
|
|
098fd5d602 | ||
|
|
2f27f64cdb | ||
|
|
cbb60d29a2 | ||
|
|
e707052f66 | ||
|
|
71bd6e73ca | ||
|
|
b8326f6452 | ||
|
|
ece2e70a53 | ||
|
|
2b14ec23d5 | ||
|
|
9999f53329 | ||
|
|
522a6e3298 | ||
|
|
76d0dd7ec3 | ||
|
|
d1073dcecb | ||
|
|
e88e8bc194 | ||
|
|
bc29931434 | ||
|
|
505f312d5f | ||
|
|
f0093d3c54 | ||
|
|
83b60beba6 | ||
|
|
5f52d7c9d0 | ||
|
|
a1b65f7080 | ||
|
|
96df858961 | ||
|
|
4eddd1e5bc | ||
|
|
28f0eb598d | ||
|
|
c1eb7f9629 | ||
|
|
2f8aa5c52a | ||
|
|
22dcc19898 | ||
|
|
e295104199 | ||
|
|
66fe69edd9 | ||
|
|
fd946feac4 | ||
|
|
8856b6ecb7 | ||
|
|
0ae78db6cb | ||
|
|
3e3bb8a8f1 | ||
|
|
e48262a797 | ||
|
|
d934f94704 | ||
|
|
1fe4a7a4ea | ||
|
|
9daa0738a9 | ||
|
|
ad7d4939a8 | ||
|
|
c4d07952cb | ||
|
|
dc302b0db4 | ||
|
|
a6d180cefb | ||
|
|
1ddceeb063 | ||
|
|
633d0deeae | ||
|
|
9e3844ddd9 | ||
|
|
4b618bd6a6 | ||
|
|
09c4ed0694 | ||
|
|
d15eb04f9e | ||
|
|
02ac7456e4 | ||
|
|
6b0875728c | ||
|
|
6a330a2894 | ||
|
|
627a127f1b | ||
|
|
ec1ae68cfc | ||
|
|
3aa3880e69 | ||
|
|
a1b8d265d0 | ||
|
|
9b122c3804 | ||
|
|
28b82fb9aa | ||
|
|
7753bbba94 | ||
|
|
ac69a9c0dc | ||
|
|
d29f7f34aa | ||
|
|
85ea3f1d5d | ||
|
|
55166da437 | ||
|
|
777d9edeaa | ||
|
|
28a20aec10 | ||
|
|
3a1e694c2f | ||
|
|
b16042a047 | ||
|
|
cbf61e5cb9 | ||
|
|
077ee4d851 | ||
|
|
7b43b0bc94 | ||
|
|
6bb7db7c11 | ||
|
|
6ffd78767f | ||
|
|
f379d069b9 | ||
|
|
2da24089de | ||
|
|
57f29aa6d7 | ||
|
|
028d3ec16d | ||
|
|
d1c6506865 | ||
|
|
c0b46a03b5 | ||
|
|
114c18e70d | ||
|
|
7367b8be6c | ||
|
|
dd768fe655 | ||
|
|
d9bda3e985 | ||
|
|
fa7a3025ec | ||
|
|
e4451d6786 | ||
|
|
468b6b83ec | ||
|
|
8a3a32f286 | ||
|
|
20e81695ca | ||
|
|
027c52171d | ||
|
|
cc206d31b7 | ||
|
|
5d99316c96 | ||
|
|
d59eec4633 | ||
|
|
25e018d1ce | ||
|
|
25123bed23 | ||
|
|
a8d6db0fc1 | ||
|
|
8a9a9cd92d | ||
|
|
530ff5420b | ||
|
|
133b11fa6d | ||
|
|
5b489284e4 | ||
|
|
cdc32eb817 | ||
|
|
eb38ff453a | ||
|
|
8eb7853a1a | ||
|
|
0c151f9fb3 | ||
|
|
c10c820311 | ||
|
|
82889e2367 | ||
|
|
92741aa4ec | ||
|
|
2f352ca5cf | ||
|
|
38c8cb9f4a | ||
|
|
73248bdced | ||
|
|
01203b247b | ||
|
|
3e1d169160 | ||
|
|
c1b93fc3d0 | ||
|
|
1f81e5c872 | ||
|
|
e5291c9e28 | ||
|
|
35505a3114 | ||
|
|
830d47a5f7 | ||
|
|
1a514dff38 | ||
|
|
2888d5c6cf | ||
|
|
400f75a2b3 | ||
|
|
07bdd3e218 | ||
|
|
35a11ae24f | ||
|
|
3d28283a81 | ||
|
|
aec5a9fa09 | ||
|
|
0f94f62025 | ||
|
|
a95ebc5ee6 | ||
|
|
a70ac1531e | ||
|
|
b6e8dd0d8a | ||
|
|
af80ce842d | ||
|
|
08bae037f0 | ||
|
|
cd39327ea2 | ||
|
|
9828f368d6 | ||
|
|
1ed15c7ec7 | ||
|
|
c66784995d | ||
|
|
dba21c06ed | ||
|
|
72fc6a74ec | ||
|
|
f916311744 | ||
|
|
05a1f4280c | ||
|
|
6608a0241d | ||
|
|
429bbfade0 | ||
|
|
28e3c35547 | ||
|
|
af3ef52252 | ||
|
|
2b5f9c52a6 | ||
|
|
5631cc2528 | ||
|
|
de33a92a1b | ||
|
|
1c6353aa3a | ||
|
|
08cb950226 | ||
|
|
765225d918 | ||
|
|
35bc65f6fa | ||
|
|
676268d461 | ||
|
|
34dce8069f | ||
|
|
f62bb33317 | ||
|
|
a557b3cfb6 | ||
|
|
3ff4834bd5 | ||
|
|
bedb889693 | ||
|
|
157ca9cd17 | ||
|
|
6f5e7e170c | ||
|
|
df7571b369 | ||
|
|
d69e41eba8 | ||
|
|
2d41a7d1b0 | ||
|
|
42e0ccfa1c | ||
|
|
55640a91ae | ||
|
|
4f6ca8792c | ||
|
|
5ced36e319 | ||
|
|
237e7ee2e6 | ||
|
|
02f281569d | ||
|
|
ab4d971c5e | ||
|
|
e21df5831e | ||
|
|
18a0119b06 | ||
|
|
34348435fd | ||
|
|
cf3b0f3b70 | ||
|
|
a336e9e959 | ||
|
|
ea93a0130d | ||
|
|
dcd9ddde50 | ||
|
|
86bafdc104 | ||
|
|
6732fa731d | ||
|
|
8cea3e262b | ||
|
|
3e8669ea7f | ||
|
|
7df040f451 | ||
|
|
472eddcc1b | ||
|
|
c0a7900341 | ||
|
|
28a50817af | ||
|
|
2c47c43f47 | ||
|
|
2ef39651eb | ||
|
|
eb20612d77 | ||
|
|
695547f4ca | ||
|
|
d35ee0d580 | ||
|
|
95581ab92f | ||
|
|
3716f13d8e | ||
|
|
c03ef852a4 | ||
|
|
28a5feef28 | ||
|
|
57a53bda74 | ||
|
|
36f4eccb5c | ||
|
|
b6de19e92e | ||
|
|
e87bc3db20 | ||
|
|
a3739d6bad | ||
|
|
068d114740 | ||
|
|
3273c14dea | ||
|
|
206c3c246c | ||
|
|
bfcd85ab15 | ||
|
|
c99bba1655 | ||
|
|
1e7376847b | ||
|
|
b03e589987 | ||
|
|
0b497b62d8 | ||
|
|
f085b7349c | ||
|
|
dd99b3bd73 | ||
|
|
f53683f01f | ||
|
|
7b707e7d75 | ||
|
|
31110be396 | ||
|
|
604016d69d | ||
|
|
7ee9d5639b | ||
|
|
1aab328e2a | ||
|
|
1366dc326a | ||
|
|
8e73287646 | ||
|
|
7eed52a97a | ||
|
|
31ada5a052 | ||
|
|
30b4f8dc9f | ||
|
|
dbdde3d781 | ||
|
|
cf0b8babbd | ||
|
|
c0c22c8ff1 | ||
|
|
73cf10e4f3 | ||
|
|
8a1c5277eb | ||
|
|
1e4c176506 | ||
|
|
3c27e7d88f | ||
|
|
5bc3cf18d9 | ||
|
|
131e67ddc1 | ||
|
|
e5ba79db04 | ||
|
|
f4e9bf51db | ||
|
|
03f9e8fce0 | ||
|
|
4ae9900363 | ||
|
|
454d4190b7 | ||
|
|
a28b60578d | ||
|
|
5a206de620 | ||
|
|
b547531499 | ||
|
|
39e668ffb0 | ||
|
|
bd1ac6cb1e | ||
|
|
8567758156 | ||
|
|
e897d70733 | ||
|
|
fe12996382 | ||
|
|
e8e4d4ce66 | ||
|
|
44af4f4f52 | ||
|
|
7daf146801 | ||
|
|
7b23d190b1 | ||
|
|
17b8310517 | ||
|
|
5ca828d445 | ||
|
|
1ea29b4de0 | ||
|
|
b00cdadb5b | ||
|
|
53e646dabc | ||
|
|
613fafdfdf | ||
|
|
5d0bc5f607 | ||
|
|
ce5cf97e17 | ||
|
|
3ee59696e5 | ||
|
|
f5c624bcd6 | ||
|
|
026b331ba5 | ||
|
|
93c36ccf78 | ||
|
|
c1f314ccdc | ||
|
|
53a89f28a0 | ||
|
|
f874d76289 | ||
|
|
76645f3b5a | ||
|
|
28775be115 | ||
|
|
7d3ff3d2d9 | ||
|
|
4a5d639d74 | ||
|
|
74a7cf55ea | ||
|
|
9393b14b01 | ||
|
|
f8bd671073 | ||
|
|
2af753f307 | ||
|
|
5cc84f32db | ||
|
|
e89674d337 | ||
|
|
de120280e3 | ||
|
|
7f2ba0ee3e | ||
|
|
5bf303fd26 | ||
|
|
e37e46b155 | ||
|
|
b8192ef6f6 | ||
|
|
23354cf1a5 | ||
|
|
dd38fab2f3 | ||
|
|
ac08601b40 | ||
|
|
ea73dac753 | ||
|
|
2a35646520 | ||
|
|
bb285984da | ||
|
|
34db2d7d4c | ||
|
|
0e20973bdb | ||
|
|
29e2481597 | ||
|
|
1596cc5d9e | ||
|
|
8f66d96915 | ||
|
|
8ad078b964 | ||
|
|
d5368d7979 | ||
|
|
9c5657b86c | ||
|
|
b79089ea57 | ||
|
|
3555de114a | ||
|
|
c5703eb00a | ||
|
|
dbe6a1bcdf | ||
|
|
eea9530f38 | ||
|
|
a195ce8146 | ||
|
|
a0f280e71f | ||
|
|
cf28751ae3 | ||
|
|
9dd15d00d8 | ||
|
|
2442305bb7 | ||
|
|
063648368d | ||
|
|
918b2efce7 | ||
|
|
2467a997f4 | ||
|
|
f457c6a0b8 | ||
|
|
1193cada46 | ||
|
|
b0e09b8ffe | ||
|
|
8d6e8bb8d1 | ||
|
|
e2c84725de | ||
|
|
b571362bf1 | ||
|
|
b1b5aefc4b | ||
|
|
06244dd492 | ||
|
|
de2f0740f7 | ||
|
|
c56a66cb90 | ||
|
|
c1329ff156 | ||
|
|
72509b5b42 | ||
|
|
c35fdc8d61 | ||
|
|
5d31f73302 | ||
|
|
7de1261555 | ||
|
|
91a511ba8c | ||
|
|
94f6294c0a | ||
|
|
77cd3adb01 | ||
|
|
7dabad4d71 | ||
|
|
54b4074369 | ||
|
|
e4d8e22846 | ||
|
|
403dcc02f4 | ||
|
|
64be6e5c5e | ||
|
|
c661006683 | ||
|
|
f879bca21c | ||
|
|
182beb4f8b | ||
|
|
2690306f4a | ||
|
|
b59e3c670b | ||
|
|
2e0bbc091f | ||
|
|
91f05e940f | ||
|
|
db794b976c | ||
|
|
9a11f90a02 | ||
|
|
412516159f | ||
|
|
65587ef43a | ||
|
|
cf713bef31 | ||
|
|
ea5c21950c | ||
|
|
47b5dfa034 | ||
|
|
2b89ddec15 | ||
|
|
f256ff7d58 | ||
|
|
07356b9634 | ||
|
|
f2b6ef2edd | ||
|
|
1409f83fb9 | ||
|
|
37dadab745 | ||
|
|
0eefa3ba42 | ||
|
|
08f8f89702 | ||
|
|
0df436901a | ||
|
|
ae63fbdbbb | ||
|
|
4f29618c73 | ||
|
|
76660f3621 | ||
|
|
3a7350cbb9 | ||
|
|
e5aa906b01 | ||
|
|
4b0162a013 | ||
|
|
5be88e79b0 | ||
|
|
93502e0cda | ||
|
|
8d6a857ba5 | ||
|
|
6ff1370035 | ||
|
|
d5391686ae | ||
|
|
70c7382a09 | ||
|
|
9f801cf79e | ||
|
|
062e0e52ee | ||
|
|
fa99b9ff5a | ||
|
|
8f6e80917f | ||
|
|
e087ebd1c7 | ||
|
|
0d3e75d6b0 | ||
|
|
873f7bcec7 | ||
|
|
e579a03035 | ||
|
|
94f998af0a | ||
|
|
fe5e300062 | ||
|
|
2253565db5 | ||
|
|
2ead1c1c59 | ||
|
|
746e99c958 | ||
|
|
47194b5f3c | ||
|
|
4515b77aa5 | ||
|
|
20b09c4514 | ||
|
|
9874181ccd | ||
|
|
cb93554938 | ||
|
|
6b7bd32c8e | ||
|
|
17a240cd43 | ||
|
|
fc481b6d6d | ||
|
|
09182dc093 | ||
|
|
b682c3dfb5 | ||
|
|
ab0a34012f | ||
|
|
f000b82d74 | ||
|
|
dad8de82fa | ||
|
|
3fea477bfd | ||
|
|
f7d7acb3c5 | ||
|
|
9e25561edf | ||
|
|
7c6bdcc459 | ||
|
|
b09629f1d4 | ||
|
|
85446d81f3 | ||
|
|
96786bbb87 | ||
|
|
a5166baba2 | ||
|
|
d1deba8620 | ||
|
|
aaee72422a | ||
|
|
3dd0e31cc4 | ||
|
|
283a8dec37 | ||
|
|
37d354cf7f | ||
|
|
f698d451df | ||
|
|
c088f8bd03 | ||
|
|
3cd40ef655 | ||
|
|
6513351e0c | ||
|
|
f44e98ddc9 | ||
|
|
fd4db4000c | ||
|
|
9602716ed2 | ||
|
|
7be1d16263 | ||
|
|
c91dfdd6fe | ||
|
|
8e733543cd | ||
|
|
26e37590e8 | ||
|
|
ddf133dd66 | ||
|
|
4584e7629a | ||
|
|
139686ddce | ||
|
|
5a0b4dba47 | ||
|
|
33c8aa660f | ||
|
|
cb76dcae2a | ||
|
|
d622de4797 | ||
|
|
9ae75c0c03 | ||
|
|
45a4281413 | ||
|
|
bfbcab3a01 | ||
|
|
4b4c73cee4 | ||
|
|
fd349f1822 | ||
|
|
cb0a085968 | ||
|
|
aabf0e13b7 | ||
|
|
92873b06ed | ||
|
|
04ca2cf9f4 | ||
|
|
b049a4dc66 | ||
|
|
5be52c9753 | ||
|
|
3c59283b3f | ||
|
|
3ba808e3c6 | ||
|
|
df5d66b5e8 | ||
|
|
7fe90e6c80 | ||
|
|
8dcd514393 | ||
|
|
2c3e420f82 | ||
|
|
917db35a84 | ||
|
|
dd52364d33 | ||
|
|
30aa5a5057 | ||
|
|
a46fcaee31 | ||
|
|
52e2748869 | ||
|
|
d2d127a4c4 | ||
|
|
0fca8e8cb5 | ||
|
|
6bec912961 | ||
|
|
214e157e5d | ||
|
|
da1d479e55 | ||
|
|
062bb0cef2 | ||
|
|
c744b016ce | ||
|
|
f486845f7f | ||
|
|
7baa96c5c7 | ||
|
|
ea07ec1fda | ||
|
|
26b70e457b | ||
|
|
5d5fcb3911 | ||
|
|
50008dff3d | ||
|
|
808638fee3 | ||
|
|
b0e3865562 | ||
|
|
bc03ffb317 | ||
|
|
1edbca1775 | ||
|
|
5a0bc016e7 | ||
|
|
bb66b7f28e | ||
|
|
0331491b2b | ||
|
|
54a782c8ae | ||
|
|
a70bc20829 | ||
|
|
102ed3b800 | ||
|
|
c8e339fe6d | ||
|
|
e4e53fe315 | ||
|
|
1c795c3f1c | ||
|
|
b2b740fed7 | ||
|
|
09550397d7 | ||
|
|
a32f7f2ec5 | ||
|
|
e8e9fa2418 | ||
|
|
1a119bdfe9 | ||
|
|
21ff2e0ffc | ||
|
|
df9b23c96a | ||
|
|
4c117aa282 | ||
|
|
88427262a6 | ||
|
|
72b24a9348 | ||
|
|
01cb8e59e3 | ||
|
|
3910326709 | ||
|
|
7ee46d80e6 | ||
|
|
0cb5450999 | ||
|
|
8c78013257 | ||
|
|
bd944898f0 | ||
|
|
c1ef1acfc0 | ||
|
|
040d3f5d8b | ||
|
|
ec393e4a90 | ||
|
|
1703d0417a | ||
|
|
fad72c0441 | ||
|
|
2f7321a076 | ||
|
|
85ee422acd | ||
|
|
089816d9ba | ||
|
|
c873c2db15 | ||
|
|
047c67baf3 | ||
|
|
8f394f117b | ||
|
|
fb7528c239 | ||
|
|
042f5fe4b3 | ||
|
|
289fb47a34 | ||
|
|
38bc6babb7 | ||
|
|
e7a8efcfa0 | ||
|
|
1b74822cfc | ||
|
|
f083dae328 | ||
|
|
23c384bd30 | ||
|
|
ced1616e51 | ||
|
|
233ac4aed2 | ||
|
|
be5a2b0e87 | ||
|
|
2b4b64f499 | ||
|
|
262490d074 | ||
|
|
8891cfd85e | ||
|
|
2cc8fa1eac | ||
|
|
79aebf06dc | ||
|
|
19dd961752 | ||
|
|
bf413ecb83 | ||
|
|
fd1a3eda1c | ||
|
|
0e2488db32 | ||
|
|
58a00bffbb | ||
|
|
bbfe57400d | ||
|
|
4372f6fdac | ||
|
|
30f798b246 | ||
|
|
282770f11a | ||
|
|
17373a4e91 | ||
|
|
a34147b602 | ||
|
|
cebd15bfd1 | ||
|
|
f51f7c0ca8 | ||
|
|
f6d26df64d | ||
|
|
fddfb2e2d6 | ||
|
|
dec728cfa2 | ||
|
|
8440091a4e | ||
|
|
2464a135b3 | ||
|
|
87fa120ebb | ||
|
|
d86f53a02c | ||
|
|
15a4fec3d9 | ||
|
|
1819be1173 | ||
|
|
ffa9b51d27 | ||
|
|
b4a8c08f43 | ||
|
|
741bcc4672 | ||
|
|
e48f1fc5f1 | ||
|
|
374f131f1e | ||
|
|
363261077f | ||
|
|
102dd07456 | ||
|
|
1e97103094 | ||
|
|
cc8907b956 | ||
|
|
c4b92ebd45 | ||
|
|
fee874b5b7 | ||
|
|
eba888207e | ||
|
|
ea28791da6 | ||
|
|
fe2d37fcdc | ||
|
|
3407a8dd78 | ||
|
|
5761fb6b30 | ||
|
|
7aeb2c083b | ||
|
|
8f47fdbe67 | ||
|
|
2ea42b3947 | ||
|
|
7510b95d8c |
@@ -1,56 +0,0 @@
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
|
||||
android-test:
|
||||
working_directory: ~/winit
|
||||
docker:
|
||||
- image: tomaka/cargo-apk
|
||||
steps:
|
||||
- run: apt-get -qq update && apt-get install -y git
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: android-test-cache-{{ checksum "Cargo.toml" }}
|
||||
- run: cargo apk build --example window
|
||||
- save_cache:
|
||||
key: android-test-cache-{{ checksum "Cargo.toml" }}
|
||||
paths:
|
||||
- target
|
||||
|
||||
asmjs-test:
|
||||
working_directory: ~/winit
|
||||
docker:
|
||||
- image: tomaka/rustc-emscripten
|
||||
steps:
|
||||
- run: apt-get -qq update && apt-get install -y git
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: asmjs-test-cache-{{ checksum "Cargo.toml" }}
|
||||
- run: cargo build --example window --target asmjs-unknown-emscripten
|
||||
- save_cache:
|
||||
key: asmjs-test-cache-{{ checksum "Cargo.toml" }}
|
||||
paths:
|
||||
- target
|
||||
|
||||
wasm-test:
|
||||
working_directory: ~/winit
|
||||
docker:
|
||||
- image: tomaka/rustc-emscripten
|
||||
steps:
|
||||
- run: apt-get -qq update && apt-get install -y git
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: wasm-test-cache-{{ checksum "Cargo.toml" }}
|
||||
- run: cargo build --example window --target wasm32-unknown-emscripten
|
||||
- save_cache:
|
||||
key: wasm-test-cache-{{ checksum "Cargo.toml" }}
|
||||
paths:
|
||||
- target
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-test-and-deploy:
|
||||
jobs:
|
||||
- android-test
|
||||
- asmjs-test
|
||||
- wasm-test
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -20,3 +20,5 @@
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
/CHANGELOG.md merge=union
|
||||
|
||||
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
- [ ] Tested on all platforms changed
|
||||
- [ ] Compilation warnings were addressed
|
||||
- [ ] `cargo fmt` has been run on this branch
|
||||
- [ ] `cargo doc` builds successfully
|
||||
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
|
||||
- [ ] 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
|
||||
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented
|
||||
116
.github/workflows/ci.yml
vendored
Normal file
116
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**.toml'
|
||||
- '.github/workflows/ci.yml'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**.toml'
|
||||
- '.github/workflows/ci.yml'
|
||||
|
||||
jobs:
|
||||
Check_Formatting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: stable
|
||||
components: rustfmt
|
||||
- name: Check Formatting
|
||||
run: cargo +stable fmt --all -- --check
|
||||
|
||||
Tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust_version: [stable, nightly]
|
||||
platform:
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||
- { target: i686-pc-windows-msvc, os: windows-latest, }
|
||||
- { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
|
||||
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
|
||||
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' }
|
||||
- { target: x86_64-apple-darwin, os: macos-latest, }
|
||||
- { target: x86_64-apple-ios, os: macos-latest, }
|
||||
- { target: aarch64-apple-ios, os: macos-latest, }
|
||||
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
|
||||
# doesn't currently work on Linux.
|
||||
- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, cmd: web }
|
||||
- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, cmd: web }
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-C debuginfo=0"
|
||||
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
|
||||
CMD: ${{ matrix.platform.cmd }}
|
||||
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Used to cache cargo-web
|
||||
- name: Cache cargo folder
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cargo
|
||||
key: ${{ matrix.platform.target }}-cargo-${{ matrix.rust_version }}
|
||||
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
|
||||
targets: ${{ matrix.platform.target }}
|
||||
|
||||
- name: Install GCC Multilib
|
||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||
run: sudo apt-get update && sudo apt-get install gcc-multilib
|
||||
- name: Install cargo-apk
|
||||
if: contains(matrix.platform.target, 'android')
|
||||
run: cargo install cargo-apk
|
||||
- name: Install cargo-web
|
||||
continue-on-error: true
|
||||
if: contains(matrix.platform.target, 'wasm32')
|
||||
run: cargo install cargo-web
|
||||
|
||||
- name: Check documentation
|
||||
shell: bash
|
||||
if: matrix.platform.target != 'wasm32-unknown-unknown'
|
||||
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
|
||||
- name: Build tests
|
||||
shell: bash
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
if: (
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32'))
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
|
||||
|
||||
- name: Build with serde enabled
|
||||
shell: bash
|
||||
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
|
||||
|
||||
- name: Build tests with serde enabled
|
||||
shell: bash
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
|
||||
- name: Run tests with serde enabled
|
||||
shell: bash
|
||||
if: (
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32'))
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
|
||||
18
.github/workflows/publish.yml
vendored
Normal file
18
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths: "Cargo.toml"
|
||||
|
||||
jobs:
|
||||
Publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: stable
|
||||
components: rustfmt
|
||||
- name: Publish to crates.io
|
||||
run: cargo publish --token ${{ secrets.cratesio_token }}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1,9 @@
|
||||
Cargo.lock
|
||||
target/
|
||||
rls/
|
||||
.vscode/
|
||||
*~
|
||||
*.wasm
|
||||
*.ts
|
||||
*.js
|
||||
#*#
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "deps/apk-builder"]
|
||||
path = deps/apk-builder
|
||||
url = https://github.com/tomaka/android-rs-glue
|
||||
url = https://github.com/rust-windowing/android-rs-glue
|
||||
|
||||
60
.travis.yml
60
.travis.yml
@@ -1,60 +0,0 @@
|
||||
language: rust
|
||||
|
||||
cache: cargo
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Linux 32bit
|
||||
- env: TARGET=i686-unknown-linux-gnu
|
||||
os: linux
|
||||
rust: nightly
|
||||
addons:
|
||||
apt:
|
||||
# Cross compiler and cross compiled C libraries
|
||||
packages: &i686_packages
|
||||
- gcc-multilib
|
||||
- env: TARGET=i686-unknown-linux-gnu
|
||||
os: linux
|
||||
rust: stable
|
||||
addons:
|
||||
apt:
|
||||
packages: *i686_packages
|
||||
|
||||
# Linux 64bit
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
os: linux
|
||||
rust: nightly
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
os: linux
|
||||
rust: stable
|
||||
|
||||
# macOS
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
rust: nightly
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
rust: stable
|
||||
|
||||
# iOS
|
||||
- env: TARGET=x86_64-apple-ios
|
||||
os: osx
|
||||
rust: nightly
|
||||
- env: TARGET=x86_64-apple-ios
|
||||
os: osx
|
||||
rust: stable
|
||||
|
||||
install:
|
||||
- rustup self update
|
||||
- rustup target add $TARGET; true
|
||||
|
||||
script:
|
||||
- cargo build --target $TARGET --verbose
|
||||
# Running iOS apps on OSX requires the simulator so we skip that for now
|
||||
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --verbose; fi
|
||||
|
||||
after_success:
|
||||
- |
|
||||
[ $TRAVIS_BRANCH = master ] &&
|
||||
[ $TRAVIS_PULL_REQUEST = false ] &&
|
||||
cargo publish --token ${CRATESIO_TOKEN}
|
||||
463
CHANGELOG.md
463
CHANGELOG.md
@@ -1,5 +1,468 @@
|
||||
# Unreleased
|
||||
|
||||
# 0.22.2 (2020-05-16)
|
||||
|
||||
- Added Clone implementation for 'static events.
|
||||
- On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`.
|
||||
- On Windows, fix `WindowBuilder::with_maximized` being ignored.
|
||||
- On Android, minimal platform support.
|
||||
- On iOS, touch positions are now properly converted to physical pixels.
|
||||
- On macOS, updated core-* dependencies and cocoa
|
||||
|
||||
# 0.22.1 (2020-04-16)
|
||||
|
||||
- On X11, fix `ResumeTimeReached` being fired too early.
|
||||
- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame`
|
||||
- On Web, fix a possible panic during event handling
|
||||
- On macOS, fix `EventLoopProxy` leaking memory for every instance.
|
||||
|
||||
# 0.22.0 (2020-03-09)
|
||||
|
||||
- On Windows, fix minor timing issue in wait_until_time_or_msg
|
||||
- On Windows, rework handling of request_redraw() to address panics.
|
||||
- On macOS, fix `set_simple_screen` to remember frame excluding title bar.
|
||||
- On Wayland, fix coordinates in touch events when scale factor isn't 1.
|
||||
- On Wayland, fix color from `close_button_icon_color` not applying.
|
||||
- Ignore locale if unsupported by X11 backend
|
||||
- On Wayland, Add HiDPI cursor support
|
||||
- On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change.
|
||||
- Fix `Event::to_static` returning `None` for user events.
|
||||
- On Wayland, Hide CSD for fullscreen windows.
|
||||
- On Windows, ignore spurious mouse move messages.
|
||||
- **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`.
|
||||
- On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource
|
||||
- Add `BadIcon::OsError` variant for when OS icon functionality fails
|
||||
- On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode
|
||||
- Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
|
||||
- on macOS, fix incorrect ReceivedCharacter events for some key combinations.
|
||||
- **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`.
|
||||
- On macOS, a mouse motion event is now generated before every mouse click.
|
||||
|
||||
# 0.21.0 (2020-02-04)
|
||||
|
||||
- On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows.
|
||||
- On macOS, fix set_minimized(true) works only with decorations.
|
||||
- On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`.
|
||||
- On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
|
||||
- On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates.
|
||||
- On macOS, fix issue where unbundled applications would sometimes open without being focused.
|
||||
- On macOS, fix `run_return` does not return unless it receives a message.
|
||||
- On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop.
|
||||
- On X11, fix deadlock on window state when handling certain window events.
|
||||
- `WindowBuilder` now implements `Default`.
|
||||
- **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends
|
||||
- On Wayland, fix coordinates in mouse events when scale factor isn't 1
|
||||
- On Web, add the ability to provide a custom canvas
|
||||
- **Breaking:** On Wayland, the `WaylandTheme` struct has been replaced with a `Theme` trait, allowing for extra configuration
|
||||
|
||||
# 0.20.0 (2020-01-05)
|
||||
|
||||
- On X11, fix `ModifiersChanged` emitting incorrect modifier change events
|
||||
- **Breaking**: Overhaul how Winit handles DPI:
|
||||
+ Window functions and events now return `PhysicalSize` instead of `LogicalSize`.
|
||||
+ Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types.
|
||||
+ `hidpi_factor` has been renamed to `scale_factor`.
|
||||
+ `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS
|
||||
resizes the window in response to the change.
|
||||
+ On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`.
|
||||
+ `Size` and `Position` types are now generic over their exact pixel type.
|
||||
|
||||
# 0.20.0 Alpha 6 (2020-01-03)
|
||||
|
||||
- On macOS, fix `set_cursor_visible` hides cursor outside of window.
|
||||
- On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size.
|
||||
- On macOS, fix error when `set_fullscreen` is called during fullscreen transition.
|
||||
- On all platforms except mobile and WASM, implement `Window::set_minimized`.
|
||||
- On X11, fix `CursorEntered` event being generated for non-winit windows.
|
||||
- On macOS, fix crash when starting maximized without decorations.
|
||||
- On macOS, fix application not terminating on `run_return`.
|
||||
- On Wayland, fix cursor icon updates on window borders when using CSD.
|
||||
- On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode.
|
||||
- On Windows, theme the title bar according to whether the system theme is "Light" or "Dark".
|
||||
- Added `WindowEvent::ThemeChanged` variant to handle changes to the system theme. Currently only implemented on Windows.
|
||||
- **Breaking**: Changes to the `RedrawRequested` event (#1041):
|
||||
- `RedrawRequested` has been moved from `WindowEvent` to `Event`.
|
||||
- `EventsCleared` has been renamed to `MainEventsCleared`.
|
||||
- `RedrawRequested` is now issued only after `MainEventsCleared`.
|
||||
- `RedrawEventsCleared` is issued after each set of `RedrawRequested` events.
|
||||
- Implement synthetic window focus key events on Windows.
|
||||
- **Breaking**: Change `ModifiersState` to a `bitflags` struct.
|
||||
- On Windows, implement `VirtualKeyCode` translation for `LWin` and `RWin`.
|
||||
- On Windows, fix closing the last opened window causing `DeviceEvent`s to stop getting emitted.
|
||||
- On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior.
|
||||
- Add `DeviceEvent::ModifiersChanged`.
|
||||
- Deprecate `modifiers` fields in other events in favor of `ModifiersChanged`.
|
||||
- On X11, `WINIT_HIDPI_FACTOR` now dominates `Xft.dpi` when picking DPI factor for output.
|
||||
- On X11, add special value `randr` for `WINIT_HIDPI_FACTOR` to make winit use self computed DPI factor instead of the one from `Xft.dpi`.
|
||||
|
||||
# 0.20.0 Alpha 5 (2019-12-09)
|
||||
|
||||
- On macOS, fix application termination on `ControlFlow::Exit`
|
||||
- On Windows, fix missing `ReceivedCharacter` events when Alt is held.
|
||||
- On macOS, stop emitting private corporate characters in `ReceivedCharacter` events.
|
||||
- On X11, fix misreporting DPI factor at startup.
|
||||
- On X11, fix events not being reported when using `run_return`.
|
||||
- On X11, fix key modifiers being incorrectly reported.
|
||||
- On X11, fix window creation hanging when another window is fullscreen.
|
||||
- On Windows, fix focusing unfocused windows when switching from fullscreen to windowed.
|
||||
- On X11, fix reporting incorrect DPI factor when waking from suspend.
|
||||
- Change `EventLoopClosed` to contain the original event.
|
||||
- **Breaking**: Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`,
|
||||
indicating that the event is generated by winit.
|
||||
- On X11, generate synthetic key events for keys held when a window gains or loses focus.
|
||||
- On X11, issue a `CursorMoved` event when a `Touch` event occurs,
|
||||
as X11 implicitly moves the cursor for such events.
|
||||
|
||||
# 0.20.0 Alpha 4 (2019-10-18)
|
||||
|
||||
- Add web support via the 'stdweb' or 'web-sys' features
|
||||
- On Windows, implemented function to get HINSTANCE
|
||||
- On macOS, implement `run_return`.
|
||||
- On iOS, fix inverted parameter in `set_prefers_home_indicator_hidden`.
|
||||
- On X11, performance is improved when rapidly calling `Window::set_cursor_icon`.
|
||||
- On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized.
|
||||
- On Windows, unset `maximized` when manually changing the window's position or size.
|
||||
- On Windows, add touch pressure information for touch events.
|
||||
- On macOS, differentiate between `CursorIcon::Grab` and `CursorIcon::Grabbing`.
|
||||
- On Wayland, fix event processing sometimes stalling when using OpenGL with vsync.
|
||||
- Officially remove the Emscripten backend.
|
||||
- On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`.
|
||||
- On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode.
|
||||
- On iOS, fix panic upon closing the app.
|
||||
- On X11, allow setting mulitple `XWindowType`s.
|
||||
- On iOS, fix null window on initial `HiDpiFactorChanged` event.
|
||||
- On Windows, fix fullscreen window shrinking upon getting restored to a normal window.
|
||||
- On macOS, fix events not being emitted during modal loops, such as when windows are being resized
|
||||
by the user.
|
||||
- On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events.
|
||||
- Always dispatch a `RedrawRequested` event after creating a new window.
|
||||
- On X11, return dummy monitor data to avoid panicking when no monitors exist.
|
||||
- On X11, prevent stealing input focus when creating a new window.
|
||||
Only steal input focus when entering fullscreen mode.
|
||||
- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced
|
||||
- On Wayland, add support for set_cursor_visible and set_cursor_grab.
|
||||
- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced.
|
||||
- Removed `derivative` crate dependency.
|
||||
- On Wayland, add support for set_cursor_icon.
|
||||
- Use `impl Iterator<Item = MonitorHandle>` instead of `AvailableMonitorsIter` consistently.
|
||||
- On macOS, fix fullscreen state being updated after entering fullscreen instead of before,
|
||||
resulting in `Window::fullscreen` returning the old state in `Resized` events instead of
|
||||
reflecting the new fullscreen state
|
||||
- On X11, fix use-after-free during window creation
|
||||
- On Windows, disable monitor change keyboard shortcut while in exclusive fullscreen.
|
||||
- On Windows, ensure that changing a borderless fullscreen window's monitor via keyboard shortcuts keeps the window fullscreen on the new monitor.
|
||||
- Prevent `EventLoop::new` and `EventLoop::with_user_event` from getting called outside the main thread.
|
||||
- This is because some platforms cannot run the event loop outside the main thread. Preventing this
|
||||
reduces the potential for cross-platform compatibility gotchyas.
|
||||
- On Windows and Linux X11/Wayland, add platform-specific functions for creating an `EventLoop` outside the main thread.
|
||||
- On Wayland, drop resize events identical to the current window size.
|
||||
- On Windows, fix window rectangle not getting set correctly on high-DPI systems.
|
||||
|
||||
# 0.20.0 Alpha 3 (2019-08-14)
|
||||
|
||||
- On macOS, drop the run closure on exit.
|
||||
- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates.
|
||||
- On X11, fix delayed events after window redraw.
|
||||
- On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface.
|
||||
- On Windows, screen saver won't start if the window is in fullscreen mode.
|
||||
- Change all occurrences of the `new_user_event` method to `with_user_event`.
|
||||
- On macOS, the dock and the menu bar are now hidden in fullscreen mode.
|
||||
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
|
||||
consists of `Fullscreen::Exclusive(VideoMode)` and
|
||||
`Fullscreen::Borderless(MonitorHandle)` variants.
|
||||
- Adds support for exclusive fullscreen mode.
|
||||
- On iOS, add support for hiding the home indicator.
|
||||
- On iOS, add support for deferring system gestures.
|
||||
- On iOS, fix a crash that occurred while acquiring a monitor's name.
|
||||
- On iOS, fix armv7-apple-ios compile target.
|
||||
- Removed the `T: Clone` requirement from the `Clone` impl of `EventLoopProxy<T>`.
|
||||
- On iOS, disable overscan compensation for external displays (removes black
|
||||
bars surrounding the image).
|
||||
- On Linux, the functions `is_wayland`, `is_x11`, `xlib_xconnection` and `wayland_display` have been moved to a new `EventLoopWindowTargetExtUnix` trait.
|
||||
- On iOS, add `set_prefers_status_bar_hidden` extension function instead of
|
||||
hijacking `set_decorations` for this purpose.
|
||||
- On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`.
|
||||
- On iOS, add touch pressure information for touch events.
|
||||
- Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms.
|
||||
- On macOS, fix the signature of `-[NSView drawRect:]`.
|
||||
- On iOS, fix the behavior of `ControlFlow::Poll`. It wasn't polling if that was the only mode ever used by the application.
|
||||
- On iOS, fix DPI sent out by views on creation was `0.0` - now it gives a reasonable number.
|
||||
- On iOS, RedrawRequested now works for gl/metal backed views.
|
||||
- On iOS, RedrawRequested is generally ordered after EventsCleared.
|
||||
|
||||
# 0.20.0 Alpha 2 (2019-07-09)
|
||||
|
||||
- On X11, non-resizable windows now have maximize explicitly disabled.
|
||||
- On Windows, support paths longer than MAX_PATH (260 characters) in `WindowEvent::DroppedFile`
|
||||
and `WindowEvent::HoveredFile`.
|
||||
- On Mac, implement `DeviceEvent::Button`.
|
||||
- Change `Event::Suspended(true / false)` to `Event::Suspended` and `Event::Resumed`.
|
||||
- On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor.
|
||||
- Revert the use of invisible surfaces in Wayland, which introduced graphical glitches with OpenGL (#835)
|
||||
- On X11, implement `_NET_WM_PING` to allow desktop environment to kill unresponsive programs.
|
||||
- On Windows, when a window is initially invisible, it won't take focus from the existing visible windows.
|
||||
- On Windows, fix multiple calls to `request_redraw` during `EventsCleared` sending multiple `RedrawRequested events.`
|
||||
- On Windows, fix edge case where `RedrawRequested` could be dispatched before input events in event loop iteration.
|
||||
- On Windows, fix timing issue that could cause events to be improperly dispatched after `RedrawRequested` but before `EventsCleared`.
|
||||
- On macOS, drop unused Metal dependency.
|
||||
- On Windows, fix the trail effect happening on transparent decorated windows. Borderless (or un-decorated) windows were not affected.
|
||||
- On Windows, fix `with_maximized` not properly setting window size to entire window.
|
||||
- On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`.
|
||||
|
||||
# 0.20.0 Alpha 1 (2019-06-21)
|
||||
|
||||
- Changes below are considered **breaking**.
|
||||
- Change all occurrences of `EventsLoop` to `EventLoop`.
|
||||
- Previously flat API is now exposed through `event`, `event_loop`, `monitor`, and `window` modules.
|
||||
- `os` module changes:
|
||||
- Renamed to `platform`.
|
||||
- All traits now have platform-specific suffixes.
|
||||
- Exposes new `desktop` module on Windows, Mac, and Linux.
|
||||
- Changes to event loop types:
|
||||
- `EventLoopProxy::wakeup` has been removed in favor of `send_event`.
|
||||
- **Major:** New `run` method drives winit event loop.
|
||||
- Returns `!` to ensure API behaves identically across all supported platforms.
|
||||
- This allows `emscripten` implementation to work without lying about the API.
|
||||
- `ControlFlow`'s variants have been replaced with `Wait`, `WaitUntil(Instant)`, `Poll`, and `Exit`.
|
||||
- Is read after `EventsCleared` is processed.
|
||||
- `Wait` waits until new events are available.
|
||||
- `WaitUntil` waits until either new events are available or the provided time has been reached.
|
||||
- `Poll` instantly resumes the event loop.
|
||||
- `Exit` aborts the event loop.
|
||||
- Takes a closure that implements `'static + FnMut(Event<T>, &EventLoop<T>, &mut ControlFlow)`.
|
||||
- `&EventLoop<T>` is provided to allow new `Window`s to be created.
|
||||
- **Major:** `platform::desktop` module exposes `EventLoopExtDesktop` trait with `run_return` method.
|
||||
- Behaves identically to `run`, but returns control flow to the calling context and can take non-`'static` closures.
|
||||
- `EventLoop`'s `poll_events` and `run_forever` methods have been removed in favor of `run` and `run_return`.
|
||||
- Changes to events:
|
||||
- Remove `Event::Awakened` in favor of `Event::UserEvent(T)`.
|
||||
- Can be sent with `EventLoopProxy::send_event`.
|
||||
- Rename `WindowEvent::Refresh` to `WindowEvent::RedrawRequested`.
|
||||
- `RedrawRequested` can be sent by the user with the `Window::request_redraw` method.
|
||||
- `EventLoop`, `EventLoopProxy`, and `Event` are now generic over `T`, for use in `UserEvent`.
|
||||
- **Major:** Add `NewEvents(StartCause)`, `EventsCleared`, and `LoopDestroyed` variants to `Event`.
|
||||
- `NewEvents` is emitted when new events are ready to be processed by event loop.
|
||||
- `StartCause` describes why new events are available, with `ResumeTimeReached`, `Poll`, `WaitCancelled`, and `Init` (sent once at start of loop).
|
||||
- `EventsCleared` is emitted when all available events have been processed.
|
||||
- Can be used to perform logic that depends on all events being processed (e.g. an iteration of a game loop).
|
||||
- `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit.
|
||||
- Rename `MonitorId` to `MonitorHandle`.
|
||||
- Removed `serde` implementations from `ControlFlow`.
|
||||
- Rename several functions to improve both internal consistency and compliance with Rust API guidelines.
|
||||
- Remove `WindowBuilder::multitouch` field, since it was only implemented on a few platforms. Multitouch is always enabled now.
|
||||
- **Breaking:** On macOS, change `ns` identifiers to use snake_case for consistency with iOS's `ui` identifiers.
|
||||
- Add `MonitorHandle::video_modes` method for retrieving supported video modes for the given monitor.
|
||||
- On Wayland, the window now exists even if nothing has been drawn.
|
||||
- On Windows, fix initial dimensions of a fullscreen window.
|
||||
- On Windows, Fix transparent borderless windows rendering wrong.
|
||||
|
||||
# Version 0.19.1 (2019-04-08)
|
||||
|
||||
- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.
|
||||
- On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus.
|
||||
- On macOS, fix command key event left and right reverse.
|
||||
- On FreeBSD, NetBSD, and OpenBSD, fix build of X11 backend.
|
||||
- On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes
|
||||
- On macOS, the numpad's subtract key has been added to the `Subtract` mapping
|
||||
- On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes
|
||||
- On Windows, fix icon not showing up in corner of window.
|
||||
- On X11, change DPI scaling factor behavior. First, winit tries to read it from "Xft.dpi" XResource, and uses DPI calculation from xrandr dimensions as fallback behavior.
|
||||
|
||||
# Version 0.19.0 (2019-03-06)
|
||||
|
||||
- On X11, we will use the faster `XRRGetScreenResourcesCurrent` function instead of `XRRGetScreenResources` when available.
|
||||
- On macOS, fix keycodes being incorrect when using a non-US keyboard layout.
|
||||
- On Wayland, fix `with_title()` not setting the windows title
|
||||
- On Wayland, add `set_wayland_theme()` to control client decoration color theme
|
||||
- Added serde serialization to `os::unix::XWindowType`.
|
||||
- **Breaking:** Remove the `icon_loading` feature and the associated `image` dependency.
|
||||
- On X11, make event loop thread safe by replacing XNextEvent with select(2) and XCheckIfEvent
|
||||
- On Windows, fix malformed function pointer typecast that could invoke undefined behavior.
|
||||
- Refactored Windows state/flag-setting code.
|
||||
- On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on.
|
||||
- On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area.
|
||||
- On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled.
|
||||
- On Windows, ignore the AltGr key when populating the `ModifersState` type.
|
||||
|
||||
# Version 0.18.1 (2018-12-30)
|
||||
|
||||
- On macOS, fix `Yen` (JIS) so applications receive the event.
|
||||
- On X11 with a tiling WM, fixed high CPU usage when moving windows across monitors.
|
||||
- On X11, fixed panic caused by dropping the window before running the event loop.
|
||||
- on macOS, added `WindowExt::set_simple_fullscreen` which does not require a separate space
|
||||
- Introduce `WindowBuilderExt::with_app_id` to allow setting the application ID on Wayland.
|
||||
- On Windows, catch panics in event loop child thread and forward them to the parent thread. This prevents an invocation of undefined behavior due to unwinding into foreign code.
|
||||
- On Windows, fix issue where resizing or moving window combined with grabbing the cursor would freeze program.
|
||||
- On Windows, fix issue where resizing or moving window would eat `Awakened` events.
|
||||
- On Windows, exiting fullscreen after entering fullscreen with disabled decorations no longer shrinks window.
|
||||
- On X11, fixed a segfault when using virtual monitors with XRandR.
|
||||
- Derive `Ord` and `PartialOrd` for `VirtualKeyCode` enum.
|
||||
- On Windows, fix issue where hovering or dropping a non file item would create a panic.
|
||||
- On Wayland, fix resizing and DPI calculation when a `wl_output` is removed without sending a `leave` event to the `wl_surface`, such as disconnecting a monitor from a laptop.
|
||||
- On Wayland, DPI calculation is handled by smithay-client-toolkit.
|
||||
- On X11, `WindowBuilder::with_min_dimensions` and `WindowBuilder::with_max_dimensions` now correctly account for DPI.
|
||||
- Added support for generating dummy `DeviceId`s and `WindowId`s to better support unit testing.
|
||||
- On macOS, fixed unsoundness in drag-and-drop that could result in drops being rejected.
|
||||
- On macOS, implemented `WindowEvent::Refresh`.
|
||||
- On macOS, all `MouseCursor` variants are now implemented and the cursor will no longer reset after unfocusing.
|
||||
- Removed minimum supported Rust version guarantee.
|
||||
|
||||
# Version 0.18.0 (2018-11-07)
|
||||
|
||||
- **Breaking:** `image` crate upgraded to 0.20. This is exposed as part of the `icon_loading` API.
|
||||
- On Wayland, pointer events will now provide the current modifiers state.
|
||||
- On Wayland, titles will now be displayed in the window header decoration.
|
||||
- On Wayland, key repetition is now ended when keyboard loses focus.
|
||||
- On Wayland, windows will now use more stylish and modern client side decorations.
|
||||
- On Wayland, windows will use server-side decorations when available.
|
||||
- **Breaking:** Added support for F16-F24 keys (variants were added to the `VirtualKeyCode` enum).
|
||||
- Fixed graphical glitches when resizing on Wayland.
|
||||
- On Windows, fix freezes when performing certain actions after a window resize has been triggered. Reintroduces some visual artifacts when resizing.
|
||||
- Updated window manager hints under X11 to v1.5 of [Extended Window Manager Hints](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm140200472629520).
|
||||
- Added `WindowBuilderExt::with_gtk_theme_variant` to X11-specific `WindowBuilder` functions.
|
||||
- Fixed UTF8 handling bug in X11 `set_title` function.
|
||||
- On Windows, `Window::set_cursor` now applies immediately instead of requiring specific events to occur first.
|
||||
- On Windows, the `HoveredFile` and `HoveredFileCancelled` events are now implemented.
|
||||
- On Windows, fix `Window::set_maximized`.
|
||||
- On Windows 10, fix transparency (#260).
|
||||
- On macOS, fix modifiers during key repeat.
|
||||
- Implemented the `Debug` trait for `Window`, `EventsLoop`, `EventsLoopProxy` and `WindowBuilder`.
|
||||
- On X11, now a `Resized` event will always be generated after a DPI change to ensure the window's logical size is consistent with the new DPI.
|
||||
- Added further clarifications to the DPI docs.
|
||||
- On Linux, if neither X11 nor Wayland manage to initialize, the corresponding panic now consists of a single line only.
|
||||
- Add optional `serde` feature with implementations of `Serialize`/`Deserialize` for DPI types and various event types.
|
||||
- Add `PartialEq`, `Eq`, and `Hash` implementations on public types that could have them but were missing them.
|
||||
- On X11, drag-and-drop receiving an unsupported drop type can no longer cause the WM to freeze.
|
||||
- Fix issue whereby the OpenGL context would not appear at startup on macOS Mojave (#1069).
|
||||
- **Breaking:** Removed `From<NSApplicationActivationPolicy>` impl from `ActivationPolicy` on macOS.
|
||||
- On macOS, the application can request the user's attention with `WindowExt::request_user_attention`.
|
||||
|
||||
# Version 0.17.2 (2018-08-19)
|
||||
|
||||
- On macOS, fix `<C-Tab>` so applications receive the event.
|
||||
- On macOS, fix `<Cmd-{key}>` so applications receive the event.
|
||||
- On Wayland, key press events will now be repeated.
|
||||
|
||||
# Version 0.17.1 (2018-08-05)
|
||||
|
||||
- On X11, prevent a compilation failure in release mode for versions of Rust greater than or equal to 1.30.
|
||||
- Fixed deadlock that broke fullscreen mode on Windows.
|
||||
|
||||
# Version 0.17.0 (2018-08-02)
|
||||
|
||||
- Cocoa and core-graphics updates.
|
||||
- Fixed thread-safety issues in several `Window` functions on Windows.
|
||||
- On MacOS, the key state for modifiers key events is now properly set.
|
||||
- On iOS, the view is now set correctly. This makes it possible to render things (instead of being stuck on a black screen), and touch events work again.
|
||||
- Added NetBSD support.
|
||||
- **Breaking:** On iOS, `UIView` is now the default root view. `WindowBuilderExt::with_root_view_class` can be used to set the root view objective-c class to `GLKView` (OpenGLES) or `MTKView` (Metal/MoltenVK).
|
||||
- On iOS, the `UIApplication` is not started until `Window::new` is called.
|
||||
- Fixed thread unsafety with cursor hiding on macOS.
|
||||
- On iOS, fixed the size of the `JmpBuf` type used for `setjmp`/`longjmp` calls. Previously this was a buffer overflow on most architectures.
|
||||
- On Windows, use cached window DPI instead of repeatedly querying the system. This fixes sporadic crashes on Windows 7.
|
||||
|
||||
# Version 0.16.2 (2018-07-07)
|
||||
|
||||
- On Windows, non-resizable windows now have the maximization button disabled. This is consistent with behavior on macOS and popular X11 WMs.
|
||||
- Corrected incorrect `unreachable!` usage when guessing the DPI factor with no detected monitors.
|
||||
|
||||
# Version 0.16.1 (2018-07-02)
|
||||
|
||||
- Added logging through `log`. Logging will become more extensive over time.
|
||||
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This *greatly* cuts back on unsightly auto-resizing that would occur immediately after window creation.
|
||||
- Fixed X11 backend compilation for environments where `c_char` is unsigned.
|
||||
|
||||
# Version 0.16.0 (2018-06-25)
|
||||
|
||||
- Windows additionally has `WindowBuilderExt::with_no_redirection_bitmap`.
|
||||
- **Breaking:** Removed `VirtualKeyCode::LMenu` and `VirtualKeyCode::RMenu`; Windows now generates `VirtualKeyCode::LAlt` and `VirtualKeyCode::RAlt` instead.
|
||||
- On X11, exiting fullscreen no longer leaves the window in the monitor's top left corner.
|
||||
- **Breaking:** `Window::hidpi_factor` has been renamed to `Window::get_hidpi_factor` for better consistency. `WindowEvent::HiDPIFactorChanged` has been renamed to `WindowEvent::HiDpiFactorChanged`. DPI factors are always represented as `f64` instead of `f32` now.
|
||||
- The Windows backend is now DPI aware. `WindowEvent::HiDpiFactorChanged` is implemented, and `MonitorId::get_hidpi_factor` and `Window::hidpi_factor` return accurate values.
|
||||
- Implemented `WindowEvent::HiDpiFactorChanged` on X11.
|
||||
- On macOS, `Window::set_cursor_position` is now relative to the client area.
|
||||
- On macOS, setting the maximum and minimum dimensions now applies to the client area dimensions rather than to the window dimensions.
|
||||
- On iOS, `MonitorId::get_dimensions` has been implemented and both `MonitorId::get_hidpi_factor` and `Window::get_hidpi_factor` return accurate values.
|
||||
- On Emscripten, `MonitorId::get_hidpi_factor` now returns the same value as `Window::get_hidpi_factor` (it previously would always return 1.0).
|
||||
- **Breaking:** The entire API for sizes, positions, etc. has changed. In the majority of cases, winit produces and consumes positions and sizes as `LogicalPosition` and `LogicalSize`, respectively. The notable exception is `MonitorId` methods, which deal in `PhysicalPosition` and `PhysicalSize`. See the documentation for specifics and explanations of the types. Additionally, winit automatically conserves logical size when the DPI factor changes.
|
||||
- **Breaking:** All deprecated methods have been removed. For `Window::platform_display` and `Window::platform_window`, switch to the appropriate platform-specific `WindowExt` methods. For `Window::get_inner_size_points` and `Window::get_inner_size_pixels`, use the `LogicalSize` returned by `Window::get_inner_size` and convert as needed.
|
||||
- HiDPI support for Wayland.
|
||||
- `EventsLoop::get_available_monitors` and `EventsLoop::get_primary_monitor` now have identical counterparts on `Window`, so this information can be acquired without an `EventsLoop` borrow.
|
||||
- `AvailableMonitorsIter` now implements `Debug`.
|
||||
- Fixed quirk on macOS where certain keys would generate characters at twice the normal rate when held down.
|
||||
- On X11, all event loops now share the same `XConnection`.
|
||||
- **Breaking:** `Window::set_cursor_state` and `CursorState` enum removed in favor of the more composable `Window::grab_cursor` and `Window::hide_cursor`. As a result, grabbing the cursor no longer automatically hides it; you must call both methods to retain the old behavior on Windows and macOS. `Cursor::NoneCursor` has been removed, as it's no longer useful.
|
||||
- **Breaking:** `Window::set_cursor_position` now returns `Result<(), String>`, thus allowing for `Box<Error>` conversion via `?`.
|
||||
|
||||
# Version 0.15.1 (2018-06-13)
|
||||
|
||||
- On X11, the `Moved` event is no longer sent when the window is resized without changing position.
|
||||
- `MouseCursor` and `CursorState` now implement `Default`.
|
||||
- `WindowBuilder::with_resizable` implemented for Windows, X11, Wayland, and macOS.
|
||||
- `Window::set_resizable` implemented for Windows, X11, Wayland, and macOS.
|
||||
- On X11, if the monitor's width or height in millimeters is reported as 0, the DPI is now 1.0 instead of +inf.
|
||||
- On X11, the environment variable `WINIT_HIDPI_FACTOR` has been added for overriding DPI factor.
|
||||
- On X11, enabling transparency no longer causes the window contents to flicker when resizing.
|
||||
- On X11, `with_override_redirect` now actually enables override redirect.
|
||||
- macOS now generates `VirtualKeyCode::LAlt` and `VirtualKeyCode::RAlt` instead of `None` for both.
|
||||
- On macOS, `VirtualKeyCode::RWin` and `VirtualKeyCode::LWin` are no longer switched.
|
||||
- On macOS, windows without decorations can once again be resized.
|
||||
- Fixed race conditions when creating an `EventsLoop` on X11, most commonly manifesting as "[xcb] Unknown sequence number while processing queue".
|
||||
- On macOS, `CursorMoved` and `MouseInput` events are only generated if they occurs within the window's client area.
|
||||
- On macOS, resizing the window no longer generates a spurious `MouseInput` event.
|
||||
|
||||
# Version 0.15.0 (2018-05-22)
|
||||
|
||||
- `Icon::to_cardinals` is no longer public, since it was never supposed to be.
|
||||
- Wayland: improve diagnostics if initialization fails
|
||||
- Fix some system event key doesn't work when focused, do not block keyevent forward to system on macOS
|
||||
- On X11, the scroll wheel position is now correctly reset on i3 and other WMs that have the same quirk.
|
||||
- On X11, `Window::get_current_monitor` now reliably returns the correct monitor.
|
||||
- On X11, `Window::hidpi_factor` returns values from XRandR rather than the inaccurate values previously queried from the core protocol.
|
||||
- On X11, the primary monitor is detected correctly even when using versions of XRandR less than 1.5.
|
||||
- `MonitorId` now implements `Debug`.
|
||||
- Fixed bug on macOS where using `with_decorations(false)` would cause `set_decorations(true)` to produce a transparent titlebar with no title.
|
||||
- Implemented `MonitorId::get_position` on macOS.
|
||||
- On macOS, `Window::get_current_monitor` now returns accurate values.
|
||||
- Added `WindowBuilderExt::with_resize_increments` to macOS.
|
||||
- **Breaking:** On X11, `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` now take `u32` values rather than `i32`.
|
||||
- macOS keyboard handling has been overhauled, allowing for the use of dead keys, IME, etc. Right modifier keys are also no longer reported as being left.
|
||||
- Added the `Window::set_ime_spot(x: i32, y: i32)` method, which is implemented on X11 and macOS.
|
||||
- **Breaking**: `os::unix::WindowExt::send_xim_spot(x: i16, y: i16)` no longer exists. Switch to the new `Window::set_ime_spot(x: i32, y: i32)`, which has equivalent functionality.
|
||||
- Fixed detection of `Pause` and `Scroll` keys on Windows.
|
||||
- On Windows, alt-tabbing while the cursor is grabbed no longer makes it impossible to re-grab the cursor.
|
||||
- On Windows, using `CursorState::Hide` when the cursor is grabbed now ungrabs the cursor first.
|
||||
- Implemented `MouseCursor::NoneCursor` on Windows.
|
||||
- Added `WindowBuilder::with_always_on_top` and `Window::set_always_on_top`. Implemented on Windows, macOS, and X11.
|
||||
- On X11, `WindowBuilderExt` now has `with_class`, `with_override_redirect`, and `with_x11_window_type` to allow for more control over window creation. `WindowExt` additionally has `set_urgent`.
|
||||
- More hints are set by default on X11, including `_NET_WM_PID` and `WM_CLIENT_MACHINE`. Note that prior to this, the `WM_CLASS` hint was automatically set to whatever value was passed to `with_title`. It's now set to the executable name to better conform to expectations and the specification; if this is undesirable, you must explicitly use `WindowBuilderExt::with_class`.
|
||||
|
||||
# Version 0.14.0 (2018-05-09)
|
||||
|
||||
- Created the `Copy`, `Paste` and `Cut` `VirtualKeyCode`s and added support for them on X11 and Wayland
|
||||
- Fix `.with_decorations(false)` in macOS
|
||||
- On Mac, `NSWindow` and supporting objects might be alive long after they were `closed` which resulted in apps consuming more heap then needed. Mainly it was affecting multi window applications. Not expecting any user visible change of behaviour after the fix.
|
||||
- Fix regression of Window platform extensions for macOS where `NSFullSizeContentViewWindowMask` was not being correctly applied to `.fullsize_content_view`.
|
||||
- Corrected `get_position` on Windows to be relative to the screen rather than to the taskbar.
|
||||
- Corrected `Moved` event on Windows to use position values equivalent to those returned by `get_position`. It previously supplied client area positions instead of window positions, and would additionally interpret negative values as being very large (around `u16::MAX`).
|
||||
- Implemented `Moved` event on macOS.
|
||||
- On X11, the `Moved` event correctly use window positions rather than client area positions. Additionally, a stray `Moved` that unconditionally accompanied `Resized` with the client area position relative to the parent has been eliminated; `Moved` is still received alongside `Resized`, but now only once and always correctly.
|
||||
- On Windows, implemented all variants of `DeviceEvent` other than `Text`. Mouse `DeviceEvent`s are now received even if the window isn't in the foreground.
|
||||
- `DeviceId` on Windows is no longer a unit struct, and now contains a `u32`. For `WindowEvent`s, this will always be 0, but on `DeviceEvent`s it will be the handle to that device. `DeviceIdExt::get_persistent_identifier` can be used to acquire a unique identifier for that device that persists across replugs/reboots/etc.
|
||||
- Corrected `run_forever` on X11 to stop discarding `Awakened` events.
|
||||
- Various safety and correctness improvements to the X11 backend internals.
|
||||
- Fixed memory leak on X11 every time the mouse entered the window.
|
||||
- On X11, drag and drop now works reliably in release mode.
|
||||
- Added `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` to X11, allowing for more optional hints to be set.
|
||||
- Rework of the wayland backend, migrating it to use [Smithay's Client Toolkit](https://github.com/Smithay/client-toolkit).
|
||||
- Added `WindowBuilder::with_window_icon` and `Window::set_window_icon`, finally making it possible to set the window icon on Windows and X11. The `icon_loading` feature can be enabled to allow for icons to be easily loaded; see example program `window_icon.rs` for usage.
|
||||
- Windows additionally has `WindowBuilderExt::with_taskbar_icon` and `WindowExt::set_taskbar_icon`.
|
||||
- On Windows, fix panic when trying to call `set_fullscreen(None)` on a window that has not been fullscreened prior.
|
||||
|
||||
# Version 0.13.1 (2018-04-26)
|
||||
|
||||
- Ensure necessary `x11-dl` version is used.
|
||||
|
||||
# Version 0.13.0 (2018-04-25)
|
||||
|
||||
- Implement `WindowBuilder::with_maximized`, `Window::set_fullscreen`, `Window::set_maximized` and `Window::set_decorations` for MacOS.
|
||||
|
||||
42
CONTRIBUTING.md
Normal file
42
CONTRIBUTING.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Winit Contributing Guidelines
|
||||
|
||||
## Scope
|
||||
[See `FEATURES.md`](./FEATURES.md). When requesting or implementing a new Winit feature, you should
|
||||
consider whether or not it's directly related to window creation or input handling. If it isn't, it
|
||||
may be worth creating a separate crate that extends Winit's API to add that functionality.
|
||||
|
||||
|
||||
## Reporting an issue
|
||||
|
||||
When reporting an issue, in order to help the maintainers understand what the problem is, please make
|
||||
your description of the issue as detailed as possible:
|
||||
|
||||
- if it is a bug, please provide clear explanation of what happens, what should happen, and how to
|
||||
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
|
||||
|
||||
When making a code contribution to winit, before opening your pull request, please make sure that:
|
||||
|
||||
- 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 changelog file if the introduced change is relevant to winit users
|
||||
- if your PR affects the platform compatibility of one or more features or adds another feature, the
|
||||
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
|
||||
should be updated.
|
||||
|
||||
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
|
||||
is that a PR must be approved by at least two maintainers of winit before being merged, including
|
||||
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
|
||||
|
||||
## Maintainers & Testers
|
||||
|
||||
The current [list of testers and contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors)
|
||||
can be found on the Wiki.
|
||||
|
||||
If you are interested in contributing or testing on a platform, please add yourself to that table!
|
||||
138
Cargo.toml
138
Cargo.toml
@@ -1,52 +1,128 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.13.0"
|
||||
authors = ["The winit contributors, Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
version = "0.22.2"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2018"
|
||||
keywords = ["windowing"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/tomaka/winit"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1"
|
||||
libc = "0.2"
|
||||
[package.metadata.docs.rs]
|
||||
features = ["serde", "web-sys"]
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"]
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies.android_glue]
|
||||
version = "0.2"
|
||||
[features]
|
||||
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
|
||||
stdweb = ["std_web", "instant/stdweb"]
|
||||
|
||||
[dependencies]
|
||||
instant = "0.1"
|
||||
lazy_static = "1"
|
||||
libc = "0.2.64"
|
||||
log = "0.4"
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
raw-window-handle = "0.3"
|
||||
bitflags = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
image = "0.23"
|
||||
simple_logger = "1"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
ndk = "0.1.0"
|
||||
ndk-sys = "0.1.0"
|
||||
ndk-glue = "0.1.0"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
objc = "0.2"
|
||||
objc = "0.2.3"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2"
|
||||
cocoa = "0.14"
|
||||
core-foundation = "0.5"
|
||||
core-graphics = "0.13"
|
||||
cocoa = "0.20"
|
||||
core-foundation = "0.7"
|
||||
core-graphics = "0.19"
|
||||
dispatch = "0.2.0"
|
||||
objc = "0.2.6"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
|
||||
version = "0.1.4"
|
||||
default_features = false
|
||||
features = ["display_link"]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
||||
version = "0.3"
|
||||
version = "0.3.6"
|
||||
features = [
|
||||
"combaseapi",
|
||||
"commctrl",
|
||||
"dwmapi",
|
||||
"errhandlingapi",
|
||||
"hidusage",
|
||||
"libloaderapi",
|
||||
"objbase",
|
||||
"ole2",
|
||||
"processthreadsapi",
|
||||
"shellapi",
|
||||
"shellscalingapi",
|
||||
"shobjidl_core",
|
||||
"unknwnbase",
|
||||
"winbase",
|
||||
"windowsx",
|
||||
"winerror",
|
||||
"wingdi",
|
||||
"winnt",
|
||||
"winuser",
|
||||
"wingdi",
|
||||
"shellapi",
|
||||
"dwmapi",
|
||||
"processthreadsapi",
|
||||
"libloaderapi",
|
||||
"windowsx",
|
||||
"hidusage",
|
||||
"combaseapi",
|
||||
"objbase",
|
||||
"unknwnbase",
|
||||
]
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))'.dependencies]
|
||||
wayland-client = { version = "0.12.0", features = ["dlopen"] }
|
||||
wayland-protocols = { version = "0.12.0", features = ["unstable_protocols"] }
|
||||
wayland-kbd = "0.13.0"
|
||||
wayland-window = "0.13.0"
|
||||
x11-dl = "2.17"
|
||||
percent-encoding = "1.0"
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
|
||||
wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] }
|
||||
mio = "0.6"
|
||||
mio-extras = "2.0"
|
||||
smithay-client-toolkit = "^0.6.6"
|
||||
x11-dl = "2.18.5"
|
||||
percent-encoding = "2.0"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
|
||||
version = "0.10"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
|
||||
package = "web-sys"
|
||||
version = "0.3.22"
|
||||
optional = true
|
||||
features = [
|
||||
'console',
|
||||
'CssStyleDeclaration',
|
||||
'BeforeUnloadEvent',
|
||||
'Document',
|
||||
'DomRect',
|
||||
'Element',
|
||||
'Event',
|
||||
'EventTarget',
|
||||
'FocusEvent',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'KeyboardEvent',
|
||||
'MediaQueryList',
|
||||
'MediaQueryListEvent',
|
||||
'MouseEvent',
|
||||
'Node',
|
||||
'PointerEvent',
|
||||
'Window',
|
||||
'WheelEvent'
|
||||
]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
|
||||
version = "0.2.45"
|
||||
optional = true
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.std_web]
|
||||
package = "stdweb"
|
||||
version = "=0.4.20"
|
||||
optional = true
|
||||
features = ["experimental_features_which_may_break_on_minor_version_bumps"]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
console_log = "0.1"
|
||||
|
||||
237
FEATURES.md
Normal file
237
FEATURES.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# Winit Scope
|
||||
|
||||
Winit aims to expose an interface that abstracts over window creation and input handling, and can
|
||||
be used to create both games and applications. It supports the main graphical platforms:
|
||||
- Desktop
|
||||
- Windows
|
||||
- macOS
|
||||
- Unix
|
||||
- via X11
|
||||
- via Wayland
|
||||
- Mobile
|
||||
- iOS
|
||||
- Android
|
||||
- Web
|
||||
- via WASM
|
||||
|
||||
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
|
||||
aim to support every single feature of every platform, but rather to abstract over the common features
|
||||
available everywhere. In this context, APIs exposed in winit can be split into different "support tiers":
|
||||
|
||||
- **Core:** Features that are essential to providing a well-formed abstraction over each platform's
|
||||
windowing and input APIs.
|
||||
- **Platform:** Platform-specific features that can't be meaningfully exposed through a common API and
|
||||
cannot be implemented outside of Winit without exposing a significant amount of Winit's internals
|
||||
or interfering with Winit's abstractions.
|
||||
- **Usability:** Features that are not strictly essential to Winit's functionality, but provide meaningful
|
||||
usability improvements and cannot be reasonably implemented in an external crate. These are
|
||||
generally optional and exposed through Cargo features.
|
||||
|
||||
Core features are taken care of by the core Winit maintainers. Platform features are not.
|
||||
When a platform feature is submitted, the submitter is considered the expert in the
|
||||
feature and may be asked to support the feature should it break in the future.
|
||||
|
||||
Winit ***does not*** directly expose functionality for drawing inside windows or creating native
|
||||
menus, but ***does*** commit to providing APIs that higher-level crates can use to implement that
|
||||
functionality.
|
||||
|
||||
## `1.0` and stability
|
||||
|
||||
When all core features are implemented to the satisfaction of the Winit maintainers, Winit 1.0 will
|
||||
be released and the library will enter maintenance mode. For the most part, new core features will not
|
||||
be added past this point. New platform features may be accepted and exposed through point releases.
|
||||
|
||||
### Tier upgrades
|
||||
Some platform features could in theory be exposed across multiple platforms, but have not gone
|
||||
through the implementation work necessary to function on all platforms. When one of these features
|
||||
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
|
||||
If that gets accepted, the platform-specific functions gets deprecated and become permanently
|
||||
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 grab**: Locking the cursor so it cannot exit the client area of a window.
|
||||
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
|
||||
- **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 taskbar icon
|
||||
* Setting the parent window
|
||||
* `WS_EX_NOREDIRECTIONBITMAP` support
|
||||
* Theme the title bar according to Windows 10 Dark Mode setting
|
||||
|
||||
### macOS
|
||||
* Window activation policy
|
||||
* Window movable by background
|
||||
* Transparent titlebar
|
||||
* Hidden titlebar
|
||||
* Hidden titlebar buttons
|
||||
* Full-size content view
|
||||
|
||||
### Unix
|
||||
* Window urgency
|
||||
* X11 Window Class
|
||||
* X11 Override Redirect Flag
|
||||
* GTK Theme Variant
|
||||
* Base window size
|
||||
|
||||
### iOS
|
||||
* `winit` has a minimum OS requirement of iOS 8
|
||||
* Get the `UIWindow` object pointer
|
||||
* Get the `UIViewController` object pointer
|
||||
* Get the `UIView` object pointer
|
||||
* Get the `UIScreen` object pointer
|
||||
* Setting the `UIView` hidpi factor
|
||||
* Valid orientations
|
||||
* Home indicator visibility
|
||||
* Status bar visibility
|
||||
* Deferrring system gestures
|
||||
* Support for custom `UIView` derived class
|
||||
* Getting the device idiom
|
||||
* Getting the preferred video mode
|
||||
|
||||
### Web
|
||||
* Get if systems preferred color scheme is "dark"
|
||||
|
||||
## Usability
|
||||
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
||||
|
||||
## 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 |WASM |
|
||||
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |
|
||||
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|
|
||||
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|
|
||||
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A**|
|
||||
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
||||
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ |
|
||||
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|
||||
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |
|
||||
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
||||
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
||||
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
||||
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|
||||
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|
||||
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|
|
||||
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |**N/A**|
|
||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|
||||
|
||||
### System information
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
||||
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
|
||||
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A**|
|
||||
|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |**N/A**|
|
||||
|
||||
### Input handling
|
||||
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
||||
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|
||||
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**|
|
||||
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|❓ |
|
||||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|
||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |
|
||||
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |
|
||||
|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |✔️ |
|
||||
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|
||||
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|
||||
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|
||||
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|
||||
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|
||||
|
||||
### 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 |WASM |
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |❓ |
|
||||
|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ |❓ |
|
||||
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ |
|
||||
|
||||
### Completed API Reworks
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|
||||
[#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
|
||||
[#804]: https://github.com/rust-windowing/winit/issues/804
|
||||
[#812]: https://github.com/rust-windowing/winit/issues/812
|
||||
14
HALL_OF_CHAMPIONS.md
Normal file
14
HALL_OF_CHAMPIONS.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Hall of Champions
|
||||
|
||||
The Winit maintainers would like to recognize the following former Winit
|
||||
contributors, without whom Winit would not exist in its current form. We thank
|
||||
them deeply for their time and efforts, and wish them best of luck in their
|
||||
future endeavors:
|
||||
|
||||
* [@tomaka]: For creating the Winit project and guiding it through its early
|
||||
years of existence.
|
||||
* [@francesca64]: For taking over the responsibility of maintaining almost every
|
||||
Winit backend, and standardizing HiDPI support across all of them
|
||||
|
||||
[@tomaka]: https://github.com/tomaka
|
||||
[@francesca64]: https://github.com/francesca64
|
||||
73
README.md
73
README.md
@@ -1,19 +1,28 @@
|
||||
# winit - Cross-platform window creation and management in Rust
|
||||
|
||||
[](https://crates.io/crates/winit)
|
||||
|
||||
[](https://crates.io/crates/winit)
|
||||
[](https://docs.rs/winit)
|
||||
|
||||
[](https://travis-ci.org/tomaka/winit)
|
||||
[](https://ci.appveyor.com/project/tomaka/winit/branch/master)
|
||||
[](https://github.com/rust-windowing/winit/actions)
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.13"
|
||||
winit = "0.22.2"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
|
||||
For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md).
|
||||
|
||||
For features _outside_ the scope of winit, see [Missing features provided by other crates](https://github.com/rust-windowing/winit/wiki/Missing-features-provided-by-other-crates) in the wiki.
|
||||
|
||||
## Contact Us
|
||||
|
||||
Join us in any of these:
|
||||
|
||||
[](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a)
|
||||
[](https://matrix.to/#/#Glutin:matrix.org)
|
||||
[](https://gitter.im/tomaka/glutin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
## Usage
|
||||
|
||||
Winit is a window creation and management library. It can create windows and lets you handle
|
||||
@@ -25,27 +34,40 @@ show something on the window you need to use the platform-specific getters provi
|
||||
another library.
|
||||
|
||||
```rust
|
||||
extern crate winit;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let window = winit::Window::new(&events_loop).unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
match event {
|
||||
winit::Event::WindowEvent {
|
||||
event: winit::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => winit::ControlFlow::Break,
|
||||
_ => winit::ControlFlow::Continue,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Winit is only officially supported on the latest stable version of the Rust compiler.
|
||||
|
||||
### Cargo Features
|
||||
|
||||
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
|
||||
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
|
||||
|
||||
### Platform-specific usage
|
||||
|
||||
#### Emscripten and WebAssembly
|
||||
#### WebAssembly
|
||||
|
||||
Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to:
|
||||
|
||||
@@ -54,3 +76,22 @@ Building a binary will yield a `.js` file. In order to use it in an HTML file, y
|
||||
the element of the `<canvas>` element (in the example you would retrieve it via `document.getElementById("my_id")`).
|
||||
More information [here](https://kripken.github.io/emscripten-site/docs/api_reference/module.html).
|
||||
- Make sure that you insert the `.js` file generated by Rust after the `Module` variable is created.
|
||||
|
||||
#### Android
|
||||
|
||||
This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
|
||||
|
||||
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
|
||||
```toml
|
||||
[[example]]
|
||||
name = "request_redraw_threaded"
|
||||
crate-type = ["cdylib"]
|
||||
```
|
||||
|
||||
And add this to the example file to add the native activity glue:
|
||||
```rust
|
||||
#[cfg(target_os = "android")]
|
||||
ndk_glue::ndk_glue!(main);
|
||||
```
|
||||
|
||||
And run the application with `cargo apk run --example request_redraw_threaded`
|
||||
|
||||
17
appveyor.yml
17
appveyor.yml
@@ -1,17 +0,0 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
install:
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe"
|
||||
- rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
|
||||
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
|
||||
- SET PATH=%PATH%;C:\MinGW\bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo test --verbose
|
||||
113
examples/control_flow.rs
Normal file
113
examples/control_flow.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use std::{thread, time};
|
||||
|
||||
use winit::{
|
||||
event::{Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Mode {
|
||||
Wait,
|
||||
WaitUntil,
|
||||
Poll,
|
||||
}
|
||||
|
||||
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
|
||||
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
|
||||
println!("Press '1' to switch to Wait mode.");
|
||||
println!("Press '2' to switch to WaitUntil mode.");
|
||||
println!("Press '3' to switch to Poll mode.");
|
||||
println!("Press 'R' to toggle request_redraw() calls.");
|
||||
println!("Press 'Esc' to close the window.");
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut mode = Mode::Wait;
|
||||
let mut request_redraw = false;
|
||||
let mut wait_cancelled = false;
|
||||
let mut close_requested = false;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
use winit::event::{ElementState, StartCause, VirtualKeyCode};
|
||||
println!("{:?}", event);
|
||||
match event {
|
||||
Event::NewEvents(start_cause) => {
|
||||
wait_cancelled = match start_cause {
|
||||
StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
close_requested = true;
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match virtual_code {
|
||||
VirtualKeyCode::Key1 => {
|
||||
mode = Mode::Wait;
|
||||
println!("\nmode: {:?}\n", mode);
|
||||
}
|
||||
VirtualKeyCode::Key2 => {
|
||||
mode = Mode::WaitUntil;
|
||||
println!("\nmode: {:?}\n", mode);
|
||||
}
|
||||
VirtualKeyCode::Key3 => {
|
||||
mode = Mode::Poll;
|
||||
println!("\nmode: {:?}\n", mode);
|
||||
}
|
||||
VirtualKeyCode::R => {
|
||||
request_redraw = !request_redraw;
|
||||
println!("\nrequest_redraw: {}\n", request_redraw);
|
||||
}
|
||||
VirtualKeyCode::Escape => {
|
||||
close_requested = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
Event::MainEventsCleared => {
|
||||
if request_redraw && !wait_cancelled && !close_requested {
|
||||
window.request_redraw();
|
||||
}
|
||||
if close_requested {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_window_id) => {}
|
||||
Event::RedrawEventsCleared => {
|
||||
*control_flow = match mode {
|
||||
Mode::Wait => ControlFlow::Wait,
|
||||
Mode::WaitUntil => {
|
||||
if wait_cancelled {
|
||||
*control_flow
|
||||
} else {
|
||||
ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME)
|
||||
}
|
||||
}
|
||||
Mode::Poll => {
|
||||
thread::sleep(POLL_SLEEP_TIME);
|
||||
ControlFlow::Poll
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,32 +1,88 @@
|
||||
extern crate winit;
|
||||
|
||||
use winit::{Event, ElementState, MouseCursor, WindowEvent, KeyboardInput, ControlFlow};
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{CursorIcon, WindowBuilder},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = winit::WindowBuilder::new().build(&events_loop).unwrap();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
window.set_title("A fantastic window!");
|
||||
|
||||
let cursors = [MouseCursor::Default, MouseCursor::Crosshair, MouseCursor::Hand, MouseCursor::Arrow, MouseCursor::Move, MouseCursor::Text, MouseCursor::Wait, MouseCursor::Help, MouseCursor::Progress, MouseCursor::NotAllowed, MouseCursor::ContextMenu, MouseCursor::NoneCursor, MouseCursor::Cell, MouseCursor::VerticalText, MouseCursor::Alias, MouseCursor::Copy, MouseCursor::NoDrop, MouseCursor::Grab, MouseCursor::Grabbing, MouseCursor::AllScroll, MouseCursor::ZoomIn, MouseCursor::ZoomOut, MouseCursor::EResize, MouseCursor::NResize, MouseCursor::NeResize, MouseCursor::NwResize, MouseCursor::SResize, MouseCursor::SeResize, MouseCursor::SwResize, MouseCursor::WResize, MouseCursor::EwResize, MouseCursor::NsResize, MouseCursor::NeswResize, MouseCursor::NwseResize, MouseCursor::ColResize, MouseCursor::RowResize];
|
||||
let mut cursor_idx = 0;
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. }, .. } => {
|
||||
println!("Setting cursor to \"{:?}\"", cursors[cursor_idx]);
|
||||
window.set_cursor(cursors[cursor_idx]);
|
||||
if cursor_idx < cursors.len() - 1 {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
|
||||
window.set_cursor_icon(CURSORS[cursor_idx]);
|
||||
if cursor_idx < CURSORS.len() - 1 {
|
||||
cursor_idx += 1;
|
||||
} else {
|
||||
cursor_idx = 0;
|
||||
}
|
||||
},
|
||||
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
|
||||
return ControlFlow::Break;
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
const CURSORS: &[CursorIcon] = &[
|
||||
CursorIcon::Default,
|
||||
CursorIcon::Crosshair,
|
||||
CursorIcon::Hand,
|
||||
CursorIcon::Arrow,
|
||||
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,
|
||||
];
|
||||
|
||||
55
examples/cursor_grab.rs
Normal file
55
examples/cursor_grab.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use winit::{
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut modifiers = ModifiersState::default();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(key),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
use winit::event::VirtualKeyCode::*;
|
||||
match key {
|
||||
Escape => *control_flow = ControlFlow::Exit,
|
||||
G => window.set_cursor_grab(!modifiers.shift()).unwrap(),
|
||||
H => window.set_cursor_visible(modifiers.shift()),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
WindowEvent::ModifiersChanged(m) => modifiers = m,
|
||||
_ => (),
|
||||
},
|
||||
Event::DeviceEvent { event, .. } => match event {
|
||||
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {:?}", delta),
|
||||
DeviceEvent::Button { button, state } => match state {
|
||||
ElementState::Pressed => println!("mouse button {} pressed", button),
|
||||
ElementState::Released => println!("mouse button {} released", button),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
52
examples/custom_events.rs
Normal file
52
examples/custom_events.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum CustomEvent {
|
||||
Timer,
|
||||
}
|
||||
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::<CustomEvent>::with_user_event();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
// `EventLoopProxy` allows you to dispatch custom events to the main Winit event
|
||||
// loop from any thread.
|
||||
let event_loop_proxy = event_loop.create_proxy();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// Wake up the `event_loop` once every second and dispatch a custom event
|
||||
// from a different thread.
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
event_loop_proxy.send_event(CustomEvent::Timer).ok();
|
||||
}
|
||||
});
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::UserEvent(event) => println!("user event: {:?}", event),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
panic!("This example is not supported on web.");
|
||||
}
|
||||
@@ -1,69 +1,66 @@
|
||||
extern crate winit;
|
||||
|
||||
use std::io::{self, Write};
|
||||
use winit::{ControlFlow, Event, WindowEvent};
|
||||
use std::io::{stdin, stdout, Write};
|
||||
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::monitor::{MonitorHandle, VideoMode};
|
||||
use winit::window::{Fullscreen, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
// enumerating monitors
|
||||
let monitor = {
|
||||
for (num, monitor) in events_loop.get_available_monitors().enumerate() {
|
||||
println!("Monitor #{}: {:?}", num, monitor.get_name());
|
||||
}
|
||||
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
|
||||
stdout().flush().unwrap();
|
||||
|
||||
print!("Please write the number of the monitor to use: ");
|
||||
io::stdout().flush().unwrap();
|
||||
let mut num = String::new();
|
||||
stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
|
||||
let mut num = String::new();
|
||||
io::stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
let monitor = events_loop.get_available_monitors().nth(num).expect("Please enter a valid ID");
|
||||
let fullscreen = Some(match num {
|
||||
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
|
||||
2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
|
||||
_ => panic!("Please enter a valid number"),
|
||||
});
|
||||
|
||||
println!("Using {:?}", monitor.get_name());
|
||||
|
||||
monitor
|
||||
};
|
||||
|
||||
let window = winit::WindowBuilder::new()
|
||||
.with_title("Hello world!")
|
||||
.with_fullscreen(Some(monitor))
|
||||
.build(&events_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut is_fullscreen = true;
|
||||
let mut is_maximized = false;
|
||||
let mut decorations = true;
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
println!("{:?}", event);
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello world!")
|
||||
.with_fullscreen(fullscreen.clone())
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => return ControlFlow::Break,
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
winit::KeyboardInput {
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match (virtual_code, state) {
|
||||
(winit::VirtualKeyCode::Escape, _) => return ControlFlow::Break,
|
||||
(winit::VirtualKeyCode::F, winit::ElementState::Pressed) => {
|
||||
is_fullscreen = !is_fullscreen;
|
||||
if !is_fullscreen {
|
||||
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
||||
(VirtualKeyCode::F, ElementState::Pressed) => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
window.set_fullscreen(Some(window.get_current_monitor()));
|
||||
window.set_fullscreen(fullscreen.clone());
|
||||
}
|
||||
}
|
||||
(winit::VirtualKeyCode::M, winit::ElementState::Pressed) => {
|
||||
(VirtualKeyCode::S, ElementState::Pressed) => {
|
||||
println!("window.fullscreen {:?}", window.fullscreen());
|
||||
}
|
||||
(VirtualKeyCode::M, ElementState::Pressed) => {
|
||||
is_maximized = !is_maximized;
|
||||
window.set_maximized(is_maximized);
|
||||
}
|
||||
(winit::VirtualKeyCode::D, winit::ElementState::Pressed) => {
|
||||
(VirtualKeyCode::D, ElementState::Pressed) => {
|
||||
decorations = !decorations;
|
||||
window.set_decorations(decorations);
|
||||
}
|
||||
@@ -73,7 +70,48 @@ fn main() {
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
// Enumerate monitors and prompt user to choose one
|
||||
fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
|
||||
for (num, monitor) in event_loop.available_monitors().enumerate() {
|
||||
println!("Monitor #{}: {:?}", num, monitor.name());
|
||||
}
|
||||
|
||||
print!("Please write the number of the monitor to use: ");
|
||||
stdout().flush().unwrap();
|
||||
|
||||
let mut num = String::new();
|
||||
stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
let monitor = event_loop
|
||||
.available_monitors()
|
||||
.nth(num)
|
||||
.expect("Please enter a valid ID");
|
||||
|
||||
println!("Using {:?}", monitor.name());
|
||||
|
||||
monitor
|
||||
}
|
||||
|
||||
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
|
||||
for (i, video_mode) in monitor.video_modes().enumerate() {
|
||||
println!("Video mode #{}: {}", i, video_mode);
|
||||
}
|
||||
|
||||
print!("Please write the number of the video mode to use: ");
|
||||
stdout().flush().unwrap();
|
||||
|
||||
let mut num = String::new();
|
||||
stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
let video_mode = monitor
|
||||
.video_modes()
|
||||
.nth(num)
|
||||
.expect("Please enter a valid ID");
|
||||
|
||||
println!("Using {}", video_mode);
|
||||
|
||||
video_mode
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
extern crate winit;
|
||||
|
||||
use winit::{ControlFlow, WindowEvent, ElementState, KeyboardInput};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
|
||||
let window = winit::WindowBuilder::new().build(&events_loop).unwrap();
|
||||
window.set_title("winit - Cursor grabbing test");
|
||||
|
||||
let mut grabbed = false;
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
winit::Event::WindowEvent { event, .. } => {
|
||||
match event {
|
||||
WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. } => {
|
||||
if grabbed {
|
||||
grabbed = false;
|
||||
window.set_cursor_state(winit::CursorState::Normal)
|
||||
.ok().expect("could not ungrab mouse cursor");
|
||||
} else {
|
||||
grabbed = true;
|
||||
window.set_cursor_state(winit::CursorState::Grab)
|
||||
.ok().expect("could not grab mouse cursor");
|
||||
}
|
||||
},
|
||||
|
||||
WindowEvent::CloseRequested => return ControlFlow::Break,
|
||||
|
||||
a @ WindowEvent::CursorMoved { .. } => {
|
||||
println!("{:?}", a);
|
||||
},
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
@@ -1,74 +1,83 @@
|
||||
extern crate winit;
|
||||
use winit::{
|
||||
event::{Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = winit::WindowBuilder::new()
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("Your faithful window")
|
||||
.build(&events_loop)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut close_requested = false;
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
use winit::WindowEvent::*;
|
||||
use winit::ElementState::Released;
|
||||
use winit::VirtualKeyCode::{N, Y};
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
use winit::event::{
|
||||
ElementState::Released,
|
||||
VirtualKeyCode::{N, Y},
|
||||
};
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
winit::Event::WindowEvent { event, .. } => match event {
|
||||
CloseRequested => {
|
||||
// `CloseRequested` is sent when the close button on the window is pressed (or
|
||||
// through whatever other mechanisms the window manager provides for closing a
|
||||
// window). If you don't handle this event, the close button won't actually do
|
||||
// anything.
|
||||
Event::WindowEvent { event, .. } => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
// `CloseRequested` is sent when the close button on the window is pressed (or
|
||||
// through whatever other mechanisms the window manager provides for closing a
|
||||
// window). If you don't handle this event, the close button won't actually do
|
||||
// anything.
|
||||
|
||||
// A common thing to do here is prompt the user if they have unsaved work.
|
||||
// Creating a proper dialog box for that is far beyond the scope of this
|
||||
// example, so here we'll just respond to the Y and N keys.
|
||||
println!("Are you ready to bid your window farewell? [Y/N]");
|
||||
close_requested = true;
|
||||
// A common thing to do here is prompt the user if they have unsaved work.
|
||||
// Creating a proper dialog box for that is far beyond the scope of this
|
||||
// example, so here we'll just respond to the Y and N keys.
|
||||
println!("Are you ready to bid your window farewell? [Y/N]");
|
||||
close_requested = true;
|
||||
|
||||
// In applications where you can safely close the window without further
|
||||
// action from the user, this is generally where you'd handle cleanup before
|
||||
// closing the window. How to close the window is detailed in the handler for
|
||||
// the Y key.
|
||||
}
|
||||
KeyboardInput {
|
||||
input:
|
||||
winit::KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state: Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match virtual_code {
|
||||
Y => {
|
||||
if close_requested {
|
||||
// This is where you'll want to do any cleanup you need.
|
||||
println!("Buh-bye!");
|
||||
|
||||
// For a single-window application like this, you'd normally just
|
||||
// break out of the event loop here. If you wanted to keep running the
|
||||
// event loop (i.e. if it's a multi-window application), you need to
|
||||
// drop the window. That closes it, and results in `Destroyed` being
|
||||
// sent.
|
||||
return winit::ControlFlow::Break;
|
||||
}
|
||||
// In applications where you can safely close the window without further
|
||||
// action from the user, this is generally where you'd handle cleanup before
|
||||
// closing the window. How to close the window is detailed in the handler for
|
||||
// the Y key.
|
||||
}
|
||||
N => {
|
||||
if close_requested {
|
||||
println!("Your window will continue to stay by your side.");
|
||||
close_requested = false;
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state: Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
match virtual_code {
|
||||
Y => {
|
||||
if close_requested {
|
||||
// This is where you'll want to do any cleanup you need.
|
||||
println!("Buh-bye!");
|
||||
|
||||
// For a single-window application like this, you'd normally just
|
||||
// break out of the event loop here. If you wanted to keep running the
|
||||
// event loop (i.e. if it's a multi-window application), you need to
|
||||
// drop the window. That closes it, and results in `Destroyed` being
|
||||
// sent.
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
N => {
|
||||
if close_requested {
|
||||
println!("Your window will continue to stay by your side.");
|
||||
close_requested = false;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
winit::ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
BIN
examples/icon.png
Normal file
BIN
examples/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -1,21 +1,29 @@
|
||||
extern crate winit;
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = winit::WindowBuilder::new()
|
||||
.build(&events_loop)
|
||||
.unwrap();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
window.set_min_dimensions(Some((400, 200)));
|
||||
window.set_max_dimensions(Some((800, 400)));
|
||||
window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0)));
|
||||
window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0)));
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
winit::Event::WindowEvent { event: winit::WindowEvent::CloseRequested, .. } => winit::ControlFlow::Break,
|
||||
_ => winit::ControlFlow::Continue,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
40
examples/minimize.rs
Normal file
40
examples/minimize.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
extern crate winit;
|
||||
|
||||
use winit::event::{Event, VirtualKeyCode, WindowEvent};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::window::WindowBuilder;
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
|
||||
// Keyboard input event to handle minimize via a hotkey
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::KeyboardInput { input, .. },
|
||||
window_id,
|
||||
} => {
|
||||
if window_id == window.id() {
|
||||
// Pressing the 'M' key will minimize the window
|
||||
if input.virtual_keycode == Some(VirtualKeyCode::M) {
|
||||
window.set_minimized(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
10
examples/monitor_list.rs
Normal file
10
examples/monitor_list.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
dbg!(window.available_monitors().collect::<Vec<_>>());
|
||||
dbg!(window.primary_monitor());
|
||||
}
|
||||
185
examples/multithreaded.rs
Normal file
185
examples/multithreaded.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||
|
||||
use winit::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{CursorIcon, Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
const WINDOW_COUNT: usize = 3;
|
||||
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
|
||||
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
|
||||
for _ in 0..WINDOW_COUNT {
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(WINDOW_SIZE)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect();
|
||||
let mut video_mode_id = 0usize;
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
window_senders.insert(window.id(), tx);
|
||||
thread::spawn(move || {
|
||||
while let Ok(event) = rx.recv() {
|
||||
match event {
|
||||
WindowEvent::Moved { .. } => {
|
||||
// We need to update our chosen video mode if the window
|
||||
// was moved to an another monitor, so that the window
|
||||
// appears on this monitor instead when we go fullscreen
|
||||
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
|
||||
video_modes = window.current_monitor().video_modes().collect();
|
||||
video_mode_id = video_mode_id.min(video_modes.len());
|
||||
let video_mode = video_modes.iter().nth(video_mode_id);
|
||||
|
||||
// Different monitors may support different video modes,
|
||||
// and the index we chose previously may now point to a
|
||||
// completely different video mode, so notify the user
|
||||
if video_mode != previous_video_mode.as_ref() {
|
||||
println!(
|
||||
"Window moved to another monitor, picked video mode: {}",
|
||||
video_modes.iter().nth(video_mode_id).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(key),
|
||||
modifiers,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
window.set_title(&format!("{:?}", key));
|
||||
let state = !modifiers.shift();
|
||||
use VirtualKeyCode::*;
|
||||
match key {
|
||||
A => window.set_always_on_top(state),
|
||||
C => window.set_cursor_icon(match state {
|
||||
true => CursorIcon::Progress,
|
||||
false => CursorIcon::Default,
|
||||
}),
|
||||
D => window.set_decorations(!state),
|
||||
// Cycle through video modes
|
||||
Right | Left => {
|
||||
video_mode_id = match key {
|
||||
Left => video_mode_id.saturating_sub(1),
|
||||
Right => (video_modes.len() - 1).min(video_mode_id + 1),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
println!(
|
||||
"Picking video mode: {}",
|
||||
video_modes.iter().nth(video_mode_id).unwrap()
|
||||
);
|
||||
}
|
||||
F => window.set_fullscreen(match (state, modifiers.alt()) {
|
||||
(true, false) => {
|
||||
Some(Fullscreen::Borderless(window.current_monitor()))
|
||||
}
|
||||
(true, true) => Some(Fullscreen::Exclusive(
|
||||
video_modes.iter().nth(video_mode_id).unwrap().clone(),
|
||||
)),
|
||||
(false, _) => None,
|
||||
}),
|
||||
G => window.set_cursor_grab(state).unwrap(),
|
||||
H => window.set_cursor_visible(!state),
|
||||
I => {
|
||||
println!("Info:");
|
||||
println!("-> outer_position : {:?}", window.outer_position());
|
||||
println!("-> inner_position : {:?}", window.inner_position());
|
||||
println!("-> outer_size : {:?}", window.outer_size());
|
||||
println!("-> inner_size : {:?}", window.inner_size());
|
||||
println!("-> fullscreen : {:?}", window.fullscreen());
|
||||
}
|
||||
L => window.set_min_inner_size(match state {
|
||||
true => Some(WINDOW_SIZE),
|
||||
false => None,
|
||||
}),
|
||||
M => window.set_maximized(state),
|
||||
P => window.set_outer_position({
|
||||
let mut position = window.outer_position().unwrap();
|
||||
let sign = if state { 1 } else { -1 };
|
||||
position.x += 10 * sign;
|
||||
position.y += 10 * sign;
|
||||
position
|
||||
}),
|
||||
Q => window.request_redraw(),
|
||||
R => window.set_resizable(state),
|
||||
S => window.set_inner_size(match state {
|
||||
true => PhysicalSize::new(
|
||||
WINDOW_SIZE.width + 100,
|
||||
WINDOW_SIZE.height + 100,
|
||||
),
|
||||
false => WINDOW_SIZE,
|
||||
}),
|
||||
W => {
|
||||
if let Size::Physical(size) = WINDOW_SIZE.into() {
|
||||
window
|
||||
.set_cursor_position(Position::Physical(
|
||||
PhysicalPosition::new(
|
||||
size.width as i32 / 2,
|
||||
size.height as i32 / 2,
|
||||
),
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
Z => {
|
||||
window.set_visible(false);
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
window.set_visible(true);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
event_loop.run(move |event, _event_loop, control_flow| {
|
||||
*control_flow = match !window_senders.is_empty() {
|
||||
true => ControlFlow::Wait,
|
||||
false => ControlFlow::Exit,
|
||||
};
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => match event {
|
||||
WindowEvent::CloseRequested
|
||||
| WindowEvent::Destroyed
|
||||
| WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
window_senders.remove(&window_id);
|
||||
}
|
||||
_ => {
|
||||
if let Some(tx) = window_senders.get(&window_id) {
|
||||
if let Some(event) = event.to_static() {
|
||||
tx.send(event).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
panic!("Example not supported on Wasm");
|
||||
}
|
||||
@@ -1,33 +1,51 @@
|
||||
extern crate winit;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
for _ in 0..3 {
|
||||
let window = winit::Window::new(&events_loop).unwrap();
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
event_loop.run(move |event, event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
winit::Event::WindowEvent {
|
||||
event: winit::WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} => {
|
||||
println!("Window {:?} has received the signal to close", window_id);
|
||||
Event::WindowEvent { event, window_id } => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("Window {:?} has received the signal to close", window_id);
|
||||
|
||||
// This drops the window, causing it to close.
|
||||
windows.remove(&window_id);
|
||||
// This drops the window, causing it to close.
|
||||
windows.remove(&window_id);
|
||||
|
||||
if windows.is_empty() {
|
||||
return winit::ControlFlow::Break;
|
||||
if windows.is_empty() {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
winit::ControlFlow::Continue
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
extern crate winit;
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
|
||||
let _window = winit::WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&events_loop)
|
||||
.unwrap();
|
||||
|
||||
let proxy = events_loop.create_proxy();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// Wake up the `events_loop` once every second.
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
proxy.wakeup().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
println!("{:?}", event);
|
||||
match event {
|
||||
winit::Event::WindowEvent { event: winit::WindowEvent::CloseRequested, .. } =>
|
||||
winit::ControlFlow::Break,
|
||||
_ => winit::ControlFlow::Continue,
|
||||
}
|
||||
});
|
||||
}
|
||||
38
examples/request_redraw.rs
Normal file
38
examples/request_redraw.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use winit::{
|
||||
event::{ElementState, Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Released,
|
||||
..
|
||||
} => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
println!("\nredrawing!\n");
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
39
examples/request_redraw_threaded.rs
Normal file
39
examples/request_redraw_threaded.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use std::{thread, time};
|
||||
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
window.request_redraw();
|
||||
});
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
println!("\nredrawing!\n");
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
45
examples/resizable.rs
Normal file
45
examples/resizable.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let mut resizable = false;
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hit space to toggle resizability.")
|
||||
.with_inner_size(LogicalSize::new(400.0, 200.0))
|
||||
.with_resizable(resizable)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(VirtualKeyCode::Space),
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
resizable = !resizable;
|
||||
println!("Resizable: {}", resizable);
|
||||
window.set_resizable(resizable);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
});
|
||||
}
|
||||
38
examples/timer.rs
Normal file
38
examples/timer.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use instant::Instant;
|
||||
use std::time::Duration;
|
||||
use winit::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let timer_length = Duration::new(1, 0);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => {
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length)
|
||||
}
|
||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length);
|
||||
println!("\nTimer\n");
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,20 +1,31 @@
|
||||
extern crate winit;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = winit::WindowBuilder::new().with_decorations(false)
|
||||
.with_transparency(true)
|
||||
.build(&events_loop).unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
.with_decorations(false)
|
||||
.with_transparent(true)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
window.set_title("A fantastic window!");
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
winit::Event::WindowEvent { event: winit::WindowEvent::CloseRequested, .. } => winit::ControlFlow::Break,
|
||||
_ => winit::ControlFlow::Continue,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
13
examples/video_modes.rs
Normal file
13
examples/video_modes.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use winit::event_loop::EventLoop;
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let monitor = event_loop.primary_monitor();
|
||||
|
||||
println!("Listing available video modes:");
|
||||
|
||||
for mode in monitor.video_modes() {
|
||||
println!("{}", mode);
|
||||
}
|
||||
}
|
||||
74
examples/web.rs
Normal file
74
examples/web.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
{
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
|
||||
let canvas = window.canvas();
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let body = document.body().unwrap();
|
||||
|
||||
body.append_child(&canvas)
|
||||
.expect("Append canvas to HTML body");
|
||||
}
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
{
|
||||
use std_web::web::INode;
|
||||
use winit::platform::web::WindowExtStdweb;
|
||||
|
||||
let canvas = window.canvas();
|
||||
|
||||
let document = std_web::web::document();
|
||||
let body: std_web::web::Node = document.body().expect("Get HTML body").into();
|
||||
|
||||
body.append_child(&canvas);
|
||||
}
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
log::debug!("{:?}", event);
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
std_web::console!(log, "%s", format!("{:?}", event));
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
mod wasm {
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn run() {
|
||||
console_log::init_with_level(log::Level::Debug);
|
||||
|
||||
super::main();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,32 @@
|
||||
extern crate winit;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = winit::WindowBuilder::new()
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&events_loop)
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
winit::Event::WindowEvent {
|
||||
event: winit::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => winit::ControlFlow::Break,
|
||||
_ => winit::ControlFlow::Continue,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
117
examples/window_debug.rs
Normal file
117
examples/window_debug.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
// This example is used by developers to test various window functions.
|
||||
|
||||
use winit::{
|
||||
dpi::{LogicalSize, PhysicalSize},
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(LogicalSize::new(100.0, 100.0))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
eprintln!("debugging keys:");
|
||||
eprintln!(" (E) Enter exclusive fullscreen");
|
||||
eprintln!(" (F) Toggle borderless fullscreen");
|
||||
eprintln!(" (M) Toggle minimized");
|
||||
eprintln!(" (Q) Quit event loop");
|
||||
eprintln!(" (V) Toggle visibility");
|
||||
eprintln!(" (X) Toggle maximized");
|
||||
|
||||
let mut minimized = false;
|
||||
let mut maximized = false;
|
||||
let mut visible = true;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::DeviceEvent {
|
||||
event:
|
||||
DeviceEvent::Key(KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
}),
|
||||
..
|
||||
} => match key {
|
||||
VirtualKeyCode::M => {
|
||||
if minimized {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
}
|
||||
VirtualKeyCode::V => {
|
||||
if !visible {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::KeyboardInput { input, .. },
|
||||
..
|
||||
} => match input {
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
} => match key {
|
||||
VirtualKeyCode::E => {
|
||||
fn area(size: PhysicalSize<u32>) -> u32 {
|
||||
size.width * size.height
|
||||
}
|
||||
|
||||
let monitor = window.current_monitor();
|
||||
if let Some(mode) = monitor
|
||||
.video_modes()
|
||||
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
|
||||
{
|
||||
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
|
||||
} else {
|
||||
eprintln!("no video modes available");
|
||||
}
|
||||
}
|
||||
VirtualKeyCode::F => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
let monitor = window.current_monitor();
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
|
||||
}
|
||||
}
|
||||
VirtualKeyCode::M => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
VirtualKeyCode::Q => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
VirtualKeyCode::V => {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
}
|
||||
VirtualKeyCode::X => {
|
||||
maximized = !maximized;
|
||||
window.set_maximized(maximized);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
56
examples/window_icon.rs
Normal file
56
examples/window_icon.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
extern crate image;
|
||||
use std::path::Path;
|
||||
use winit::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{Icon, WindowBuilder},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().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/icon.png");
|
||||
|
||||
let icon = load_icon(Path::new(path));
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("An iconic window!")
|
||||
// At present, this only does anything on Windows and X11, so if you want to save load
|
||||
// time, you can put icon loading behind a function that returns `None` on other platforms.
|
||||
.with_window_icon(Some(icon))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
use winit::event::WindowEvent::*;
|
||||
match event {
|
||||
CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
DroppedFile(path) => {
|
||||
window.set_window_icon(Some(load_icon(&path)));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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_rgba();
|
||||
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")
|
||||
}
|
||||
61
examples/window_run_return.rs
Normal file
61
examples/window_run_return.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
// Limit this example to only compatible platforms.
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
fn main() {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
platform::desktop::EventLoopExtDesktop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
let mut event_loop = EventLoop::new();
|
||||
|
||||
simple_logger::init().unwrap();
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut quit = false;
|
||||
|
||||
while !quit {
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
// Print only Window events to reduce noise
|
||||
println!("{:?}", event);
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
quit = true;
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
// Sleep for 1/60 second to simulate rendering
|
||||
println!("rendering");
|
||||
sleep(Duration::from_millis(16));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
println!("This platform doesn't support run_return.");
|
||||
}
|
||||
3
rustfmt.toml
Normal file
3
rustfmt.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
force_explicit_abi=true
|
||||
use_field_init_shorthand=true
|
||||
# merge_imports=true
|
||||
501
src/dpi.rs
Normal file
501
src/dpi.rs
Normal file
@@ -0,0 +1,501 @@
|
||||
//! UI scaling is important, so read the docs for this module if you don't want to be confused.
|
||||
//!
|
||||
//! ## Why should I care about UI scaling?
|
||||
//!
|
||||
//! Modern computer screens don't have a consistent relationship between resolution and size.
|
||||
//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
|
||||
//! normally being less than a quarter the size of their desktop counterparts. What's more, neither
|
||||
//! desktop nor mobile screens are consistent resolutions within their own size classes - common
|
||||
//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
|
||||
//! and beyond.
|
||||
//!
|
||||
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
|
||||
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen,
|
||||
//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
|
||||
//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
|
||||
//! problematic with text rendering, where quarter-sized text becomes a significant legibility
|
||||
//! problem.
|
||||
//!
|
||||
//! Failure to account for the scale factor can create a significantly degraded user experience.
|
||||
//! Most notably, it can make users feel like they have bad eyesight, which will potentially cause
|
||||
//! them to think about growing elderly, resulting in them having an existential crisis. Once users
|
||||
//! enter that state, they will no longer be focused on your application.
|
||||
//!
|
||||
//! ## How should I handle it?
|
||||
//!
|
||||
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is
|
||||
//! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
|
||||
//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device
|
||||
//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
|
||||
//!
|
||||
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
|
||||
//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's
|
||||
//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather
|
||||
//! than any DPI-dependent units.
|
||||
//!
|
||||
//! ### Position and Size types
|
||||
//!
|
||||
//! Winit's `Physical(Position|Size)` types correspond with the actual pixels on the device, and the
|
||||
//! `Logical(Position|Size)` types correspond to the physical pixels divided by the scale factor.
|
||||
//! 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's position and size types types are generic over their exact pixel type, `P`, to allow the
|
||||
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
|
||||
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
|
||||
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
|
||||
//! will truncate the fractional part of the float, rather than properly round to the nearest
|
||||
//! integer. Use the provided `cast` function or `From`/`Into` conversions, which handle the
|
||||
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
|
||||
//! int, although rounding lessens the problem.
|
||||
//!
|
||||
//! ### Events
|
||||
//!
|
||||
//! Winit will dispatch a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged)
|
||||
//! event whenever a window's scale factor has changed. This can happen if the user drags their
|
||||
//! window from a standard-resolution monitor to a high-DPI monitor, or if the user changes their
|
||||
//! DPI settings. This gives you a chance to rescale your application's UI elements and adjust how
|
||||
//! the platform changes the window's size to reflect the new scale factor. If a window hasn't
|
||||
//! received a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged) event,
|
||||
//! then its scale factor is `1.0`.
|
||||
//!
|
||||
//! ## How is the scale factor calculated?
|
||||
//!
|
||||
//! Scale factor is calculated differently on different platforms:
|
||||
//!
|
||||
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
|
||||
//! display settings. While users are free to select any option they want, they're only given a
|
||||
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
|
||||
//! global and changing it requires logging out. See [this article][windows_1] for technical
|
||||
//! details.
|
||||
//! - **macOS:** "retina displays" have a scale factor of 2.0. Otherwise, the scale factor is 1.0.
|
||||
//! Intermediate scale factors are never used. It's possible for any display to use that 2.0 scale
|
||||
//! factor, given the use of the command line.
|
||||
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
|
||||
//! currently uses a three-pronged approach:
|
||||
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
|
||||
//! + If not present, use the value set in `Xft.dpi` in Xresources.
|
||||
//! + Otherwise, calcuate the scale factor based on the millimeter monitor dimensions provided by XRandR.
|
||||
//!
|
||||
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
|
||||
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
|
||||
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
|
||||
//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always
|
||||
//! integers (most often 1 or 2).
|
||||
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
|
||||
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
|
||||
//! information.
|
||||
//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the
|
||||
//! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information.
|
||||
//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels.
|
||||
//!
|
||||
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
||||
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
||||
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
|
||||
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
|
||||
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
|
||||
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
|
||||
|
||||
pub trait Pixel: Copy + Into<f64> {
|
||||
fn from_f64(f: f64) -> Self;
|
||||
fn cast<P: Pixel>(self) -> P {
|
||||
P::from_f64(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pixel for u8 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as u8
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
impl Pixel for f32 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f as f32
|
||||
}
|
||||
}
|
||||
impl Pixel for f64 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that the scale factor is a normal positive `f64`.
|
||||
///
|
||||
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from
|
||||
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
|
||||
/// otherwise, you risk panics.
|
||||
#[inline]
|
||||
pub fn validate_scale_factor(scale_factor: f64) -> bool {
|
||||
scale_factor.is_sign_positive() && scale_factor.is_normal()
|
||||
}
|
||||
|
||||
/// A position represented in logical pixels.
|
||||
///
|
||||
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
|
||||
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
|
||||
/// implementation is provided which does the rounding for you.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LogicalPosition<P> {
|
||||
pub x: P,
|
||||
pub y: P,
|
||||
}
|
||||
|
||||
impl<P> LogicalPosition<P> {
|
||||
#[inline]
|
||||
pub const fn new(x: P, y: P) -> Self {
|
||||
LogicalPosition { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> LogicalPosition<P> {
|
||||
#[inline]
|
||||
pub fn from_physical<T: Into<PhysicalPosition<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) -> PhysicalPosition<X> {
|
||||
assert!(validate_scale_factor(scale_factor));
|
||||
let x = self.x.into() * scale_factor;
|
||||
let y = self.y.into() * scale_factor;
|
||||
PhysicalPosition::new(x, y).cast()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> {
|
||||
LogicalPosition {
|
||||
x: self.x.cast(),
|
||||
y: self.y.cast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
|
||||
fn from((x, y): (X, X)) -> LogicalPosition<P> {
|
||||
LogicalPosition::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalPosition<P> {
|
||||
fn into(self: Self) -> (X, X) {
|
||||
(self.x.cast(), self.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> Into<[X; 2]> for LogicalPosition<P> {
|
||||
fn into(self: Self) -> [X; 2] {
|
||||
[self.x.cast(), self.y.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
/// A position represented in physical pixels.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PhysicalPosition<P> {
|
||||
pub x: P,
|
||||
pub y: P,
|
||||
}
|
||||
|
||||
impl<P> PhysicalPosition<P> {
|
||||
#[inline]
|
||||
pub const fn new(x: P, y: P) -> Self {
|
||||
PhysicalPosition { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> PhysicalPosition<P> {
|
||||
#[inline]
|
||||
pub fn from_logical<T: Into<LogicalPosition<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) -> LogicalPosition<X> {
|
||||
assert!(validate_scale_factor(scale_factor));
|
||||
let x = self.x.into() / scale_factor;
|
||||
let y = self.y.into() / scale_factor;
|
||||
LogicalPosition::new(x, y).cast()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> {
|
||||
PhysicalPosition {
|
||||
x: self.x.cast(),
|
||||
y: self.y.cast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
|
||||
fn from((x, y): (X, X)) -> PhysicalPosition<P> {
|
||||
PhysicalPosition::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalPosition<P> {
|
||||
fn into(self: Self) -> (X, X) {
|
||||
(self.x.cast(), self.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> Into<[X; 2]> for PhysicalPosition<P> {
|
||||
fn into(self: Self) -> [X; 2] {
|
||||
[self.x.cast(), self.y.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
/// A size represented in logical pixels.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LogicalSize<P> {
|
||||
pub width: P,
|
||||
pub height: P,
|
||||
}
|
||||
|
||||
impl<P> LogicalSize<P> {
|
||||
#[inline]
|
||||
pub const fn new(width: P, height: P) -> Self {
|
||||
LogicalSize { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> LogicalSize<P> {
|
||||
#[inline]
|
||||
pub fn from_physical<T: Into<PhysicalSize<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) -> PhysicalSize<X> {
|
||||
assert!(validate_scale_factor(scale_factor));
|
||||
let width = self.width.into() * scale_factor;
|
||||
let height = self.height.into() * scale_factor;
|
||||
PhysicalSize::new(width, height).cast()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cast<X: Pixel>(&self) -> LogicalSize<X> {
|
||||
LogicalSize {
|
||||
width: self.width.cast(),
|
||||
height: self.height.cast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
|
||||
fn from((x, y): (X, X)) -> LogicalSize<P> {
|
||||
LogicalSize::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalSize<P> {
|
||||
fn into(self: LogicalSize<P>) -> (X, X) {
|
||||
(self.width.cast(), self.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> Into<[X; 2]> for LogicalSize<P> {
|
||||
fn into(self: Self) -> [X; 2] {
|
||||
[self.width.cast(), self.height.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
/// A size represented in physical pixels.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PhysicalSize<P> {
|
||||
pub width: P,
|
||||
pub height: P,
|
||||
}
|
||||
|
||||
impl<P> PhysicalSize<P> {
|
||||
#[inline]
|
||||
pub const fn new(width: P, height: P) -> Self {
|
||||
PhysicalSize { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> PhysicalSize<P> {
|
||||
#[inline]
|
||||
pub fn from_logical<T: Into<LogicalSize<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) -> LogicalSize<X> {
|
||||
assert!(validate_scale_factor(scale_factor));
|
||||
let width = self.width.into() / scale_factor;
|
||||
let height = self.height.into() / scale_factor;
|
||||
LogicalSize::new(width, height).cast()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cast<X: Pixel>(&self) -> PhysicalSize<X> {
|
||||
PhysicalSize {
|
||||
width: self.width.cast(),
|
||||
height: self.height.cast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
|
||||
fn from((x, y): (X, X)) -> PhysicalSize<P> {
|
||||
PhysicalSize::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalSize<P> {
|
||||
fn into(self: Self) -> (X, X) {
|
||||
(self.width.cast(), self.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> Into<[X; 2]> for PhysicalSize<P> {
|
||||
fn into(self: Self) -> [X; 2] {
|
||||
[self.width.cast(), self.height.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
/// A size that's either physical or logical.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Size {
|
||||
Physical(PhysicalSize<u32>),
|
||||
Logical(LogicalSize<f64>),
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub fn new<S: Into<Size>>(size: S) -> Size {
|
||||
size.into()
|
||||
}
|
||||
|
||||
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalSize<P> {
|
||||
match *self {
|
||||
Size::Physical(size) => size.to_logical(scale_factor),
|
||||
Size::Logical(size) => size.cast(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalSize<P> {
|
||||
match *self {
|
||||
Size::Physical(size) => size.cast(),
|
||||
Size::Logical(size) => size.to_physical(scale_factor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> From<PhysicalSize<P>> for Size {
|
||||
#[inline]
|
||||
fn from(size: PhysicalSize<P>) -> Size {
|
||||
Size::Physical(size.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> From<LogicalSize<P>> for Size {
|
||||
#[inline]
|
||||
fn from(size: LogicalSize<P>) -> Size {
|
||||
Size::Logical(size.cast())
|
||||
}
|
||||
}
|
||||
|
||||
/// A position that's either physical or logical.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Position {
|
||||
Physical(PhysicalPosition<i32>),
|
||||
Logical(LogicalPosition<f64>),
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub fn new<S: Into<Position>>(position: S) -> Position {
|
||||
position.into()
|
||||
}
|
||||
|
||||
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalPosition<P> {
|
||||
match *self {
|
||||
Position::Physical(position) => position.to_logical(scale_factor),
|
||||
Position::Logical(position) => position.cast(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<P> {
|
||||
match *self {
|
||||
Position::Physical(position) => position.cast(),
|
||||
Position::Logical(position) => position.to_physical(scale_factor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> From<PhysicalPosition<P>> for Position {
|
||||
#[inline]
|
||||
fn from(position: PhysicalPosition<P>) -> Position {
|
||||
Position::Physical(position.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> From<LogicalPosition<P>> for Position {
|
||||
#[inline]
|
||||
fn from(position: LogicalPosition<P>) -> Position {
|
||||
Position::Logical(position.cast())
|
||||
}
|
||||
}
|
||||
82
src/error.rs
Normal file
82
src/error.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use std::{error, fmt};
|
||||
|
||||
use crate::platform_impl;
|
||||
|
||||
/// An error whose cause it outside Winit's control.
|
||||
#[derive(Debug)]
|
||||
pub enum ExternalError {
|
||||
/// The operation is not supported by the backend.
|
||||
NotSupported(NotSupportedError),
|
||||
/// 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,
|
||||
}
|
||||
|
||||
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::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 error::Error for OsError {}
|
||||
impl error::Error for ExternalError {}
|
||||
impl error::Error for NotSupportedError {}
|
||||
1066
src/event.rs
Normal file
1066
src/event.rs
Normal file
File diff suppressed because it is too large
Load Diff
225
src/event_loop.rs
Normal file
225
src/event_loop.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
//! The `EventLoop` struct and assorted supporting types, including `ControlFlow`.
|
||||
//!
|
||||
//! If you want to send custom events to the event loop, use [`EventLoop::create_proxy()`][create_proxy]
|
||||
//! to acquire an [`EventLoopProxy`][event_loop_proxy] and call its [`send_event`][send_event] method.
|
||||
//!
|
||||
//! See the root-level documentation for information on how to create and use an event loop to
|
||||
//! handle events.
|
||||
//!
|
||||
//! [create_proxy]: crate::event_loop::EventLoop::create_proxy
|
||||
//! [event_loop_proxy]: crate::event_loop::EventLoopProxy
|
||||
//! [send_event]: crate::event_loop::EventLoopProxy::send_event
|
||||
use instant::Instant;
|
||||
use std::ops::Deref;
|
||||
use std::{error, fmt};
|
||||
|
||||
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 the `EventLoop` 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 `EventLoop` _can_ be sent to an other thread, and the
|
||||
/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread.
|
||||
///
|
||||
pub struct EventLoop<T: 'static> {
|
||||
pub(crate) event_loop: platform_impl::EventLoop<T>,
|
||||
pub(crate) _marker: ::std::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. `EventLoop` will coerce into this type (`impl<T> Deref for
|
||||
/// EventLoop<T>`), so functions that take this as a parameter can also take
|
||||
/// `&EventLoop`.
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
|
||||
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for EventLoop<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("EventLoop { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("EventLoopWindowTarget { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
/// Set by the user callback given to the `EventLoop::run` method.
|
||||
///
|
||||
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`][events_cleared]
|
||||
/// is emitted. Defaults to `Poll`.
|
||||
///
|
||||
/// ## Persistency
|
||||
/// Almost every change is persistent between multiple calls to the event loop closure within a
|
||||
/// given run loop. The only exception to this is `Exit` which, once set, cannot be unset. Changes
|
||||
/// are **not** persistent between multiple calls to `run_return` - issuing a new call will reset
|
||||
/// the control flow to `Poll`.
|
||||
///
|
||||
/// [events_cleared]: crate::event::Event::RedrawEventsCleared
|
||||
#[derive(Copy, Clone, Debug, 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. For web, events are sent when
|
||||
/// `requestAnimationFrame` fires.
|
||||
Poll,
|
||||
/// When the current loop iteration finishes, suspend the thread until another event arrives.
|
||||
Wait,
|
||||
/// When the current loop iteration finishes, suspend the thread until either another event
|
||||
/// arrives or the given time is reached.
|
||||
WaitUntil(Instant),
|
||||
/// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set,
|
||||
/// `control_flow` cannot be changed from `Exit`, and any future attempts to do so will result
|
||||
/// in the `control_flow` parameter being reset to `Exit`.
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl Default for ControlFlow {
|
||||
#[inline(always)]
|
||||
fn default() -> ControlFlow {
|
||||
ControlFlow::Poll
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop<()> {
|
||||
/// Builds a new event loop with a `()` as the user event type.
|
||||
///
|
||||
/// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.***
|
||||
/// Attempting to create the event loop on a different 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. `EventLoopExt::new_any_thread` functions are exposed
|
||||
/// in the relevant `platform` module if the target platform supports creating an event loop on
|
||||
/// any thread.
|
||||
///
|
||||
/// Usage will result in display backend initialisation, this can be controlled on linux
|
||||
/// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
|
||||
/// If it is not set, winit will try to connect to a wayland connection, and if it fails will
|
||||
/// fallback on x11. If this variable is set with any other value, winit will panic.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
pub fn new() -> EventLoop<()> {
|
||||
EventLoop::<()>::with_user_event()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoop<T> {
|
||||
/// Builds a new event loop.
|
||||
///
|
||||
/// All caveats documented in [`EventLoop::new`] apply to this function.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
pub fn with_user_event() -> EventLoop<T> {
|
||||
EventLoop {
|
||||
event_loop: platform_impl::EventLoop::new(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Hijacks the calling thread and initializes the winit event loop with the provided
|
||||
/// closure. Since the closure is `'static`, it must be a `move` closure if it needs to
|
||||
/// access any data from the calling context.
|
||||
///
|
||||
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
|
||||
/// event loop's behavior.
|
||||
///
|
||||
/// Any values not passed to this function will *not* be dropped.
|
||||
///
|
||||
/// [`ControlFlow`]: crate::event_loop::ControlFlow
|
||||
#[inline]
|
||||
pub fn run<F>(self, event_handler: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
self.event_loop.run(event_handler)
|
||||
}
|
||||
|
||||
/// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy {
|
||||
event_loop_proxy: self.event_loop.create_proxy(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of all the monitors available on the system.
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
||||
self.event_loop
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(|inner| MonitorHandle { inner })
|
||||
}
|
||||
|
||||
/// Returns the primary monitor of the system.
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle {
|
||||
inner: self.event_loop.primary_monitor(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for EventLoop<T> {
|
||||
type Target = EventLoopWindowTarget<T>;
|
||||
fn deref(&self) -> &EventLoopWindowTarget<T> {
|
||||
self.event_loop.window_target()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
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 `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> {}
|
||||
451
src/events.rs
451
src/events.rs
@@ -1,451 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
use {WindowId, DeviceId};
|
||||
|
||||
/// Describes a generic event.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Event {
|
||||
WindowEvent {
|
||||
window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
},
|
||||
DeviceEvent {
|
||||
device_id: DeviceId,
|
||||
event: DeviceEvent,
|
||||
},
|
||||
Awakened,
|
||||
|
||||
/// The application has been suspended or resumed.
|
||||
///
|
||||
/// The parameter is true if app was suspended, and false if it has been resumed.
|
||||
Suspended(bool),
|
||||
}
|
||||
|
||||
/// Describes an event from a `Window`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum WindowEvent {
|
||||
|
||||
/// The size of the window has changed.
|
||||
Resized(u32, u32),
|
||||
|
||||
/// The position of the window has changed.
|
||||
Moved(i32, i32),
|
||||
|
||||
/// The window has been requested to close.
|
||||
CloseRequested,
|
||||
|
||||
/// The window has been destroyed.
|
||||
Destroyed,
|
||||
|
||||
/// A file has been dropped into the window.
|
||||
DroppedFile(PathBuf),
|
||||
|
||||
/// A file is being hovered over the window.
|
||||
HoveredFile(PathBuf),
|
||||
|
||||
/// A file was hovered, but has exited the window.
|
||||
HoveredFileCancelled,
|
||||
|
||||
/// The window received a unicode character.
|
||||
ReceivedCharacter(char),
|
||||
|
||||
/// The window gained or lost focus.
|
||||
///
|
||||
/// The parameter is true if the window has gained focus, and false if it has lost focus.
|
||||
Focused(bool),
|
||||
|
||||
/// An event from the keyboard has been received.
|
||||
KeyboardInput { device_id: DeviceId, input: KeyboardInput },
|
||||
|
||||
/// The cursor has moved on the window.
|
||||
CursorMoved {
|
||||
device_id: DeviceId,
|
||||
|
||||
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is
|
||||
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
|
||||
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
|
||||
position: (f64, f64),
|
||||
modifiers: ModifiersState
|
||||
},
|
||||
|
||||
/// The cursor has entered the window.
|
||||
CursorEntered { device_id: DeviceId },
|
||||
|
||||
/// The cursor has left the window.
|
||||
CursorLeft { device_id: DeviceId },
|
||||
|
||||
/// A mouse wheel movement or touchpad scroll occurred.
|
||||
MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, modifiers: ModifiersState },
|
||||
|
||||
/// An mouse button press has been received.
|
||||
MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton, modifiers: ModifiersState },
|
||||
|
||||
|
||||
/// Touchpad pressure event.
|
||||
///
|
||||
/// At the moment, only supported on Apple forcetouch-capable macbooks.
|
||||
/// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad
|
||||
/// is being pressed) and stage (integer representing the click level).
|
||||
TouchpadPressure { device_id: DeviceId, pressure: f32, stage: i64 },
|
||||
|
||||
/// Motion on some analog axis. May report data redundant to other, more specific events.
|
||||
AxisMotion { device_id: DeviceId, axis: AxisId, value: f64 },
|
||||
|
||||
/// The window needs to be redrawn.
|
||||
Refresh,
|
||||
|
||||
/// Touch event has been received
|
||||
Touch(Touch),
|
||||
|
||||
/// DPI scaling factor of the window has changed.
|
||||
///
|
||||
/// The following actions cause DPI changes:
|
||||
///
|
||||
/// * A user changes the resolution.
|
||||
/// * A user changes the desktop scaling value (e.g. in Control Panel on Windows).
|
||||
/// * A user moves the application window to a display with a different DPI.
|
||||
HiDPIFactorChanged(f32),
|
||||
}
|
||||
|
||||
/// Represents raw hardware events that are not associated with any particular window.
|
||||
///
|
||||
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
|
||||
/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because
|
||||
/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs
|
||||
/// may not match.
|
||||
///
|
||||
/// Note that these events are delivered regardless of input focus.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DeviceEvent {
|
||||
Added,
|
||||
Removed,
|
||||
|
||||
/// Change in physical position of a pointing device.
|
||||
///
|
||||
/// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`.
|
||||
MouseMotion {
|
||||
/// (x, y) change in position in unspecified units.
|
||||
///
|
||||
/// Different devices may use different units.
|
||||
delta: (f64, f64),
|
||||
},
|
||||
|
||||
/// Physical scroll event
|
||||
MouseWheel {
|
||||
delta: MouseScrollDelta,
|
||||
},
|
||||
|
||||
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
|
||||
/// that winit supports on this platform, including mouse devices. If the device is a mouse
|
||||
/// device then this will be reported alongside the MouseMotion event.
|
||||
Motion { axis: AxisId, value: f64 },
|
||||
|
||||
Button { button: ButtonId, state: ElementState },
|
||||
Key(KeyboardInput),
|
||||
Text { codepoint: char },
|
||||
}
|
||||
|
||||
/// Describes a keyboard input event.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct KeyboardInput {
|
||||
/// Identifies the physical key pressed
|
||||
///
|
||||
/// This should not change if the user adjusts the host's keyboard map. Use when the physical location of the
|
||||
/// key is more important than the key's host GUI semantics, such as for movement controls in a first-person
|
||||
/// game.
|
||||
pub scancode: ScanCode,
|
||||
|
||||
pub state: ElementState,
|
||||
|
||||
/// Identifies the semantic meaning of the key
|
||||
///
|
||||
/// Use when the semantics of the key are more important than the physical location of the key, such as when
|
||||
/// implementing appropriate behavior for "page up."
|
||||
pub virtual_keycode: Option<VirtualKeyCode>,
|
||||
|
||||
/// Modifier keys active at the time of this input.
|
||||
///
|
||||
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
|
||||
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
|
||||
pub modifiers: ModifiersState
|
||||
}
|
||||
|
||||
/// Describes touch-screen input state.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum TouchPhase {
|
||||
Started,
|
||||
Moved,
|
||||
Ended,
|
||||
Cancelled
|
||||
}
|
||||
|
||||
/// Represents touch event
|
||||
///
|
||||
/// Every time user touches screen new Start event with some finger id is generated.
|
||||
/// When the finger is removed from the screen End event with same id is generated.
|
||||
///
|
||||
/// For every id there will be at least 2 events with phases Start and End (or Cancelled).
|
||||
/// There may be 0 or more Move events.
|
||||
///
|
||||
///
|
||||
/// Depending on platform implementation id may or may not be reused by system after End event.
|
||||
///
|
||||
/// Gesture regonizer using this event should assume that Start event received with same id
|
||||
/// as previously received End event is a new finger and has nothing to do with an old one.
|
||||
///
|
||||
/// Touch may be cancelled if for example window lost focus.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Touch {
|
||||
pub device_id: DeviceId,
|
||||
pub phase: TouchPhase,
|
||||
pub location: (f64,f64),
|
||||
/// unique identifier of a finger.
|
||||
pub id: u64
|
||||
}
|
||||
|
||||
/// Hardware-dependent keyboard scan code.
|
||||
pub type ScanCode = u32;
|
||||
|
||||
/// Identifier for a specific analog axis on some device.
|
||||
pub type AxisId = u32;
|
||||
|
||||
/// Identifier for a specific button on some device.
|
||||
pub type ButtonId = u32;
|
||||
|
||||
/// Describes the input state of a key.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum ElementState {
|
||||
Pressed,
|
||||
Released,
|
||||
}
|
||||
|
||||
/// Describes a button of a mouse controller.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Other(u8),
|
||||
}
|
||||
|
||||
/// Describes a difference in the mouse scroll wheel state.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum MouseScrollDelta {
|
||||
/// Amount in lines or rows to scroll in the horizontal
|
||||
/// and vertical directions.
|
||||
///
|
||||
/// Positive values indicate movement forward
|
||||
/// (away from the user) or rightwards.
|
||||
LineDelta(f32, f32),
|
||||
/// Amount in pixels to scroll in the horizontal and
|
||||
/// vertical direction.
|
||||
///
|
||||
/// Scroll events are expressed as a PixelDelta if
|
||||
/// supported by the device (eg. a touchpad) and
|
||||
/// platform.
|
||||
PixelDelta(f32, f32)
|
||||
}
|
||||
|
||||
/// Symbolic name for a keyboard key.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
#[repr(u32)]
|
||||
pub enum VirtualKeyCode {
|
||||
/// The '1' key over the letters.
|
||||
Key1,
|
||||
/// The '2' key over the letters.
|
||||
Key2,
|
||||
/// The '3' key over the letters.
|
||||
Key3,
|
||||
/// The '4' key over the letters.
|
||||
Key4,
|
||||
/// The '5' key over the letters.
|
||||
Key5,
|
||||
/// The '6' key over the letters.
|
||||
Key6,
|
||||
/// The '7' key over the letters.
|
||||
Key7,
|
||||
/// The '8' key over the letters.
|
||||
Key8,
|
||||
/// The '9' key over the letters.
|
||||
Key9,
|
||||
/// The '0' key over the 'O' and 'P' keys.
|
||||
Key0,
|
||||
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
|
||||
/// The Escape key, next to F1.
|
||||
Escape,
|
||||
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
|
||||
/// Print Screen/SysRq.
|
||||
Snapshot,
|
||||
/// Scroll Lock.
|
||||
Scroll,
|
||||
/// Pause/Break key, next to Scroll lock.
|
||||
Pause,
|
||||
|
||||
/// `Insert`, next to Backspace.
|
||||
Insert,
|
||||
Home,
|
||||
Delete,
|
||||
End,
|
||||
PageDown,
|
||||
PageUp,
|
||||
|
||||
Left,
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
|
||||
/// The Backspace key, right over Enter.
|
||||
// TODO: rename
|
||||
Back,
|
||||
/// The Enter key.
|
||||
Return,
|
||||
/// The space bar.
|
||||
Space,
|
||||
|
||||
/// The "Compose" key on Linux.
|
||||
Compose,
|
||||
|
||||
Caret,
|
||||
|
||||
Numlock,
|
||||
Numpad0,
|
||||
Numpad1,
|
||||
Numpad2,
|
||||
Numpad3,
|
||||
Numpad4,
|
||||
Numpad5,
|
||||
Numpad6,
|
||||
Numpad7,
|
||||
Numpad8,
|
||||
Numpad9,
|
||||
|
||||
AbntC1,
|
||||
AbntC2,
|
||||
Add,
|
||||
Apostrophe,
|
||||
Apps,
|
||||
At,
|
||||
Ax,
|
||||
Backslash,
|
||||
Calculator,
|
||||
Capital,
|
||||
Colon,
|
||||
Comma,
|
||||
Convert,
|
||||
Decimal,
|
||||
Divide,
|
||||
Equals,
|
||||
Grave,
|
||||
Kana,
|
||||
Kanji,
|
||||
LAlt,
|
||||
LBracket,
|
||||
LControl,
|
||||
LMenu,
|
||||
LShift,
|
||||
LWin,
|
||||
Mail,
|
||||
MediaSelect,
|
||||
MediaStop,
|
||||
Minus,
|
||||
Multiply,
|
||||
Mute,
|
||||
MyComputer,
|
||||
NavigateForward, // also called "Prior"
|
||||
NavigateBackward, // also called "Next"
|
||||
NextTrack,
|
||||
NoConvert,
|
||||
NumpadComma,
|
||||
NumpadEnter,
|
||||
NumpadEquals,
|
||||
OEM102,
|
||||
Period,
|
||||
PlayPause,
|
||||
Power,
|
||||
PrevTrack,
|
||||
RAlt,
|
||||
RBracket,
|
||||
RControl,
|
||||
RMenu,
|
||||
RShift,
|
||||
RWin,
|
||||
Semicolon,
|
||||
Slash,
|
||||
Sleep,
|
||||
Stop,
|
||||
Subtract,
|
||||
Sysrq,
|
||||
Tab,
|
||||
Underline,
|
||||
Unlabeled,
|
||||
VolumeDown,
|
||||
VolumeUp,
|
||||
Wake,
|
||||
WebBack,
|
||||
WebFavorites,
|
||||
WebForward,
|
||||
WebHome,
|
||||
WebRefresh,
|
||||
WebSearch,
|
||||
WebStop,
|
||||
Yen,
|
||||
}
|
||||
|
||||
/// Represents the current state of the keyboard modifiers
|
||||
///
|
||||
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
|
||||
#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct ModifiersState {
|
||||
/// The "shift" key
|
||||
pub shift: bool,
|
||||
/// The "control" key
|
||||
pub ctrl: bool,
|
||||
/// The "alt" key
|
||||
pub alt: bool,
|
||||
/// The "logo" key
|
||||
///
|
||||
/// This is the "windows" key on PC and "command" key on Mac.
|
||||
pub logo: bool
|
||||
}
|
||||
135
src/icon.rs
Normal file
135
src/icon.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
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 ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
|
||||
byte_count,
|
||||
),
|
||||
BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height,
|
||||
pixel_count,
|
||||
} => write!(f,
|
||||
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
|
||||
width, height, pixel_count, width_x_height,
|
||||
),
|
||||
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadIcon {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
/// 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> {
|
||||
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> {
|
||||
Ok(Icon {
|
||||
inner: PlatformIcon::from_rgba(rgba, width, height)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
546
src/lib.rs
546
src/lib.rs
@@ -1,462 +1,162 @@
|
||||
//! Winit allows you to build a window on as many platforms as possible.
|
||||
//! Winit is a cross-platform window creation and event loop management library.
|
||||
//!
|
||||
//! # Building a window
|
||||
//! # Building windows
|
||||
//!
|
||||
//! Before you can build a window, you first need to build an `EventsLoop`. This is done with the
|
||||
//! `EventsLoop::new()` function. Example:
|
||||
//! Before you can build a [`Window`], you first need to build an [`EventLoop`]. This is done with the
|
||||
//! [`EventLoop::new()`] function.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use winit::EventsLoop;
|
||||
//! let events_loop = EventsLoop::new();
|
||||
//! use winit::event_loop::EventLoop;
|
||||
//! let event_loop = EventLoop::new();
|
||||
//! ```
|
||||
//!
|
||||
//! Once this is done there are two ways to create a window:
|
||||
//! Once this is done there are two ways to create a [`Window`]:
|
||||
//!
|
||||
//! - Calling `Window::new(&events_loop)`.
|
||||
//! - Calling `let builder = WindowBuilder::new()` then `builder.build(&events_loop)`.
|
||||
//! - Calling [`Window::new(&event_loop)`][window_new].
|
||||
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
|
||||
//!
|
||||
//! The first way is the simpliest way and will give you default values for everything.
|
||||
//! The first method is the simplest, and will give you default values for everything. The second
|
||||
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
|
||||
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
|
||||
//!
|
||||
//! The second way allows you to customize the way your window will look and behave by modifying
|
||||
//! the fields of the `WindowBuilder` object before you create the window.
|
||||
//! # Event handling
|
||||
//!
|
||||
//! # Events 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 [`UserEvent`]s, if desired.
|
||||
//!
|
||||
//! Once a window has been created, it will *generate events*. For example whenever the user moves
|
||||
//! the window, resizes the window, moves the mouse, etc. an event is generated.
|
||||
//! You can retreive events by calling [`EventLoop::run`][event_loop_run]. This function will
|
||||
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
|
||||
//! will run until the `control_flow` argument given to the closure is set to
|
||||
//! [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] is emitted and the
|
||||
//! entire program terminates.
|
||||
//!
|
||||
//! The events generated by a window can be retreived from the `EventsLoop` the window was created
|
||||
//! with.
|
||||
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
||||
//! model, since that can't be implemented properly on web and mobile platforms and works poorly on
|
||||
//! most desktop platforms. However, this model can be re-implemented to an extent on desktops with
|
||||
//! [`EventLoopExtDesktop::run_return`]. See that method's documentation for more reasons about why
|
||||
//! it's discouraged, beyond mobile/web compatibility reasons.
|
||||
//!
|
||||
//! There are two ways to do so. The first is to call `events_loop.poll_events(...)`, which will
|
||||
//! retreive all the events pending on the windows and immediately return after no new event is
|
||||
//! available. You usually want to use this method in application that render continuously on the
|
||||
//! screen, such as video games.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use winit::{Event, WindowEvent};
|
||||
//! # use winit::EventsLoop;
|
||||
//! # let mut events_loop = EventsLoop::new();
|
||||
//! use winit::{
|
||||
//! event::{Event, WindowEvent},
|
||||
//! event_loop::{ControlFlow, EventLoop},
|
||||
//! window::WindowBuilder,
|
||||
//! };
|
||||
//!
|
||||
//! loop {
|
||||
//! events_loop.poll_events(|event| {
|
||||
//! match event {
|
||||
//! Event::WindowEvent { event: WindowEvent::Resized(w, h), .. } => {
|
||||
//! println!("The window was resized to {}x{}", w, h);
|
||||
//! },
|
||||
//! _ => ()
|
||||
//! }
|
||||
//! });
|
||||
//! }
|
||||
//! ```
|
||||
//! let event_loop = EventLoop::new();
|
||||
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
//!
|
||||
//! The second way is to call `events_loop.run_forever(...)`. As its name tells, it will run
|
||||
//! forever unless it is stopped by returning `ControlFlow::Break`.
|
||||
//! event_loop.run(move |event, _, control_flow| {
|
||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
||||
//! // dispatched any events. This is ideal for games and similar applications.
|
||||
//! *control_flow = ControlFlow::Poll;
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use winit::{ControlFlow, Event, WindowEvent};
|
||||
//! # use winit::EventsLoop;
|
||||
//! # let mut events_loop = EventsLoop::new();
|
||||
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
|
||||
//! // This is ideal for non-game applications that only update in response to user
|
||||
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
||||
//! *control_flow = ControlFlow::Wait;
|
||||
//!
|
||||
//! events_loop.run_forever(|event| {
|
||||
//! match event {
|
||||
//! Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
|
||||
//! Event::WindowEvent {
|
||||
//! event: WindowEvent::CloseRequested,
|
||||
//! ..
|
||||
//! } => {
|
||||
//! println!("The close button was pressed; stopping");
|
||||
//! ControlFlow::Break
|
||||
//! *control_flow = ControlFlow::Exit
|
||||
//! },
|
||||
//! _ => ControlFlow::Continue,
|
||||
//! Event::MainEventsCleared => {
|
||||
//! // Application update code.
|
||||
//!
|
||||
//! // 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 just render here instead.
|
||||
//! window.request_redraw();
|
||||
//! },
|
||||
//! Event::RedrawRequested(_) => {
|
||||
//! // Redraw the application.
|
||||
//! //
|
||||
//! // It's preferable for applications that do not render continuously to render in
|
||||
//! // this event rather than in MainEventsCleared, since rendering in here allows
|
||||
//! // the program to gracefully handle redraws requested by the OS.
|
||||
//! },
|
||||
//! _ => ()
|
||||
//! }
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! If you use multiple windows, the `WindowEvent` event has a member named `window_id`. You can
|
||||
//! compare it with the value returned by the `id()` method of `Window` in order to know which
|
||||
//! window has received the event.
|
||||
//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
|
||||
//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`]
|
||||
//! dispatched the event.
|
||||
//!
|
||||
//! # Drawing on the window
|
||||
//!
|
||||
//! Winit doesn't provide any function that allows drawing on a window. However it allows you to
|
||||
//! retreive the raw handle of the window (see the `os` module for that), which in turn allows you
|
||||
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw 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 (see the [`platform`] module and/or the
|
||||
//! [`raw_window_handle`] method), 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::WindowBuilder::with_visible) and explicitly make the
|
||||
//! window visible only once you're ready to render into it.
|
||||
//!
|
||||
//! [`EventLoop`]: event_loop::EventLoop
|
||||
//! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return
|
||||
//! [`EventLoop::new()`]: event_loop::EventLoop::new
|
||||
//! [event_loop_run]: event_loop::EventLoop::run
|
||||
//! [`ControlFlow`]: event_loop::ControlFlow
|
||||
//! [`Exit`]: event_loop::ControlFlow::Exit
|
||||
//! [`Window`]: window::Window
|
||||
//! [`WindowId`]: window::WindowId
|
||||
//! [`WindowBuilder`]: window::WindowBuilder
|
||||
//! [window_new]: window::Window::new
|
||||
//! [window_builder_new]: window::WindowBuilder::new
|
||||
//! [window_builder_build]: window::WindowBuilder::build
|
||||
//! [window_id_fn]: window::Window::id
|
||||
//! [`Event`]: event::Event
|
||||
//! [`WindowEvent`]: event::WindowEvent
|
||||
//! [`DeviceEvent`]: event::DeviceEvent
|
||||
//! [`UserEvent`]: event::Event::UserEvent
|
||||
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
|
||||
//! [`platform`]: platform
|
||||
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "windows"))]
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
extern crate libc;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate winapi;
|
||||
extern crate log;
|
||||
#[cfg(feature = "serde")]
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
#[cfg(target_os = "macos")]
|
||||
extern crate cocoa;
|
||||
#[cfg(target_os = "macos")]
|
||||
extern crate core_foundation;
|
||||
#[cfg(target_os = "macos")]
|
||||
extern crate core_graphics;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
extern crate x11_dl;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
extern crate percent_encoding;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
|
||||
#[cfg(all(target_arch = "wasm32", feature = "std_web"))]
|
||||
extern crate std_web as stdweb;
|
||||
|
||||
pub mod dpi;
|
||||
#[macro_use]
|
||||
extern crate wayland_client;
|
||||
pub mod error;
|
||||
pub mod event;
|
||||
pub mod event_loop;
|
||||
mod icon;
|
||||
pub mod monitor;
|
||||
mod platform_impl;
|
||||
pub mod window;
|
||||
|
||||
pub use events::*;
|
||||
pub use window::{AvailableMonitorsIter, MonitorId};
|
||||
|
||||
mod platform;
|
||||
mod events;
|
||||
mod window;
|
||||
|
||||
pub mod os;
|
||||
|
||||
/// Represents a window.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use winit::{Event, EventsLoop, Window, WindowEvent, ControlFlow};
|
||||
///
|
||||
/// let mut events_loop = EventsLoop::new();
|
||||
/// let window = Window::new(&events_loop).unwrap();
|
||||
///
|
||||
/// events_loop.run_forever(|event| {
|
||||
/// match event {
|
||||
/// Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
|
||||
/// ControlFlow::Break
|
||||
/// },
|
||||
/// _ => ControlFlow::Continue,
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
pub struct Window {
|
||||
window: platform::Window,
|
||||
}
|
||||
|
||||
/// Identifier of a window. Unique for each window.
|
||||
///
|
||||
/// Can be obtained with `window.id()`.
|
||||
///
|
||||
/// Whenever you receive an event specific to a window, this event contains a `WindowId` which you
|
||||
/// can then compare to the ids of your windows.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(platform::WindowId);
|
||||
|
||||
/// Identifier of an input device.
|
||||
///
|
||||
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
|
||||
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
|
||||
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(platform::DeviceId);
|
||||
|
||||
/// Provides a way to retreive events from the system and from the windows that were registered to
|
||||
/// the events loop.
|
||||
///
|
||||
/// An `EventsLoop` can be seen more or less as a "context". Calling `EventsLoop::new()`
|
||||
/// initializes everything that will be required to create windows. For example on Linux creating
|
||||
/// an events loop opens a connection to the X or Wayland server.
|
||||
///
|
||||
/// To wake up an `EventsLoop` from a another thread, see the `EventsLoopProxy` docs.
|
||||
///
|
||||
/// Note that the `EventsLoop` cannot be shared accross threads (due to platform-dependant logic
|
||||
/// forbiding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the
|
||||
/// `Window` created from this `EventsLoop` _can_ be sent to an other thread, and the
|
||||
/// `EventsLoopProxy` allows you to wakeup an `EventsLoop` from an other thread.
|
||||
pub struct EventsLoop {
|
||||
events_loop: platform::EventsLoop,
|
||||
_marker: ::std::marker::PhantomData<*mut ()> // Not Send nor Sync
|
||||
}
|
||||
|
||||
/// Returned by the user callback given to the `EventsLoop::run_forever` method.
|
||||
///
|
||||
/// Indicates whether the `run_forever` method should continue or complete.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ControlFlow {
|
||||
/// Continue looping and waiting for events.
|
||||
Continue,
|
||||
/// Break from the event loop.
|
||||
Break,
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
/// Builds a new events loop.
|
||||
///
|
||||
/// Usage will result in display backend initialisation, this can be controlled on linux
|
||||
/// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
|
||||
/// If it is not set, winit will try to connect to a wayland connection, and if it fails will
|
||||
/// fallback on x11. If this variable is set with any other value, winit will panic.
|
||||
pub fn new() -> EventsLoop {
|
||||
EventsLoop {
|
||||
events_loop: platform::EventsLoop::new(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of all the monitors available on the system.
|
||||
///
|
||||
// Note: should be replaced with `-> impl Iterator` once stable.
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> AvailableMonitorsIter {
|
||||
let data = self.events_loop.get_available_monitors();
|
||||
AvailableMonitorsIter{ data: data.into_iter() }
|
||||
}
|
||||
|
||||
/// Returns the primary monitor of the system.
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId { inner: self.events_loop.get_primary_monitor() }
|
||||
}
|
||||
|
||||
/// Fetches all the events that are pending, calls the callback function for each of them,
|
||||
/// and returns.
|
||||
#[inline]
|
||||
pub fn poll_events<F>(&mut self, callback: F)
|
||||
where F: FnMut(Event)
|
||||
{
|
||||
self.events_loop.poll_events(callback)
|
||||
}
|
||||
|
||||
/// Calls `callback` every time an event is received. If no event is available, sleeps the
|
||||
/// current thread and waits for an event. If the callback returns `ControlFlow::Break` then
|
||||
/// `run_forever` will immediately return.
|
||||
#[inline]
|
||||
pub fn run_forever<F>(&mut self, callback: F)
|
||||
where F: FnMut(Event) -> ControlFlow
|
||||
{
|
||||
self.events_loop.run_forever(callback)
|
||||
}
|
||||
|
||||
/// Creates an `EventsLoopProxy` that can be used to wake up the `EventsLoop` from another
|
||||
/// thread.
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
EventsLoopProxy {
|
||||
events_loop_proxy: self.events_loop.create_proxy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to wake up the `EventsLoop` from another thread.
|
||||
#[derive(Clone)]
|
||||
pub struct EventsLoopProxy {
|
||||
events_loop_proxy: platform::EventsLoopProxy,
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
/// Wake up the `EventsLoop` from which this proxy was created.
|
||||
///
|
||||
/// This causes the `EventsLoop` to emit an `Awakened` event.
|
||||
///
|
||||
/// Returns an `Err` if the associated `EventsLoop` no longer exists.
|
||||
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
|
||||
self.events_loop_proxy.wakeup()
|
||||
}
|
||||
}
|
||||
|
||||
/// The error that is returned when an `EventsLoopProxy` attempts to wake up an `EventsLoop` that
|
||||
/// no longer exists.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct EventsLoopClosed;
|
||||
|
||||
impl std::fmt::Display for EventsLoopClosed {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", std::error::Error::description(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for EventsLoopClosed {
|
||||
fn description(&self) -> &str {
|
||||
"Tried to wake up a closed `EventsLoop`"
|
||||
}
|
||||
}
|
||||
|
||||
/// Object that allows you to build windows.
|
||||
#[derive(Clone)]
|
||||
pub struct WindowBuilder {
|
||||
/// The attributes to use to create the window.
|
||||
pub window: WindowAttributes,
|
||||
|
||||
// Platform-specific configuration. Private.
|
||||
platform_specific: platform::PlatformSpecificWindowBuilderAttributes,
|
||||
}
|
||||
|
||||
/// Error that can happen while creating a window or a headless renderer.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CreationError {
|
||||
OsError(String),
|
||||
/// TODO: remove this error
|
||||
NotSupported,
|
||||
}
|
||||
|
||||
impl CreationError {
|
||||
fn to_string(&self) -> &str {
|
||||
match *self {
|
||||
CreationError::OsError(ref text) => &text,
|
||||
CreationError::NotSupported => "Some of the requested attributes are not supported",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CreationError {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
formatter.write_str(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CreationError {
|
||||
fn description(&self) -> &str {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the appearance of the mouse cursor.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum MouseCursor {
|
||||
/// The platform-dependent default cursor.
|
||||
Default,
|
||||
/// A simple crosshair.
|
||||
Crosshair,
|
||||
/// A hand (often used to indicate links in web browsers).
|
||||
Hand,
|
||||
/// Self explanatory.
|
||||
Arrow,
|
||||
/// Indicates something is to be moved.
|
||||
Move,
|
||||
/// Indicates text that may be selected or edited.
|
||||
Text,
|
||||
/// Program busy indicator.
|
||||
Wait,
|
||||
/// Help indicator (often rendered as a "?")
|
||||
Help,
|
||||
/// Progress indicator. Shows that processing is being done. But in contrast
|
||||
/// with "Wait" the user may still interact with the program. Often rendered
|
||||
/// as a spinning beach ball, or an arrow with a watch or hourglass.
|
||||
Progress,
|
||||
|
||||
/// Cursor showing that something cannot be done.
|
||||
NotAllowed,
|
||||
ContextMenu,
|
||||
NoneCursor,
|
||||
Cell,
|
||||
VerticalText,
|
||||
Alias,
|
||||
Copy,
|
||||
NoDrop,
|
||||
Grab,
|
||||
Grabbing,
|
||||
AllScroll,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
|
||||
/// Indicate that some edge is to be moved. For example, the 'SeResize' cursor
|
||||
/// is used when the movement starts from the south-east corner of the box.
|
||||
EResize,
|
||||
NResize,
|
||||
NeResize,
|
||||
NwResize,
|
||||
SResize,
|
||||
SeResize,
|
||||
SwResize,
|
||||
WResize,
|
||||
EwResize,
|
||||
NsResize,
|
||||
NeswResize,
|
||||
NwseResize,
|
||||
ColResize,
|
||||
RowResize,
|
||||
}
|
||||
|
||||
/// Describes how winit handles the cursor.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum CursorState {
|
||||
/// Normal cursor behavior.
|
||||
Normal,
|
||||
|
||||
/// The cursor will be invisible when over the window.
|
||||
Hide,
|
||||
|
||||
/// Grabs the mouse cursor. The cursor's motion will be confined to this
|
||||
/// window and the window has exclusive access to further events regarding
|
||||
/// the cursor.
|
||||
///
|
||||
/// This is useful for first-person cameras for example.
|
||||
Grab,
|
||||
}
|
||||
|
||||
/// Attributes to use when creating a window.
|
||||
#[derive(Clone)]
|
||||
pub struct WindowAttributes {
|
||||
/// The dimensions of the window. If this is `None`, some platform-specific dimensions will be
|
||||
/// used.
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub dimensions: Option<(u32, u32)>,
|
||||
|
||||
/// The minimum dimensions a window can be, If this is `None`, the window will have no minimum dimensions (aside from reserved).
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub min_dimensions: Option<(u32, u32)>,
|
||||
|
||||
/// The maximum dimensions a window can be, If this is `None`, the maximum will have no maximum or will be set to the primary monitor's dimensions by the platform.
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub max_dimensions: Option<(u32, u32)>,
|
||||
|
||||
/// Whether the window should be set as fullscreen upon creation.
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub fullscreen: Option<MonitorId>,
|
||||
|
||||
/// The title of the window in the title bar.
|
||||
///
|
||||
/// The default is `"winit window"`.
|
||||
pub title: String,
|
||||
|
||||
/// Whether the window should be maximized upon creation.
|
||||
///
|
||||
/// The default is `false`.
|
||||
pub maximized: bool,
|
||||
|
||||
/// Whether the window should be immediately visible upon creation.
|
||||
///
|
||||
/// The default is `true`.
|
||||
pub visible: bool,
|
||||
|
||||
/// Whether the the window should be transparent. If this is true, writing colors
|
||||
/// with alpha values different than `1.0` will produce a transparent window.
|
||||
///
|
||||
/// The default is `false`.
|
||||
pub transparent: bool,
|
||||
|
||||
/// Whether the window should have borders and bars.
|
||||
///
|
||||
/// The default is `true`.
|
||||
pub decorations: bool,
|
||||
|
||||
/// [iOS only] Enable multitouch,
|
||||
/// see [multipleTouchEnabled](https://developer.apple.com/documentation/uikit/uiview/1622519-multipletouchenabled)
|
||||
pub multitouch: bool,
|
||||
}
|
||||
|
||||
impl Default for WindowAttributes {
|
||||
#[inline]
|
||||
fn default() -> WindowAttributes {
|
||||
WindowAttributes {
|
||||
dimensions: None,
|
||||
min_dimensions: None,
|
||||
max_dimensions: None,
|
||||
title: "winit window".to_owned(),
|
||||
maximized: false,
|
||||
fullscreen: None,
|
||||
visible: true,
|
||||
transparent: false,
|
||||
decorations: true,
|
||||
multitouch: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub mod platform;
|
||||
|
||||
174
src/monitor.rs
Normal file
174
src/monitor.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
//! Types useful for interacting with a user's monitors.
|
||||
//!
|
||||
//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle]
|
||||
//! type. This is retreived from one of the following methods, which return an iterator of
|
||||
//! [`MonitorHandle`][monitor_handle]:
|
||||
//! - [`EventLoop::available_monitors`][loop_get]
|
||||
//! - [`Window::available_monitors`][window_get].
|
||||
//!
|
||||
//! [monitor_handle]: crate::monitor::MonitorHandle
|
||||
//! [loop_get]: crate::event_loop::EventLoop::available_monitors
|
||||
//! [window_get]: crate::window::Window::available_monitors
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
platform_impl,
|
||||
};
|
||||
|
||||
/// Describes a fullscreen video mode of a monitor.
|
||||
///
|
||||
/// Can be acquired with:
|
||||
/// - [`MonitorHandle::video_modes`][monitor_get].
|
||||
///
|
||||
/// [monitor_get]: crate::monitor::MonitorHandle::video_modes
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) video_mode: platform_impl::VideoMode,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VideoMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.video_mode.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for VideoMode {
|
||||
fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for VideoMode {
|
||||
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
|
||||
// TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32`
|
||||
// to `u32` there
|
||||
let size: (u32, u32) = self.size().into();
|
||||
let other_size: (u32, u32) = other.size().into();
|
||||
self.monitor().cmp(&other.monitor()).then(
|
||||
size.cmp(&other_size)
|
||||
.then(
|
||||
self.refresh_rate()
|
||||
.cmp(&other.refresh_rate())
|
||||
.then(self.bit_depth().cmp(&other.bit_depth())),
|
||||
)
|
||||
.reverse(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
/// 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:** 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. **Note**: the returned
|
||||
/// refresh rate is an integer approximation, and you shouldn't rely on this
|
||||
/// value to be exact.
|
||||
#[inline]
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.video_mode.refresh_rate()
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
self.video_mode.monitor()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VideoMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}x{} @ {} Hz ({} bpp)",
|
||||
self.size().width,
|
||||
self.size().height,
|
||||
self.refresh_rate(),
|
||||
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.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Always returns None
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
self.inner.name()
|
||||
}
|
||||
|
||||
/// Returns the monitor's resolution.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Always returns (0,0)
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.inner.size()
|
||||
}
|
||||
|
||||
/// Returns the top-left corner position of the monitor relative to the larger full
|
||||
/// screen area.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Always returns (0,0)
|
||||
#[inline]
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
self.inner.position()
|
||||
}
|
||||
|
||||
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
///
|
||||
/// See the [`dpi`](crate::dpi) module for more information.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
|
||||
/// - **Android:** Always returns 1.0.
|
||||
/// - **Web:** Always returns 1.0
|
||||
#[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 = VideoMode> {
|
||||
self.inner.video_modes()
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#![cfg(any(target_os = "android"))]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
use EventsLoop;
|
||||
use Window;
|
||||
use WindowBuilder;
|
||||
|
||||
/// Additional methods on `EventsLoop` that are specific to Android.
|
||||
pub trait EventsLoopExt {
|
||||
/// Makes it possible for glutin to register a callback when a suspend event happens on Android
|
||||
fn set_suspend_callback(&self, cb: Option<Box<Fn(bool) -> ()>>);
|
||||
}
|
||||
|
||||
impl EventsLoopExt for EventsLoop {
|
||||
fn set_suspend_callback(&self, cb: Option<Box<Fn(bool) -> ()>>) {
|
||||
self.events_loop.set_suspend_callback(cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Android.
|
||||
pub trait WindowExt {
|
||||
fn get_native_window(&self) -> *const c_void;
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
#[inline]
|
||||
fn get_native_window(&self) -> *const c_void {
|
||||
self.window.get_native_window()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Android.
|
||||
pub trait WindowBuilderExt {
|
||||
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
}
|
||||
151
src/os/macos.rs
151
src/os/macos.rs
@@ -1,151 +0,0 @@
|
||||
#![cfg(target_os = "macos")]
|
||||
|
||||
use std::convert::From;
|
||||
use std::os::raw::c_void;
|
||||
use cocoa::appkit::NSApplicationActivationPolicy;
|
||||
use {MonitorId, Window, WindowBuilder};
|
||||
|
||||
/// Additional methods on `Window` that are specific to MacOS.
|
||||
pub trait WindowExt {
|
||||
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
fn get_nswindow(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the cocoa `NSView` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
fn get_nsview(&self) -> *mut c_void;
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
#[inline]
|
||||
fn get_nswindow(&self) -> *mut c_void {
|
||||
self.window.get_nswindow()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_nsview(&self) -> *mut c_void {
|
||||
self.window.get_nsview()
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ActivationPolicy {
|
||||
/// Corresponds to `NSApplicationActivationPolicyRegular`.
|
||||
Regular,
|
||||
/// Corresponds to `NSApplicationActivationPolicyAccessory`.
|
||||
Accessory,
|
||||
/// Corresponds to `NSApplicationActivationPolicyProhibited`.
|
||||
Prohibited,
|
||||
}
|
||||
|
||||
impl Default for ActivationPolicy {
|
||||
fn default() -> Self {
|
||||
ActivationPolicy::Regular
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ActivationPolicy> for NSApplicationActivationPolicy {
|
||||
fn from(activation_policy: ActivationPolicy) -> Self {
|
||||
match activation_policy {
|
||||
ActivationPolicy::Regular =>
|
||||
NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
|
||||
ActivationPolicy::Accessory =>
|
||||
NSApplicationActivationPolicy::NSApplicationActivationPolicyAccessory,
|
||||
ActivationPolicy::Prohibited =>
|
||||
NSApplicationActivationPolicy::NSApplicationActivationPolicyProhibited,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to MacOS.
|
||||
///
|
||||
/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method
|
||||
/// on the base `WindowBuilder`:
|
||||
///
|
||||
/// - `with_titlebar_transparent`
|
||||
/// - `with_title_hidden`
|
||||
/// - `with_titlebar_hidden`
|
||||
/// - `with_titlebar_buttons_hidden`
|
||||
/// - `with_fullsize_content_view`
|
||||
pub trait WindowBuilderExt {
|
||||
fn with_activation_policy(self, activation_policy: ActivationPolicy) -> WindowBuilder;
|
||||
fn with_movable_by_window_background(self, movable_by_window_background: bool) -> WindowBuilder;
|
||||
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder;
|
||||
fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder;
|
||||
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder;
|
||||
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
|
||||
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
/// Sets the activation policy for the window being built
|
||||
#[inline]
|
||||
fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder {
|
||||
self.platform_specific.activation_policy = activation_policy;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables click-and-drag behavior for the entire window, not just the titlebar
|
||||
#[inline]
|
||||
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> WindowBuilder {
|
||||
self.platform_specific.movable_by_window_background = movable_by_window_background;
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the titlebar transparent and allows the content to appear behind it
|
||||
#[inline]
|
||||
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder {
|
||||
self.platform_specific.titlebar_transparent = titlebar_transparent;
|
||||
self
|
||||
}
|
||||
|
||||
/// Hides the window titlebar
|
||||
#[inline]
|
||||
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.titlebar_hidden = titlebar_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
/// Hides the window titlebar buttons
|
||||
#[inline]
|
||||
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
/// Hides the window title
|
||||
#[inline]
|
||||
fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.title_hidden = title_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the window content appear behind the titlebar
|
||||
#[inline]
|
||||
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder {
|
||||
self.platform_specific.fullsize_content_view = fullsize_content_view;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorId` that are specific to MacOS.
|
||||
pub trait MonitorIdExt {
|
||||
/// Returns the identifier of the monitor for Cocoa.
|
||||
fn native_id(&self) -> u32;
|
||||
/// Returns a pointer to the NSScreen representing this monitor.
|
||||
fn get_nsscreen(&self) -> Option<*mut c_void>;
|
||||
}
|
||||
|
||||
impl MonitorIdExt for MonitorId {
|
||||
#[inline]
|
||||
fn native_id(&self) -> u32 {
|
||||
self.inner.get_native_identifier()
|
||||
}
|
||||
|
||||
fn get_nsscreen(&self) -> Option<*mut c_void> {
|
||||
self.inner.get_nsscreen().map(|s| s as *mut c_void)
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
//! Contains traits with platform-specific methods in them.
|
||||
//!
|
||||
//! Contains the follow modules:
|
||||
//!
|
||||
//! - `android`
|
||||
//! - `macos`
|
||||
//! - `unix`
|
||||
//! - `windows`
|
||||
//!
|
||||
//! However only the module corresponding to the platform you're compiling to will be available.
|
||||
//!
|
||||
pub mod android;
|
||||
pub mod macos;
|
||||
pub mod unix;
|
||||
pub mod windows;
|
||||
231
src/os/unix.rs
231
src/os/unix.rs
@@ -1,231 +0,0 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
|
||||
use std::os::raw;
|
||||
use std::sync::Arc;
|
||||
use std::ptr;
|
||||
use EventsLoop;
|
||||
use MonitorId;
|
||||
use Window;
|
||||
use platform::EventsLoop as LinuxEventsLoop;
|
||||
use platform::Window as LinuxWindow;
|
||||
use WindowBuilder;
|
||||
use platform::x11::XConnection;
|
||||
use platform::x11::ffi::XVisualInfo;
|
||||
|
||||
// TODO: stupid hack so that glutin can do its work
|
||||
#[doc(hidden)]
|
||||
pub use platform::x11;
|
||||
|
||||
pub use platform::XNotSupported;
|
||||
|
||||
/// Additional methods on `EventsLoop` that are specific to Linux.
|
||||
pub trait EventsLoopExt {
|
||||
/// Builds a new `EventsLoop` that is forced to use X11.
|
||||
fn new_x11() -> Result<Self, XNotSupported>
|
||||
where Self: Sized;
|
||||
|
||||
/// Builds a new `EventsLoop` that is forced to use Wayland.
|
||||
fn new_wayland() -> Self
|
||||
where Self: Sized;
|
||||
|
||||
/// True if the `EventsLoop` uses Wayland.
|
||||
fn is_wayland(&self) -> bool;
|
||||
|
||||
/// True if the `EventsLoop` uses X11.
|
||||
fn is_x11(&self) -> bool;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
}
|
||||
|
||||
impl EventsLoopExt for EventsLoop {
|
||||
#[inline]
|
||||
fn new_x11() -> Result<Self, XNotSupported> {
|
||||
LinuxEventsLoop::new_x11().map(|ev|
|
||||
EventsLoop {
|
||||
events_loop: ev,
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_wayland() -> Self {
|
||||
EventsLoop {
|
||||
events_loop: match LinuxEventsLoop::new_wayland() {
|
||||
Ok(e) => e,
|
||||
Err(_) => panic!() // TODO: propagate
|
||||
},
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_wayland(&self) -> bool {
|
||||
self.events_loop.is_wayland()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_x11(&self) -> bool {
|
||||
!self.events_loop.is_wayland()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
self.events_loop.x_connection().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Unix.
|
||||
pub trait WindowExt {
|
||||
/// Returns the ID of the `Window` xlib object that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
fn get_xlib_window(&self) -> Option<raw::c_ulong>;
|
||||
|
||||
/// Returns a pointer to the `Display` object of xlib that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn get_xlib_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
fn get_xlib_screen_id(&self) -> Option<raw::c_int>;
|
||||
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
|
||||
fn send_xim_spot(&self, x: i16, y: i16);
|
||||
|
||||
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn get_xcb_connection(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn get_wayland_surface(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Check if the window is ready for drawing
|
||||
///
|
||||
/// It is a remnant of a previous implementation detail for the
|
||||
/// wayland backend, and is no longer relevant.
|
||||
///
|
||||
/// Always return true.
|
||||
#[deprecated]
|
||||
fn is_ready(&self) -> bool;
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
#[inline]
|
||||
fn get_xlib_window(&self) -> Option<raw::c_ulong> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xlib_window()),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_xlib_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xlib_display()),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_xlib_screen_id(&self) -> Option<raw::c_int> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xlib_screen_id()),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xlib_xconnection()),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_xcb_connection(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xcb_connection()),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn send_xim_spot(&self, x: i16, y: i16) {
|
||||
if let LinuxWindow::X(ref w) = self.window {
|
||||
w.send_xim_spot(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_wayland_surface(&self) -> Option<*mut raw::c_void> {
|
||||
use wayland_client::Proxy;
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.get_surface().ptr() as *mut _),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
use wayland_client::Proxy;
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.get_display().ptr() as *mut _),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_ready(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Unix.
|
||||
pub trait WindowBuilderExt {
|
||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> WindowBuilder;
|
||||
fn with_x11_screen(self, screen_id: i32) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> WindowBuilder {
|
||||
self.platform_specific.visual_infos = Some(
|
||||
unsafe { ptr::read(visual_infos as *const XVisualInfo) }
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_x11_screen(mut self, screen_id: i32) -> WindowBuilder {
|
||||
self.platform_specific.screen_id = Some(screen_id);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorId` that are specific to Linux.
|
||||
pub trait MonitorIdExt {
|
||||
/// Returns the inner identifier of the monitor.
|
||||
fn native_id(&self) -> u32;
|
||||
}
|
||||
|
||||
impl MonitorIdExt for MonitorId {
|
||||
#[inline]
|
||||
fn native_id(&self) -> u32 {
|
||||
self.inner.get_native_identifier()
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
use libc;
|
||||
use MonitorId;
|
||||
use Window;
|
||||
use WindowBuilder;
|
||||
use winapi::shared::windef::HWND;
|
||||
|
||||
/// Additional methods on `Window` that are specific to Windows.
|
||||
pub trait WindowExt {
|
||||
/// Returns the native handle that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the native window was destroyed.
|
||||
fn get_hwnd(&self) -> *mut libc::c_void;
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
#[inline]
|
||||
fn get_hwnd(&self) -> *mut libc::c_void {
|
||||
self.window.hwnd() as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Windows.
|
||||
pub trait WindowBuilderExt {
|
||||
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
/// Sets a parent to the window to be created.
|
||||
#[inline]
|
||||
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
|
||||
self.platform_specific.parent = Some(parent);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorId` that are specific to Windows.
|
||||
pub trait MonitorIdExt {
|
||||
/// 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) -> *mut c_void;
|
||||
}
|
||||
|
||||
impl MonitorIdExt for MonitorId {
|
||||
#[inline]
|
||||
fn native_id(&self) -> String {
|
||||
self.inner.get_native_identifier()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hmonitor(&self) -> *mut c_void {
|
||||
self.inner.get_hmonitor() as *mut _
|
||||
}
|
||||
}
|
||||
40
src/platform/android.rs
Normal file
40
src/platform/android.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
#![cfg(any(target_os = "android"))]
|
||||
|
||||
use crate::{
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
use ndk::configuration::Configuration;
|
||||
use ndk_glue::Rect;
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Android.
|
||||
pub trait EventLoopExtAndroid {}
|
||||
|
||||
impl<T> EventLoopExtAndroid for EventLoop<T> {}
|
||||
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to Android.
|
||||
pub trait EventLoopWindowTargetExtAndroid {}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Android.
|
||||
pub trait WindowExtAndroid {
|
||||
fn content_rect(&self) -> Rect;
|
||||
|
||||
fn config(&self) -> Configuration;
|
||||
}
|
||||
|
||||
impl WindowExtAndroid for Window {
|
||||
fn content_rect(&self) -> Rect {
|
||||
self.window.content_rect()
|
||||
}
|
||||
|
||||
fn config(&self) -> Configuration {
|
||||
self.window.config()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Android.
|
||||
pub trait WindowBuilderExtAndroid {}
|
||||
|
||||
impl WindowBuilderExtAndroid for WindowBuilder {}
|
||||
@@ -1,108 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use libc;
|
||||
use std::os::raw;
|
||||
|
||||
#[link(name = "android")]
|
||||
#[link(name = "EGL")]
|
||||
#[link(name = "GLESv2")]
|
||||
extern {}
|
||||
|
||||
/**
|
||||
* asset_manager.h
|
||||
*/
|
||||
pub type AAssetManager = raw::c_void;
|
||||
|
||||
/**
|
||||
* native_window.h
|
||||
*/
|
||||
pub type ANativeWindow = raw::c_void;
|
||||
|
||||
extern {
|
||||
pub fn ANativeWindow_getHeight(window: *const ANativeWindow) -> libc::int32_t;
|
||||
pub fn ANativeWindow_getWidth(window: *const ANativeWindow) -> libc::int32_t;
|
||||
}
|
||||
|
||||
/**
|
||||
* native_activity.h
|
||||
*/
|
||||
pub type JavaVM = ();
|
||||
pub type JNIEnv = ();
|
||||
pub type jobject = *const libc::c_void;
|
||||
|
||||
pub type AInputQueue = (); // FIXME: wrong
|
||||
pub type ARect = (); // FIXME: wrong
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ANativeActivity {
|
||||
pub callbacks: *mut ANativeActivityCallbacks,
|
||||
pub vm: *mut JavaVM,
|
||||
pub env: *mut JNIEnv,
|
||||
pub clazz: jobject,
|
||||
pub internalDataPath: *const libc::c_char,
|
||||
pub externalDataPath: *const libc::c_char,
|
||||
pub sdkVersion: libc::int32_t,
|
||||
pub instance: *mut libc::c_void,
|
||||
pub assetManager: *mut AAssetManager,
|
||||
pub obbPath: *const libc::c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ANativeActivityCallbacks {
|
||||
pub onStart: extern fn(*mut ANativeActivity),
|
||||
pub onResume: extern fn(*mut ANativeActivity),
|
||||
pub onSaveInstanceState: extern fn(*mut ANativeActivity, *mut libc::size_t),
|
||||
pub onPause: extern fn(*mut ANativeActivity),
|
||||
pub onStop: extern fn(*mut ANativeActivity),
|
||||
pub onDestroy: extern fn(*mut ANativeActivity),
|
||||
pub onWindowFocusChanged: extern fn(*mut ANativeActivity, libc::c_int),
|
||||
pub onNativeWindowCreated: extern fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowResized: extern fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowRedrawNeeded: extern fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowDestroyed: extern fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onInputQueueCreated: extern fn(*mut ANativeActivity, *mut AInputQueue),
|
||||
pub onInputQueueDestroyed: extern fn(*mut ANativeActivity, *mut AInputQueue),
|
||||
pub onContentRectChanged: extern fn(*mut ANativeActivity, *const ARect),
|
||||
pub onConfigurationChanged: extern fn(*mut ANativeActivity),
|
||||
pub onLowMemory: extern fn(*mut ANativeActivity),
|
||||
}
|
||||
|
||||
/**
|
||||
* looper.h
|
||||
*/
|
||||
pub type ALooper = ();
|
||||
|
||||
#[link(name = "android")]
|
||||
extern {
|
||||
pub fn ALooper_forThread() -> *const ALooper;
|
||||
pub fn ALooper_acquire(looper: *const ALooper);
|
||||
pub fn ALooper_release(looper: *const ALooper);
|
||||
pub fn ALooper_prepare(opts: libc::c_int) -> *const ALooper;
|
||||
pub fn ALooper_pollOnce(timeoutMillis: libc::c_int, outFd: *mut libc::c_int,
|
||||
outEvents: *mut libc::c_int, outData: *mut *mut libc::c_void) -> libc::c_int;
|
||||
pub fn ALooper_pollAll(timeoutMillis: libc::c_int, outFd: *mut libc::c_int,
|
||||
outEvents: *mut libc::c_int, outData: *mut *mut libc::c_void) -> libc::c_int;
|
||||
pub fn ALooper_wake(looper: *const ALooper);
|
||||
pub fn ALooper_addFd(looper: *const ALooper, fd: libc::c_int, ident: libc::c_int,
|
||||
events: libc::c_int, callback: ALooper_callbackFunc, data: *mut libc::c_void)
|
||||
-> libc::c_int;
|
||||
pub fn ALooper_removeFd(looper: *const ALooper, fd: libc::c_int) -> libc::c_int;
|
||||
}
|
||||
|
||||
pub const ALOOPER_PREPARE_ALLOW_NON_CALLBACKS: libc::c_int = 1 << 0;
|
||||
|
||||
pub const ALOOPER_POLL_WAKE: libc::c_int = -1;
|
||||
pub const ALOOPER_POLL_CALLBACK: libc::c_int = -2;
|
||||
pub const ALOOPER_POLL_TIMEOUT: libc::c_int = -3;
|
||||
pub const ALOOPER_POLL_ERROR: libc::c_int = -4;
|
||||
|
||||
pub const ALOOPER_EVENT_INPUT: libc::c_int = 1 << 0;
|
||||
pub const ALOOPER_EVENT_OUTPUT: libc::c_int = 1 << 1;
|
||||
pub const ALOOPER_EVENT_ERROR: libc::c_int = 1 << 2;
|
||||
pub const ALOOPER_EVENT_HANGUP: libc::c_int = 1 << 3;
|
||||
pub const ALOOPER_EVENT_INVALID: libc::c_int = 1 << 4;
|
||||
|
||||
pub type ALooper_callbackFunc = extern fn(libc::c_int, libc::c_int, *mut libc::c_void) -> libc::c_int;
|
||||
@@ -1,340 +0,0 @@
|
||||
#![cfg(target_os = "android")]
|
||||
|
||||
extern crate android_glue;
|
||||
|
||||
use libc;
|
||||
use std::sync::mpsc::{Receiver, channel};
|
||||
use std::os::raw::c_void;
|
||||
use {CreationError, Event, WindowEvent, MouseCursor};
|
||||
use CreationError::OsError;
|
||||
use WindowId as RootWindowId;
|
||||
use events::{Touch, TouchPhase};
|
||||
use window::MonitorId as RootMonitorId;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use CursorState;
|
||||
use WindowAttributes;
|
||||
|
||||
pub struct EventsLoop {
|
||||
event_rx: Receiver<android_glue::Event>,
|
||||
suspend_callback: RefCell<Option<Box<Fn(bool) -> ()>>>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventsLoopProxy;
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn new() -> EventsLoop {
|
||||
let (tx, rx) = channel();
|
||||
android_glue::add_sender(tx);
|
||||
EventsLoop {
|
||||
event_rx: rx,
|
||||
suspend_callback: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut rb = VecDeque::new();
|
||||
rb.push_back(MonitorId);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId
|
||||
}
|
||||
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event)
|
||||
{
|
||||
while let Ok(event) = self.event_rx.try_recv() {
|
||||
|
||||
let e = match event{
|
||||
android_glue::Event::EventMotion(motion) => {
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
phase: match motion.action {
|
||||
android_glue::MotionAction::Down => TouchPhase::Started,
|
||||
android_glue::MotionAction::Move => TouchPhase::Moved,
|
||||
android_glue::MotionAction::Up => TouchPhase::Ended,
|
||||
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
|
||||
},
|
||||
location: (motion.x as f64, motion.y as f64),
|
||||
id: motion.pointer_id as u64,
|
||||
device_id: DEVICE_ID,
|
||||
}),
|
||||
})
|
||||
},
|
||||
android_glue::Event::InitWindow => {
|
||||
// The activity went to foreground.
|
||||
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
|
||||
(*cb)(false);
|
||||
}
|
||||
Some(Event::Suspended(false))
|
||||
},
|
||||
android_glue::Event::TermWindow => {
|
||||
// The activity went to background.
|
||||
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
|
||||
(*cb)(true);
|
||||
}
|
||||
Some(Event::Suspended(true))
|
||||
},
|
||||
android_glue::Event::WindowResized |
|
||||
android_glue::Event::ConfigChanged => {
|
||||
// Activity Orientation changed or resized.
|
||||
let native_window = unsafe { android_glue::get_native_window() };
|
||||
if native_window.is_null() {
|
||||
None
|
||||
} else {
|
||||
let w = unsafe { ffi::ANativeWindow_getWidth(native_window as *const _) } as u32;
|
||||
let h = unsafe { ffi::ANativeWindow_getHeight(native_window as *const _) } as u32;
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Resized(w, h),
|
||||
})
|
||||
}
|
||||
},
|
||||
android_glue::Event::WindowRedrawNeeded => {
|
||||
// The activity needs to be redrawn.
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Refresh,
|
||||
})
|
||||
}
|
||||
android_glue::Event::Wake => {
|
||||
Some(Event::Awakened)
|
||||
}
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(event) = e {
|
||||
callback(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set_suspend_callback(&self, cb: Option<Box<Fn(bool) -> ()>>) {
|
||||
*self.suspend_callback.borrow_mut() = cb;
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event) -> ::ControlFlow,
|
||||
{
|
||||
// Yeah that's a very bad implementation.
|
||||
loop {
|
||||
let mut control_flow = ::ControlFlow::Continue;
|
||||
self.poll_events(|e| {
|
||||
if let ::ControlFlow::Break = callback(e) {
|
||||
control_flow = ::ControlFlow::Break;
|
||||
}
|
||||
});
|
||||
if let ::ControlFlow::Break = control_flow {
|
||||
break;
|
||||
}
|
||||
::std::thread::sleep(::std::time::Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
EventsLoopProxy
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), ::EventsLoopClosed> {
|
||||
android_glue::wake_event_loop();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
pub struct Window {
|
||||
native_window: *const c_void,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId;
|
||||
|
||||
mod ffi;
|
||||
|
||||
impl MonitorId {
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some("Primary".to_string())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
unsafe {
|
||||
let window = android_glue::get_native_window();
|
||||
(ffi::ANativeWindow_getWidth(window) as u32, ffi::ANativeWindow_getHeight(window) as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
// Android assumes single screen
|
||||
(0, 0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes;
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificHeadlessBuilderAttributes;
|
||||
|
||||
impl Window {
|
||||
pub fn new(_: &EventsLoop, win_attribs: &WindowAttributes,
|
||||
_: &PlatformSpecificWindowBuilderAttributes)
|
||||
-> Result<Window, CreationError>
|
||||
{
|
||||
// not implemented
|
||||
assert!(win_attribs.min_dimensions.is_none());
|
||||
assert!(win_attribs.max_dimensions.is_none());
|
||||
|
||||
let native_window = unsafe { android_glue::get_native_window() };
|
||||
if native_window.is_null() {
|
||||
return Err(OsError(format!("Android's native window is null")));
|
||||
}
|
||||
|
||||
android_glue::set_multitouch(win_attribs.multitouch);
|
||||
|
||||
Ok(Window {
|
||||
native_window: native_window as *const _,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_window(&self) -> *const c_void {
|
||||
self.native_window
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, _: &str) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, _x: i32, _y: i32) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
|
||||
if self.native_window.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
unsafe { ffi::ANativeWindow_getWidth(self.native_window as *const _) } as u32,
|
||||
unsafe { ffi::ANativeWindow_getHeight(self.native_window as *const _) } as u32
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
|
||||
self.get_inner_size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, _x: u32, _y: u32) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_display(&self) -> *mut libc::c_void {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_window(&self) -> *mut libc::c_void {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, _: MouseCursor) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_state(&self, _state: CursorState) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, _maximized: bool) {
|
||||
// Android has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
|
||||
// Android has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, _decorations: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
RootMonitorId{inner: MonitorId}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Window {}
|
||||
unsafe impl Sync for Window {}
|
||||
|
||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
||||
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
|
||||
53
src/platform/desktop.rs
Normal file
53
src/platform/desktop.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
#![cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"
|
||||
))]
|
||||
|
||||
use crate::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||
};
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to desktop platforms.
|
||||
pub trait EventLoopExtDesktop {
|
||||
/// A type provided by the user that can be passed through `Event::UserEvent`.
|
||||
type UserEvent;
|
||||
|
||||
/// Initializes the `winit` event loop.
|
||||
///
|
||||
/// Unlike `run`, this function accepts non-`'static` (i.e. non-`move`) closures and returns
|
||||
/// control flow to the caller when `control_flow` is set to `ControlFlow::Exit`.
|
||||
///
|
||||
/// # Caveats
|
||||
/// Despite its apperance at first glance, this is *not* a perfect replacement for
|
||||
/// `poll_events`. For example, this function will not return on Windows or macOS while a
|
||||
/// window is getting resized, resulting in all application logic outside of the
|
||||
/// `event_handler` closure not running until the resize operation ends. Other OS operations
|
||||
/// may also result in such freezes. This behavior is caused by fundamental limitations in the
|
||||
/// underyling OS APIs, which cannot be hidden by Winit without severe stability reprecussions.
|
||||
///
|
||||
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
|
||||
fn run_return<F>(&mut self, event_handler: F)
|
||||
where
|
||||
F: FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtDesktop for EventLoop<T> {
|
||||
type UserEvent = T;
|
||||
|
||||
fn run_return<F>(&mut self, event_handler: F)
|
||||
where
|
||||
F: FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
),
|
||||
{
|
||||
self.event_loop.run_return(event_handler)
|
||||
}
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use std::os::raw::{c_int, c_char, c_void, c_ulong, c_double, c_long, c_ushort};
|
||||
#[cfg(test)]
|
||||
use std::mem;
|
||||
|
||||
pub type EM_BOOL = c_int;
|
||||
pub type EM_UTF8 = c_char;
|
||||
pub type EMSCRIPTEN_RESULT = c_int;
|
||||
|
||||
pub const EM_TRUE: EM_BOOL = 1;
|
||||
pub const EM_FALSE: EM_BOOL = 0;
|
||||
|
||||
// values for EMSCRIPTEN_RESULT
|
||||
pub const EMSCRIPTEN_RESULT_SUCCESS: c_int = 0;
|
||||
pub const EMSCRIPTEN_RESULT_DEFERRED: c_int = 1;
|
||||
pub const EMSCRIPTEN_RESULT_NOT_SUPPORTED: c_int = -1;
|
||||
pub const EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED: c_int = -2;
|
||||
pub const EMSCRIPTEN_RESULT_INVALID_TARGET: c_int = -3;
|
||||
pub const EMSCRIPTEN_RESULT_UNKNOWN_TARGET: c_int = -4;
|
||||
pub const EMSCRIPTEN_RESULT_INVALID_PARAM: c_int = -5;
|
||||
pub const EMSCRIPTEN_RESULT_FAILED: c_int = -6;
|
||||
pub const EMSCRIPTEN_RESULT_NO_DATA: c_int = -7;
|
||||
|
||||
// values for EMSCRIPTEN EVENT
|
||||
pub const EMSCRIPTEN_EVENT_KEYPRESS: c_int = 1;
|
||||
pub const EMSCRIPTEN_EVENT_KEYDOWN: c_int = 2;
|
||||
pub const EMSCRIPTEN_EVENT_KEYUP: c_int = 3;
|
||||
pub const EMSCRIPTEN_EVENT_CLICK: c_int = 4;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEDOWN: c_int = 5;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEUP: c_int = 6;
|
||||
pub const EMSCRIPTEN_EVENT_DBLCLICK: c_int = 7;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEMOVE: c_int = 8;
|
||||
pub const EMSCRIPTEN_EVENT_WHEEL: c_int = 9;
|
||||
pub const EMSCRIPTEN_EVENT_RESIZE: c_int = 10;
|
||||
pub const EMSCRIPTEN_EVENT_SCROLL: c_int = 11;
|
||||
pub const EMSCRIPTEN_EVENT_BLUR: c_int = 12;
|
||||
pub const EMSCRIPTEN_EVENT_FOCUS: c_int = 13;
|
||||
pub const EMSCRIPTEN_EVENT_FOCUSIN: c_int = 14;
|
||||
pub const EMSCRIPTEN_EVENT_FOCUSOUT: c_int = 15;
|
||||
pub const EMSCRIPTEN_EVENT_DEVICEORIENTATION: c_int = 16;
|
||||
pub const EMSCRIPTEN_EVENT_DEVICEMOTION: c_int = 17;
|
||||
pub const EMSCRIPTEN_EVENT_ORIENTATIONCHANGE: c_int = 18;
|
||||
pub const EMSCRIPTEN_EVENT_FULLSCREENCHANGE: c_int = 19;
|
||||
pub const EMSCRIPTEN_EVENT_POINTERLOCKCHANGE: c_int = 20;
|
||||
pub const EMSCRIPTEN_EVENT_VISIBILITYCHANGE: c_int = 21;
|
||||
pub const EMSCRIPTEN_EVENT_TOUCHSTART: c_int = 22;
|
||||
pub const EMSCRIPTEN_EVENT_TOUCHEND: c_int = 23;
|
||||
pub const EMSCRIPTEN_EVENT_TOUCHMOVE: c_int = 24;
|
||||
pub const EMSCRIPTEN_EVENT_TOUCHCANCEL: c_int = 25;
|
||||
pub const EMSCRIPTEN_EVENT_GAMEPADCONNECTED: c_int = 26;
|
||||
pub const EMSCRIPTEN_EVENT_GAMEPADDISCONNECTED: c_int = 27;
|
||||
pub const EMSCRIPTEN_EVENT_BEFOREUNLOAD: c_int = 28;
|
||||
pub const EMSCRIPTEN_EVENT_BATTERYCHARGINGCHANGE: c_int = 29;
|
||||
pub const EMSCRIPTEN_EVENT_BATTERYLEVELCHANGE: c_int = 30;
|
||||
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: c_int = 31;
|
||||
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: c_int = 32;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEENTER: c_int = 33;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSELEAVE: c_int = 34;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEOVER: c_int = 35;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEOUT: c_int = 36;
|
||||
pub const EMSCRIPTEN_EVENT_CANVASRESIZED: c_int = 37;
|
||||
pub const EMSCRIPTEN_EVENT_POINTERLOCKERROR: c_int = 38;
|
||||
|
||||
pub const EM_HTML5_SHORT_STRING_LEN_BYTES: usize = 32;
|
||||
|
||||
pub const DOM_KEY_LOCATION_STANDARD: c_ulong = 0x00;
|
||||
pub const DOM_KEY_LOCATION_LEFT: c_ulong = 0x01;
|
||||
pub const DOM_KEY_LOCATION_RIGHT: c_ulong = 0x02;
|
||||
pub const DOM_KEY_LOCATION_NUMPAD: c_ulong = 0x03;
|
||||
|
||||
pub type em_callback_func = Option<unsafe extern "C" fn()>;
|
||||
|
||||
pub type em_key_callback_func = Option<unsafe extern "C" fn(
|
||||
eventType: c_int,
|
||||
keyEvent: *const EmscriptenKeyboardEvent,
|
||||
userData: *mut c_void) -> EM_BOOL>;
|
||||
|
||||
pub type em_mouse_callback_func = Option<unsafe extern "C" fn(
|
||||
eventType: c_int,
|
||||
mouseEvent: *const EmscriptenMouseEvent,
|
||||
userData: *mut c_void) -> EM_BOOL>;
|
||||
|
||||
pub type em_pointerlockchange_callback_func = Option<unsafe extern "C" fn(
|
||||
eventType: c_int,
|
||||
pointerlockChangeEvent: *const EmscriptenPointerlockChangeEvent,
|
||||
userData: *mut c_void) -> EM_BOOL>;
|
||||
|
||||
pub type em_fullscreenchange_callback_func = Option<unsafe extern "C" fn(
|
||||
eventType: c_int,
|
||||
fullscreenChangeEvent: *const EmscriptenFullscreenChangeEvent,
|
||||
userData: *mut c_void) -> EM_BOOL>;
|
||||
|
||||
pub type em_touch_callback_func = Option<unsafe extern "C" fn(
|
||||
eventType: c_int,
|
||||
touchEvent: *const EmscriptenTouchEvent,
|
||||
userData: *mut c_void) -> EM_BOOL>;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct EmscriptenFullscreenChangeEvent {
|
||||
pub isFullscreen: c_int,
|
||||
pub fullscreenEnabled: c_int,
|
||||
pub nodeName: [c_char; 128usize],
|
||||
pub id: [c_char; 128usize],
|
||||
pub elementWidth: c_int,
|
||||
pub elementHeight: c_int,
|
||||
pub screenWidth: c_int,
|
||||
pub screenHeight: c_int,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenFullscreenChangeEvent() {
|
||||
assert_eq!(mem::size_of::<EmscriptenFullscreenChangeEvent>(), 280usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenFullscreenChangeEvent>(), 4usize);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy)]
|
||||
pub struct EmscriptenKeyboardEvent {
|
||||
pub key: [c_char; 32usize],
|
||||
pub code: [c_char; 32usize],
|
||||
pub location: c_ulong,
|
||||
pub ctrlKey: c_int,
|
||||
pub shiftKey: c_int,
|
||||
pub altKey: c_int,
|
||||
pub metaKey: c_int,
|
||||
pub repeat: c_int,
|
||||
pub locale: [c_char; 32usize],
|
||||
pub charValue: [c_char; 32usize],
|
||||
pub charCode: c_ulong,
|
||||
pub keyCode: c_ulong,
|
||||
pub which: c_ulong,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenKeyboardEvent() {
|
||||
assert_eq!(mem::size_of::<EmscriptenKeyboardEvent>(), 184usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenKeyboardEvent>(), 8usize);
|
||||
}
|
||||
impl Clone for EmscriptenKeyboardEvent {
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct EmscriptenMouseEvent {
|
||||
pub timestamp: f64,
|
||||
pub screenX: c_long,
|
||||
pub screenY: c_long,
|
||||
pub clientX: c_long,
|
||||
pub clientY: c_long,
|
||||
pub ctrlKey: c_int,
|
||||
pub shiftKey: c_int,
|
||||
pub altKey: c_int,
|
||||
pub metaKey: c_int,
|
||||
pub button: c_ushort,
|
||||
pub buttons: c_ushort,
|
||||
pub movementX: c_long,
|
||||
pub movementY: c_long,
|
||||
pub targetX: c_long,
|
||||
pub targetY: c_long,
|
||||
pub canvasX: c_long,
|
||||
pub canvasY: c_long,
|
||||
pub padding: c_long,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenMouseEvent() {
|
||||
assert_eq!(mem::size_of::<EmscriptenMouseEvent>(), 120usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenMouseEvent>(), 8usize);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct EmscriptenTouchPoint {
|
||||
pub identifier: c_long,
|
||||
pub screenX: c_long,
|
||||
pub screenY: c_long,
|
||||
pub clientX: c_long,
|
||||
pub clientY: c_long,
|
||||
pub pageX: c_long,
|
||||
pub pageY: c_long,
|
||||
pub isChanged: c_int,
|
||||
pub onTarget: c_int,
|
||||
pub targetX: c_long,
|
||||
pub targetY: c_long,
|
||||
pub canvasX: c_long,
|
||||
pub canvasY: c_long,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenTouchPoint() {
|
||||
assert_eq!(mem::size_of::<EmscriptenTouchPoint>(), 96usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenTouchPoint>(), 8usize);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct EmscriptenTouchEvent {
|
||||
pub numTouches: c_int,
|
||||
pub ctrlKey: c_int,
|
||||
pub shiftKey: c_int,
|
||||
pub altKey: c_int,
|
||||
pub metaKey: c_int,
|
||||
pub touches: [EmscriptenTouchPoint; 32usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenTouchEvent() {
|
||||
assert_eq!(mem::size_of::<EmscriptenTouchEvent>(), 3096usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenTouchEvent>(), 8usize);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct EmscriptenPointerlockChangeEvent {
|
||||
pub isActive: c_int,
|
||||
pub nodeName: [c_char; 128usize],
|
||||
pub id: [c_char; 128usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenPointerlockChangeEvent() {
|
||||
assert_eq!(mem::size_of::<EmscriptenPointerlockChangeEvent>(), 260usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenPointerlockChangeEvent>(), 4usize);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn emscripten_set_canvas_size(
|
||||
width: c_int, height: c_int)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_get_canvas_size(
|
||||
width: *mut c_int, height: *mut c_int,
|
||||
is_fullscreen: *mut c_int)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_element_css_size(
|
||||
target: *const c_char, width: c_double,
|
||||
height: c_double) -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_get_element_css_size(
|
||||
target: *const c_char, width: *mut c_double,
|
||||
height: *mut c_double) -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_request_pointerlock(
|
||||
target: *const c_char, deferUntilInEventHandler: EM_BOOL)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_exit_pointerlock() -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_request_fullscreen(
|
||||
target: *const c_char, deferUntilInEventHandler: EM_BOOL)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_exit_fullscreen() -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_keydown_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: EM_BOOL, callback: em_key_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_keyup_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: EM_BOOL, callback: em_key_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_mousemove_callback(
|
||||
target: *const c_char, user_data: *mut c_void,
|
||||
use_capture: EM_BOOL, callback: em_mouse_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_mousedown_callback(
|
||||
target: *const c_char, user_data: *mut c_void,
|
||||
use_capture: EM_BOOL, callback: em_mouse_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_mouseup_callback(
|
||||
target: *const c_char, user_data: *mut c_void,
|
||||
use_capture: EM_BOOL, callback: em_mouse_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_hide_mouse();
|
||||
|
||||
pub fn emscripten_get_device_pixel_ratio() -> f64;
|
||||
|
||||
pub fn emscripten_set_pointerlockchange_callback(
|
||||
target: *const c_char, userData: *mut c_void, useCapture: EM_BOOL,
|
||||
callback: em_pointerlockchange_callback_func) -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_fullscreenchange_callback(
|
||||
target: *const c_char, userData: *mut c_void, useCapture: EM_BOOL,
|
||||
callback: em_fullscreenchange_callback_func) -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_asm_const(code: *const c_char);
|
||||
|
||||
pub fn emscripten_set_main_loop(
|
||||
func: em_callback_func, fps: c_int, simulate_infinite_loop: EM_BOOL);
|
||||
|
||||
pub fn emscripten_cancel_main_loop();
|
||||
|
||||
pub fn emscripten_set_touchstart_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: c_int, callback: em_touch_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_touchend_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: c_int, callback: em_touch_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_touchmove_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: c_int, callback: em_touch_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_touchcancel_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: c_int, callback: em_touch_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
317
src/platform/ios.rs
Normal file
317
src/platform/ios.rs
Normal file
@@ -0,0 +1,317 @@
|
||||
#![cfg(target_os = "ios")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use crate::{
|
||||
event_loop::EventLoop,
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// 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 {
|
||||
/// Returns a pointer to the [`UIWindow`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
///
|
||||
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
||||
fn ui_window(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the [`UIViewController`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
///
|
||||
/// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc
|
||||
fn ui_view_controller(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the [`UIView`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
///
|
||||
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
|
||||
fn ui_view(&self) -> *mut c_void;
|
||||
|
||||
/// 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 changes the value returned by
|
||||
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc),
|
||||
/// and then calls
|
||||
/// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc).
|
||||
fn set_prefers_status_bar_hidden(&self, hidden: bool);
|
||||
}
|
||||
|
||||
impl WindowExtIOS for Window {
|
||||
#[inline]
|
||||
fn ui_window(&self) -> *mut c_void {
|
||||
self.window.ui_window() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ui_view_controller(&self) -> *mut c_void {
|
||||
self.window.ui_view_controller() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ui_view(&self) -> *mut c_void {
|
||||
self.window.ui_view() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_scale_factor(&self, scale_factor: f64) {
|
||||
self.window.set_scale_factor(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
||||
self.window.set_valid_orientations(valid_orientations)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||
self.window.set_prefers_home_indicator_hidden(hidden)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
||||
self.window
|
||||
.set_preferred_screen_edges_deferring_system_gestures(edges)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||
self.window.set_prefers_status_bar_hidden(hidden)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to iOS.
|
||||
pub trait WindowBuilderExtIOS {
|
||||
/// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided.
|
||||
///
|
||||
/// An instance of the class will be initialized by calling [`-[UIView initWithFrame:]`](https://developer.apple.com/documentation/uikit/uiview/1622488-initwithframe?language=objc).
|
||||
///
|
||||
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
|
||||
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
|
||||
|
||||
/// 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) -> WindowBuilder;
|
||||
|
||||
/// 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) -> WindowBuilder;
|
||||
|
||||
/// 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) -> WindowBuilder;
|
||||
|
||||
/// 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,
|
||||
) -> WindowBuilder;
|
||||
|
||||
/// 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) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtIOS for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder {
|
||||
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder {
|
||||
self.platform_specific.scale_factor = Some(scale_factor);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> WindowBuilder {
|
||||
self.platform_specific.valid_orientations = valid_orientations;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.prefers_home_indicator_hidden = hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_preferred_screen_edges_deferring_system_gestures(
|
||||
mut self,
|
||||
edges: ScreenEdge,
|
||||
) -> WindowBuilder {
|
||||
self.platform_specific
|
||||
.preferred_screen_edges_deferring_system_gestures = edges;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.prefers_status_bar_hidden = hidden;
|
||||
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 [`VideoMode`] 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) -> VideoMode;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtIOS for MonitorHandle {
|
||||
#[inline]
|
||||
fn ui_screen(&self) -> *mut c_void {
|
||||
self.inner.ui_screen() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn preferred_video_mode(&self) -> VideoMode {
|
||||
self.inner.preferred_video_mode()
|
||||
}
|
||||
}
|
||||
|
||||
/// Valid orientations for a particular [`Window`].
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ValidOrientations {
|
||||
/// Excludes `PortraitUpsideDown` on iphone
|
||||
LandscapeAndPortrait,
|
||||
|
||||
Landscape,
|
||||
|
||||
/// Excludes `PortraitUpsideDown` on iphone
|
||||
Portrait,
|
||||
}
|
||||
|
||||
impl Default for ValidOrientations {
|
||||
#[inline]
|
||||
fn default() -> ValidOrientations {
|
||||
ValidOrientations::LandscapeAndPortrait
|
||||
}
|
||||
}
|
||||
|
||||
/// The device [idiom].
|
||||
///
|
||||
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Idiom {
|
||||
Unspecified,
|
||||
|
||||
/// iPhone and iPod touch.
|
||||
Phone,
|
||||
|
||||
/// iPad.
|
||||
Pad,
|
||||
|
||||
/// tvOS and Apple TV.
|
||||
TV,
|
||||
CarPlay,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// The [edges] of a screen.
|
||||
///
|
||||
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
|
||||
#[derive(Default)]
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
use std::ffi::CString;
|
||||
|
||||
use libc;
|
||||
use objc::runtime::{ Object, Class };
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type id = *mut Object;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const nil: id = 0 as id;
|
||||
|
||||
pub type CFStringRef = *const libc::c_void;
|
||||
pub type CFTimeInterval = f64;
|
||||
pub type Boolean = u32;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const kCFRunLoopRunHandledSource: i32 = 4;
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
pub type CGFloat = f32;
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub type CGFloat = f64;
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
pub type NSUInteger = u32;
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub type NSUInteger = u64;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGPoint {
|
||||
pub x: CGFloat,
|
||||
pub y: CGFloat,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGRect {
|
||||
pub origin: CGPoint,
|
||||
pub size: CGSize
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGSize {
|
||||
pub width: CGFloat,
|
||||
pub height: CGFloat
|
||||
}
|
||||
|
||||
#[link(name = "UIKit", kind = "framework")]
|
||||
#[link(name = "CoreFoundation", kind = "framework")]
|
||||
#[link(name = "GlKit", kind = "framework")]
|
||||
extern {
|
||||
pub static kCFRunLoopDefaultMode: CFStringRef;
|
||||
|
||||
// int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName );
|
||||
pub fn UIApplicationMain(argc: libc::c_int, argv: *const libc::c_char, principalClassName: id, delegateClassName: id) -> libc::c_int;
|
||||
|
||||
// SInt32 CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled );
|
||||
pub fn CFRunLoopRunInMode(mode: CFStringRef, seconds: CFTimeInterval, returnAfterSourceHandled: Boolean) -> i32;
|
||||
}
|
||||
|
||||
extern {
|
||||
pub fn setjmp(env: *mut libc::c_void) -> libc::c_int;
|
||||
pub fn longjmp(env: *mut libc::c_void, val: libc::c_int);
|
||||
}
|
||||
|
||||
pub trait NSString: Sized {
|
||||
unsafe fn alloc(_: Self) -> id {
|
||||
msg_send![class("NSString"), alloc]
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const i8) -> id;
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn stringByAppendingString_(self, other: id) -> id;
|
||||
unsafe fn init_str(self, string: &str) -> Self;
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn UTF8String(self) -> *const libc::c_char;
|
||||
}
|
||||
|
||||
impl NSString for id {
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const i8) -> id {
|
||||
msg_send![self, initWithUTF8String:c_string as id]
|
||||
}
|
||||
|
||||
unsafe fn stringByAppendingString_(self, other: id) -> id {
|
||||
msg_send![self, stringByAppendingString:other]
|
||||
}
|
||||
|
||||
unsafe fn init_str(self, string: &str) -> id {
|
||||
let cstring = CString::new(string).unwrap();
|
||||
self.initWithUTF8String_(cstring.as_ptr())
|
||||
}
|
||||
|
||||
unsafe fn UTF8String(self) -> *const libc::c_char {
|
||||
msg_send![self, UTF8String]
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn class(name: &str) -> *mut Class {
|
||||
unsafe {
|
||||
::std::mem::transmute(Class::get(name))
|
||||
}
|
||||
}
|
||||
@@ -1,564 +0,0 @@
|
||||
//! iOS support
|
||||
//!
|
||||
//! # 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_glutin_app() {
|
||||
//! start_inner()
|
||||
//! }
|
||||
//!
|
||||
//! fn start_inner() {
|
||||
//! ...
|
||||
//! }
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! Compile project and then drag resulting .a into Xcode project. Add glutin.h to xcode.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! void start_glutin_app();
|
||||
//! ```
|
||||
//!
|
||||
//! Use start_glutin_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 glutin:
|
||||
//!
|
||||
//! - applicationDidBecomeActive is Focused(true)
|
||||
//! - applicationWillResignActive is Focused(false)
|
||||
//! - applicationDidEnterBackground is Suspended(true)
|
||||
//! - applicationWillEnterForeground is Suspended(false)
|
||||
//! - applicationWillTerminate is Destroyed
|
||||
//!
|
||||
//! Keep in mind that after Destroyed event is received every attempt to draw with
|
||||
//! opengl will result in segfault.
|
||||
//!
|
||||
//! Also note that app will not receive Destroyed event if suspended, it will be SIGKILL'ed
|
||||
|
||||
#![cfg(target_os = "ios")]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::ptr;
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use libc;
|
||||
use libc::c_int;
|
||||
use objc::runtime::{Class, Object, Sel, BOOL, YES };
|
||||
use objc::declare::{ ClassDecl };
|
||||
|
||||
use { CreationError, CursorState, MouseCursor, WindowAttributes };
|
||||
use WindowId as RootEventId;
|
||||
use WindowEvent;
|
||||
use Event;
|
||||
use events::{ Touch, TouchPhase };
|
||||
use window::MonitorId as RootMonitorId;
|
||||
|
||||
mod ffi;
|
||||
use self::ffi::{
|
||||
setjmp,
|
||||
UIApplicationMain,
|
||||
CFTimeInterval,
|
||||
CFRunLoopRunInMode,
|
||||
kCFRunLoopDefaultMode,
|
||||
kCFRunLoopRunHandledSource,
|
||||
id,
|
||||
nil,
|
||||
NSString,
|
||||
CGFloat,
|
||||
longjmp,
|
||||
CGRect,
|
||||
CGPoint
|
||||
};
|
||||
|
||||
static mut jmpbuf: [c_int;27] = [0;27];
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId;
|
||||
|
||||
pub struct Window {
|
||||
delegate_state: *mut DelegateState
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WindowProxy;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DelegateState {
|
||||
events_queue: VecDeque<Event>,
|
||||
window: id,
|
||||
controller: id,
|
||||
size: (u32,u32),
|
||||
scale: f32
|
||||
}
|
||||
|
||||
|
||||
impl DelegateState {
|
||||
#[inline]
|
||||
fn new(window: id, controller:id, size: (u32,u32), scale: f32) -> DelegateState {
|
||||
DelegateState {
|
||||
events_queue: VecDeque::new(),
|
||||
window: window,
|
||||
controller: controller,
|
||||
size: size,
|
||||
scale: scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some("Primary".to_string())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
// iOS assumes single screen
|
||||
(0, 0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventsLoop {
|
||||
delegate_state: *mut DelegateState
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventsLoopProxy;
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn new() -> EventsLoop {
|
||||
unsafe {
|
||||
if setjmp(mem::transmute(&mut jmpbuf)) != 0 {
|
||||
let app: id = msg_send![Class::get("UIApplication").unwrap(), sharedApplication];
|
||||
let delegate: id = msg_send![app, delegate];
|
||||
let state: *mut c_void = *(&*delegate).get_ivar("glutinState");
|
||||
let state = state as *mut DelegateState;
|
||||
|
||||
let events_loop = EventsLoop {
|
||||
delegate_state: state
|
||||
};
|
||||
|
||||
return events_loop;
|
||||
}
|
||||
}
|
||||
|
||||
create_delegate_class();
|
||||
create_view_class();
|
||||
start_app();
|
||||
|
||||
panic!("Couldn't create UIApplication")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut rb = VecDeque::new();
|
||||
rb.push_back(MonitorId);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event)
|
||||
{
|
||||
unsafe {
|
||||
let state = &mut *self.delegate_state;
|
||||
|
||||
if let Some(event) = state.events_queue.pop_front() {
|
||||
callback(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// jump hack, so we won't quit on willTerminate event before processing it
|
||||
if setjmp(mem::transmute(&mut jmpbuf)) != 0 {
|
||||
if let Some(event) = state.events_queue.pop_front() {
|
||||
callback(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// run runloop
|
||||
let seconds: CFTimeInterval = 0.000002;
|
||||
while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {}
|
||||
|
||||
if let Some(event) = state.events_queue.pop_front() {
|
||||
callback(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event) -> ::ControlFlow,
|
||||
{
|
||||
// Yeah that's a very bad implementation.
|
||||
loop {
|
||||
let mut control_flow = ::ControlFlow::Continue;
|
||||
self.poll_events(|e| {
|
||||
if let ::ControlFlow::Break = callback(e) {
|
||||
control_flow = ::ControlFlow::Break;
|
||||
}
|
||||
});
|
||||
if let ::ControlFlow::Break = control_flow {
|
||||
break;
|
||||
}
|
||||
::std::thread::sleep(::std::time::Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
EventsLoopProxy
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), ::EventsLoopClosed> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes;
|
||||
|
||||
impl Window {
|
||||
pub fn new(ev: &EventsLoop, _: &WindowAttributes, _: &PlatformSpecificWindowBuilderAttributes)
|
||||
-> Result<Window, CreationError>
|
||||
{
|
||||
Ok(Window {
|
||||
delegate_state: ev.delegate_state,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, _: &str) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, _x: i32, _y: i32) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
|
||||
unsafe { Some((&*self.delegate_state).size) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
|
||||
self.get_inner_size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, _x: u32, _y: u32) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
|
||||
|
||||
#[inline]
|
||||
pub fn platform_display(&self) -> *mut libc::c_void {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_window(&self) -> *mut libc::c_void {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_resize_callback(&mut self, _: Option<fn(u32, u32)>) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, _: MouseCursor) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_state(&self, _: CursorState) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f32 {
|
||||
unsafe { (&*self.delegate_state) }.scale
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_window_proxy(&self) -> WindowProxy {
|
||||
WindowProxy
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, _maximized: bool) {
|
||||
// iOS has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
|
||||
// iOS has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, _decorations: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
RootMonitorId{inner: MonitorId}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId
|
||||
}
|
||||
}
|
||||
|
||||
fn create_delegate_class() {
|
||||
extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL {
|
||||
unsafe {
|
||||
let main_screen: id = msg_send![Class::get("UIScreen").unwrap(), mainScreen];
|
||||
let bounds: CGRect = msg_send![main_screen, bounds];
|
||||
let scale: CGFloat = msg_send![main_screen, nativeScale];
|
||||
|
||||
let window: id = msg_send![Class::get("UIWindow").unwrap(), alloc];
|
||||
let window: id = msg_send![window, initWithFrame:bounds.clone()];
|
||||
|
||||
let size = (bounds.size.width as u32, bounds.size.height as u32);
|
||||
|
||||
let view_controller: id = msg_send![Class::get("MainViewController").unwrap(), alloc];
|
||||
let view_controller: id = msg_send![view_controller, init];
|
||||
|
||||
let _: () = msg_send![window, setRootViewController:view_controller];
|
||||
let _: () = msg_send![window, makeKeyAndVisible];
|
||||
|
||||
let state = Box::new(DelegateState::new(window, view_controller, size, scale as f32));
|
||||
let state_ptr: *mut DelegateState = mem::transmute(state);
|
||||
this.set_ivar("glutinState", state_ptr as *mut c_void);
|
||||
|
||||
|
||||
let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0];
|
||||
}
|
||||
YES
|
||||
}
|
||||
|
||||
extern fn post_launch(_: &Object, _: Sel, _: id) {
|
||||
unsafe { longjmp(mem::transmute(&mut jmpbuf),1); }
|
||||
}
|
||||
|
||||
extern fn did_become_active(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
state.events_queue.push_back(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Focused(true),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extern fn will_resign_active(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
state.events_queue.push_back(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Focused(false),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extern fn will_enter_foreground(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
state.events_queue.push_back(Event::Suspended(false));
|
||||
}
|
||||
}
|
||||
|
||||
extern fn did_enter_background(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
state.events_queue.push_back(Event::Suspended(true));
|
||||
}
|
||||
}
|
||||
|
||||
extern fn will_terminate(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
// push event to the front to garantee that we'll process it
|
||||
// immidiatly after jump
|
||||
state.events_queue.push_front(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Destroyed,
|
||||
});
|
||||
longjmp(mem::transmute(&mut jmpbuf),1);
|
||||
}
|
||||
}
|
||||
|
||||
extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
|
||||
let touches_enum: id = msg_send![touches, objectEnumerator];
|
||||
|
||||
loop {
|
||||
let touch: id = msg_send![touches_enum, nextObject];
|
||||
if touch == nil {
|
||||
break
|
||||
}
|
||||
let location: CGPoint = msg_send![touch, locationInView:nil];
|
||||
let touch_id = touch as u64;
|
||||
let phase: i32 = msg_send![touch, phase];
|
||||
|
||||
state.events_queue.push_back(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
device_id: DEVICE_ID,
|
||||
id: touch_id,
|
||||
location: (location.x as f64, location.y as f64),
|
||||
phase: match phase {
|
||||
0 => TouchPhase::Started,
|
||||
1 => TouchPhase::Moved,
|
||||
// 2 is UITouchPhaseStationary and is not expected here
|
||||
3 => TouchPhase::Ended,
|
||||
4 => TouchPhase::Cancelled,
|
||||
_ => panic!("unexpected touch phase: {:?}", phase)
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ui_responder = Class::get("UIResponder").unwrap();
|
||||
let mut decl = ClassDecl::new("AppDelegate", ui_responder).unwrap();
|
||||
|
||||
unsafe {
|
||||
decl.add_method(sel!(application:didFinishLaunchingWithOptions:),
|
||||
did_finish_launching as extern fn(&mut Object, Sel, id, id) -> BOOL);
|
||||
|
||||
decl.add_method(sel!(applicationDidBecomeActive:),
|
||||
did_become_active as extern fn(&Object, Sel, id));
|
||||
|
||||
decl.add_method(sel!(applicationWillResignActive:),
|
||||
will_resign_active as extern fn(&Object, Sel, id));
|
||||
|
||||
decl.add_method(sel!(applicationWillEnterForeground:),
|
||||
will_enter_foreground as extern fn(&Object, Sel, id));
|
||||
|
||||
decl.add_method(sel!(applicationDidEnterBackground:),
|
||||
did_enter_background as extern fn(&Object, Sel, id));
|
||||
|
||||
decl.add_method(sel!(applicationWillTerminate:),
|
||||
will_terminate as extern fn(&Object, Sel, id));
|
||||
|
||||
|
||||
decl.add_method(sel!(touchesBegan:withEvent:),
|
||||
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
|
||||
|
||||
decl.add_method(sel!(touchesMoved:withEvent:),
|
||||
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
|
||||
|
||||
decl.add_method(sel!(touchesEnded:withEvent:),
|
||||
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
|
||||
|
||||
decl.add_method(sel!(touchesCancelled:withEvent:),
|
||||
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
|
||||
|
||||
|
||||
decl.add_method(sel!(postLaunch:),
|
||||
post_launch as extern fn(&Object, Sel, id));
|
||||
|
||||
decl.add_ivar::<*mut c_void>("glutinState");
|
||||
|
||||
decl.register();
|
||||
}
|
||||
}
|
||||
|
||||
fn create_view_class() {
|
||||
let ui_view_controller = Class::get("UIViewController").unwrap();
|
||||
let decl = ClassDecl::new("MainViewController", ui_view_controller).unwrap();
|
||||
|
||||
decl.register();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn start_app() {
|
||||
unsafe {
|
||||
UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate"));
|
||||
}
|
||||
}
|
||||
|
||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
||||
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
|
||||
@@ -1,15 +0,0 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::os::raw::{c_void, c_char, c_int};
|
||||
|
||||
pub const RTLD_LAZY: c_int = 0x001;
|
||||
pub const RTLD_NOW: c_int = 0x002;
|
||||
|
||||
#[link="dl"]
|
||||
extern {
|
||||
pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
|
||||
pub fn dlerror() -> *mut c_char;
|
||||
pub fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
|
||||
pub fn dlclose(handle: *mut c_void) -> c_int;
|
||||
}
|
||||
@@ -1,451 +0,0 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::env;
|
||||
|
||||
use {CreationError, CursorState, EventsLoopClosed, MouseCursor, ControlFlow};
|
||||
use libc;
|
||||
|
||||
use self::x11::XConnection;
|
||||
use self::x11::XError;
|
||||
use self::x11::ffi::XVisualInfo;
|
||||
|
||||
pub use self::x11::XNotSupported;
|
||||
use window::MonitorId as RootMonitorId;
|
||||
|
||||
mod dlopen;
|
||||
pub mod wayland;
|
||||
pub mod x11;
|
||||
|
||||
/// Environment variable specifying which backend should be used on unix platform.
|
||||
///
|
||||
/// Legal values are x11 and wayland. If this variable is set only the named backend
|
||||
/// will be tried by winit. If it is not set, winit will try to connect to a wayland connection,
|
||||
/// and if it fails will fallback on x11.
|
||||
///
|
||||
/// If this variable is set with any other value, winit will panic.
|
||||
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub visual_infos: Option<XVisualInfo>,
|
||||
pub screen_id: Option<i32>,
|
||||
}
|
||||
|
||||
lazy_static!(
|
||||
pub static ref X11_BACKEND: Result<Arc<XConnection>, XNotSupported> = {
|
||||
XConnection::new(Some(x_error_callback)).map(Arc::new)
|
||||
};
|
||||
);
|
||||
|
||||
pub enum Window {
|
||||
X(x11::Window),
|
||||
Wayland(wayland::Window)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum WindowId {
|
||||
X(x11::WindowId),
|
||||
Wayland(wayland::WindowId)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DeviceId {
|
||||
X(x11::DeviceId),
|
||||
Wayland(wayland::DeviceId)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum MonitorId {
|
||||
X(x11::MonitorId),
|
||||
Wayland(wayland::MonitorId),
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_name(),
|
||||
&MonitorId::Wayland(ref m) => m.get_name(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> u32 {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_native_identifier(),
|
||||
&MonitorId::Wayland(ref m) => m.get_native_identifier(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_dimensions(),
|
||||
&MonitorId::Wayland(ref m) => m.get_dimensions(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_position(),
|
||||
&MonitorId::Wayland(ref m) => m.get_position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_hidpi_factor(),
|
||||
&MonitorId::Wayland(ref m) => m.get_hidpi_factor(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
#[inline]
|
||||
pub fn new(events_loop: &EventsLoop,
|
||||
window: &::WindowAttributes,
|
||||
pl_attribs: &PlatformSpecificWindowBuilderAttributes)
|
||||
-> Result<Self, CreationError>
|
||||
{
|
||||
match *events_loop {
|
||||
EventsLoop::Wayland(ref evlp) => {
|
||||
wayland::Window::new(evlp, window).map(Window::Wayland)
|
||||
},
|
||||
|
||||
EventsLoop::X(ref el) => {
|
||||
x11::Window::new(el, window, pl_attribs).map(Window::X)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
match self {
|
||||
&Window::X(ref w) => WindowId::X(w.id()),
|
||||
&Window::Wayland(ref w) => WindowId::Wayland(w.id())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, title: &str) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_title(title),
|
||||
&Window::Wayland(ref w) => w.set_title(title)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.show(),
|
||||
&Window::Wayland(ref w) => w.show()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.hide(),
|
||||
&Window::Wayland(ref w) => w.hide()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_position(),
|
||||
&Window::Wayland(ref w) => w.get_position()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
|
||||
match self {
|
||||
&Window::X(ref m) => m.get_inner_position(),
|
||||
&Window::Wayland(ref m) => m.get_inner_position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, x: i32, y: i32) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_position(x, y),
|
||||
&Window::Wayland(ref w) => w.set_position(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_inner_size(),
|
||||
&Window::Wayland(ref w) => w.get_inner_size()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_outer_size(),
|
||||
&Window::Wayland(ref w) => w.get_outer_size()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, x: u32, y: u32) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_inner_size(x, y),
|
||||
&Window::Wayland(ref w) => w.set_inner_size(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_min_dimensions(dimensions),
|
||||
&Window::Wayland(ref w) => w.set_min_dimensions(dimensions)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_max_dimensions(dimensions),
|
||||
&Window::Wayland(ref w) => w.set_max_dimensions(dimensions)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, cursor: MouseCursor) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_cursor(cursor),
|
||||
&Window::Wayland(ref w) => w.set_cursor(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_cursor_state(state),
|
||||
&Window::Wayland(ref w) => w.set_cursor_state(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f32 {
|
||||
match self {
|
||||
&Window::X(ref w) => w.hidpi_factor(),
|
||||
&Window::Wayland(ref w) => w.hidpi_factor()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_cursor_position(x, y),
|
||||
&Window::Wayland(ref w) => w.set_cursor_position(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_display(&self) -> *mut libc::c_void {
|
||||
use wayland_client::Proxy;
|
||||
match self {
|
||||
&Window::X(ref w) => w.platform_display(),
|
||||
&Window::Wayland(ref w) => w.get_display().ptr() as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_window(&self) -> *mut libc::c_void {
|
||||
use wayland_client::Proxy;
|
||||
match self {
|
||||
&Window::X(ref w) => w.platform_window(),
|
||||
&Window::Wayland(ref w) => w.get_surface().ptr() as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_maximized(maximized),
|
||||
&Window::Wayland(ref w) => w.set_maximized(maximized),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_fullscreen(monitor),
|
||||
&Window::Wayland(ref w) => w.set_fullscreen(monitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_decorations(decorations),
|
||||
&Window::Wayland(ref w) => w.set_decorations(decorations)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
match self {
|
||||
&Window::X(ref w) => RootMonitorId{inner: MonitorId::X(w.get_current_monitor())},
|
||||
&Window::Wayland(ref w) => RootMonitorId{inner: MonitorId::Wayland(w.get_current_monitor())},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn x_error_callback(dpy: *mut x11::ffi::Display, event: *mut x11::ffi::XErrorEvent)
|
||||
-> libc::c_int
|
||||
{
|
||||
use std::ffi::CStr;
|
||||
|
||||
if let Ok(ref x) = *X11_BACKEND {
|
||||
let mut buff: Vec<u8> = Vec::with_capacity(1024);
|
||||
(x.xlib.XGetErrorText)(dpy, (*event).error_code as i32, buff.as_mut_ptr() as *mut libc::c_char, buff.capacity() as i32);
|
||||
let description = CStr::from_ptr(buff.as_mut_ptr() as *const libc::c_char).to_string_lossy();
|
||||
|
||||
let error = XError {
|
||||
description: description.into_owned(),
|
||||
error_code: (*event).error_code,
|
||||
request_code: (*event).request_code,
|
||||
minor_code: (*event).minor_code,
|
||||
};
|
||||
|
||||
*x.latest_error.lock().unwrap() = Some(error);
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
pub enum EventsLoop {
|
||||
Wayland(wayland::EventsLoop),
|
||||
X(x11::EventsLoop)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum EventsLoopProxy {
|
||||
X(x11::EventsLoopProxy),
|
||||
Wayland(wayland::EventsLoopProxy),
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn new() -> EventsLoop {
|
||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
||||
match env_var.as_str() {
|
||||
"x11" => {
|
||||
return EventsLoop::new_x11().unwrap(); // TODO: propagate
|
||||
},
|
||||
"wayland" => {
|
||||
match EventsLoop::new_wayland() {
|
||||
Ok(e) => return e,
|
||||
Err(_) => panic!() // TODO: propagate
|
||||
}
|
||||
},
|
||||
_ => panic!("Unknown environment variable value for {}, try one of `x11`,`wayland`",
|
||||
BACKEND_PREFERENCE_ENV_VAR),
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(el) = EventsLoop::new_wayland() {
|
||||
return el;
|
||||
}
|
||||
|
||||
if let Ok(el) = EventsLoop::new_x11() {
|
||||
return el;
|
||||
}
|
||||
|
||||
panic!("No backend is available")
|
||||
}
|
||||
|
||||
pub fn new_wayland() -> Result<EventsLoop, ()> {
|
||||
wayland::EventsLoop::new()
|
||||
.map(EventsLoop::Wayland)
|
||||
.ok_or(())
|
||||
}
|
||||
|
||||
pub fn new_x11() -> Result<EventsLoop, XNotSupported> {
|
||||
match *X11_BACKEND {
|
||||
Ok(ref x) => Ok(EventsLoop::X(x11::EventsLoop::new(x.clone()))),
|
||||
Err(ref err) => Err(err.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref evlp) => evlp.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorId::Wayland)
|
||||
.collect(),
|
||||
EventsLoop::X(ref evlp) => x11::get_available_monitors(evlp.x_connection())
|
||||
.into_iter()
|
||||
.map(MonitorId::X)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref evlp) => MonitorId::Wayland(evlp.get_primary_monitor()),
|
||||
EventsLoop::X(ref evlp) => MonitorId::X(x11::get_primary_monitor(evlp.x_connection())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref evlp) => EventsLoopProxy::Wayland(evlp.create_proxy()),
|
||||
EventsLoop::X(ref evlp) => EventsLoopProxy::X(evlp.create_proxy()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, callback: F)
|
||||
where F: FnMut(::Event)
|
||||
{
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref mut evlp) => evlp.poll_events(callback),
|
||||
EventsLoop::X(ref mut evlp) => evlp.poll_events(callback)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, callback: F)
|
||||
where F: FnMut(::Event) -> ControlFlow
|
||||
{
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref mut evlp) => evlp.run_forever(callback),
|
||||
EventsLoop::X(ref mut evlp) => evlp.run_forever(callback)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_wayland(&self) -> bool {
|
||||
match *self {
|
||||
EventsLoop::Wayland(_) => true,
|
||||
EventsLoop::X(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x_connection(&self) -> Option<&Arc<XConnection>> {
|
||||
match *self {
|
||||
EventsLoop::Wayland(_) => None,
|
||||
EventsLoop::X(ref ev) => Some(ev.x_connection()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
|
||||
match *self {
|
||||
EventsLoopProxy::Wayland(ref proxy) => proxy.wakeup(),
|
||||
EventsLoopProxy::X(ref proxy) => proxy.wakeup(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,594 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use {EventsLoopClosed, ControlFlow};
|
||||
|
||||
use super::WindowId;
|
||||
use super::window::WindowStore;
|
||||
use super::keyboard::init_keyboard;
|
||||
|
||||
use wayland_client::{EnvHandler, EnvNotify, default_connect, EventQueue, EventQueueHandle, Proxy, StateToken};
|
||||
use wayland_client::protocol::{wl_compositor, wl_seat, wl_shell, wl_shm, wl_subcompositor,
|
||||
wl_display, wl_registry, wl_output, wl_surface,
|
||||
wl_pointer, wl_keyboard, wl_touch};
|
||||
|
||||
use super::wayland_window::{Frame, Shell, create_frame, FrameImplementation};
|
||||
use super::wayland_protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6;
|
||||
|
||||
pub struct EventsLoopSink {
|
||||
buffer: VecDeque<::Event>
|
||||
}
|
||||
|
||||
unsafe impl Send for EventsLoopSink { }
|
||||
|
||||
impl EventsLoopSink {
|
||||
pub fn new() -> EventsLoopSink{
|
||||
EventsLoopSink {
|
||||
buffer: VecDeque::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_event(&mut self, evt: ::WindowEvent, wid: WindowId) {
|
||||
let evt = ::Event::WindowEvent {
|
||||
event: evt,
|
||||
window_id: ::WindowId(::platform::WindowId::Wayland(wid))
|
||||
};
|
||||
self.buffer.push_back(evt);
|
||||
}
|
||||
|
||||
pub fn send_raw_event(&mut self, evt: ::Event) {
|
||||
self.buffer.push_back(evt);
|
||||
}
|
||||
|
||||
fn empty_with<F>(&mut self, callback: &mut F) where F: FnMut(::Event) {
|
||||
for evt in self.buffer.drain(..) {
|
||||
callback(evt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventsLoop {
|
||||
// The Event Queue
|
||||
pub evq: RefCell<EventQueue>,
|
||||
// our sink, shared with some handlers, buffering the events
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
// Whether or not there is a pending `Awakened` event to be emitted.
|
||||
pending_wakeup: Arc<AtomicBool>,
|
||||
// The window store
|
||||
pub store: StateToken<WindowStore>,
|
||||
// the env
|
||||
env_token: StateToken<EnvHandler<InnerEnv>>,
|
||||
// the ctxt
|
||||
pub ctxt_token: StateToken<StateContext>,
|
||||
// a cleanup switch to prune dead windows
|
||||
pub cleanup_needed: Arc<Mutex<bool>>,
|
||||
// The wayland display
|
||||
pub display: Arc<wl_display::WlDisplay>,
|
||||
}
|
||||
|
||||
// A handle that can be sent across threads and used to wake up the `EventsLoop`.
|
||||
//
|
||||
// We should only try and wake up the `EventsLoop` if it still exists, so we hold Weak ptrs.
|
||||
#[derive(Clone)]
|
||||
pub struct EventsLoopProxy {
|
||||
display: Weak<wl_display::WlDisplay>,
|
||||
pending_wakeup: Weak<AtomicBool>,
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
// Causes the `EventsLoop` to stop blocking on `run_forever` and emit an `Awakened` event.
|
||||
//
|
||||
// Returns `Err` if the associated `EventsLoop` no longer exists.
|
||||
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
|
||||
let display = self.display.upgrade();
|
||||
let wakeup = self.pending_wakeup.upgrade();
|
||||
match (display, wakeup) {
|
||||
(Some(display), Some(wakeup)) => {
|
||||
// Update the `EventsLoop`'s `pending_wakeup` flag.
|
||||
wakeup.store(true, Ordering::Relaxed);
|
||||
// Cause the `EventsLoop` to break from `dispatch` if it is currently blocked.
|
||||
display.sync();
|
||||
display.flush().map_err(|_| EventsLoopClosed)?;
|
||||
Ok(())
|
||||
},
|
||||
_ => Err(EventsLoopClosed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn new() -> Option<EventsLoop> {
|
||||
let (display, mut event_queue) = match default_connect() {
|
||||
Ok(ret) => ret,
|
||||
Err(_) => return None
|
||||
};
|
||||
|
||||
let registry = display.get_registry();
|
||||
let ctxt_token = event_queue.state().insert(
|
||||
StateContext::new(registry.clone().unwrap())
|
||||
);
|
||||
let env_token = EnvHandler::init_with_notify(
|
||||
&mut event_queue,
|
||||
®istry,
|
||||
env_notify(),
|
||||
ctxt_token.clone()
|
||||
);
|
||||
|
||||
// two round trips to fully initialize
|
||||
event_queue.sync_roundtrip().expect("Wayland connection unexpectedly lost");
|
||||
event_queue.sync_roundtrip().expect("Wayland connection unexpectedly lost");
|
||||
|
||||
event_queue.state().with_value(&ctxt_token, |proxy, ctxt| {
|
||||
ctxt.ensure_shell(proxy.get_mut(&env_token))
|
||||
});
|
||||
|
||||
let sink = Arc::new(Mutex::new(EventsLoopSink::new()));
|
||||
|
||||
let store = event_queue.state().insert(WindowStore::new());
|
||||
|
||||
let seat_idata = SeatIData {
|
||||
sink: sink.clone(),
|
||||
keyboard: None,
|
||||
pointer: None,
|
||||
touch: None,
|
||||
windows_token: store.clone()
|
||||
};
|
||||
|
||||
let mut me = EventsLoop {
|
||||
display: Arc::new(display),
|
||||
evq: RefCell::new(event_queue),
|
||||
sink: sink,
|
||||
pending_wakeup: Arc::new(AtomicBool::new(false)),
|
||||
store: store,
|
||||
ctxt_token: ctxt_token,
|
||||
env_token: env_token,
|
||||
cleanup_needed: Arc::new(Mutex::new(false))
|
||||
};
|
||||
|
||||
me.init_seat(|evqh, seat| {
|
||||
evqh.register(seat, seat_implementation(), seat_idata);
|
||||
});
|
||||
|
||||
Some(me)
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
EventsLoopProxy {
|
||||
display: Arc::downgrade(&self.display),
|
||||
pending_wakeup: Arc::downgrade(&self.pending_wakeup),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event)
|
||||
{
|
||||
// send pending events to the server
|
||||
self.display.flush().expect("Wayland connection lost.");
|
||||
|
||||
// dispatch any pre-buffered events
|
||||
self.sink.lock().unwrap().empty_with(&mut callback);
|
||||
|
||||
// try to read pending events
|
||||
if let Some(h) = self.evq.get_mut().prepare_read() {
|
||||
h.read_events().expect("Wayland connection lost.");
|
||||
}
|
||||
// dispatch wayland events
|
||||
self.evq.get_mut().dispatch_pending().expect("Wayland connection lost.");
|
||||
self.post_dispatch_triggers();
|
||||
|
||||
// dispatch buffered events to client
|
||||
self.sink.lock().unwrap().empty_with(&mut callback);
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event) -> ControlFlow,
|
||||
{
|
||||
// send pending events to the server
|
||||
self.display.flush().expect("Wayland connection lost.");
|
||||
|
||||
// Check for control flow by wrapping the callback.
|
||||
let control_flow = ::std::cell::Cell::new(ControlFlow::Continue);
|
||||
let mut callback = |event| if let ControlFlow::Break = callback(event) {
|
||||
control_flow.set(ControlFlow::Break);
|
||||
};
|
||||
|
||||
// dispatch any pre-buffered events
|
||||
self.post_dispatch_triggers();
|
||||
self.sink.lock().unwrap().empty_with(&mut callback);
|
||||
|
||||
loop {
|
||||
// dispatch events blocking if needed
|
||||
self.evq.get_mut().dispatch().expect("Wayland connection lost.");
|
||||
self.post_dispatch_triggers();
|
||||
|
||||
// empty buffer of events
|
||||
self.sink.lock().unwrap().empty_with(&mut callback);
|
||||
|
||||
if let ControlFlow::Break = control_flow.get() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
let mut guard = self.evq.borrow_mut();
|
||||
let state = guard.state();
|
||||
let state_ctxt = state.get(&self.ctxt_token);
|
||||
if let Some(info) = state_ctxt.monitors.iter().next() {
|
||||
MonitorId {
|
||||
info: info.clone()
|
||||
}
|
||||
} else {
|
||||
panic!("No monitor is available.")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut guard = self.evq.borrow_mut();
|
||||
let state = guard.state();
|
||||
let state_ctxt = state.get(&self.ctxt_token);
|
||||
state_ctxt.monitors.iter()
|
||||
.map(|m| MonitorId { info: m.clone() })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Private EventsLoop Internals
|
||||
*/
|
||||
|
||||
wayland_env!(InnerEnv,
|
||||
compositor: wl_compositor::WlCompositor,
|
||||
shm: wl_shm::WlShm,
|
||||
subcompositor: wl_subcompositor::WlSubcompositor
|
||||
);
|
||||
|
||||
pub struct StateContext {
|
||||
registry: wl_registry::WlRegistry,
|
||||
seat: Option<wl_seat::WlSeat>,
|
||||
shell: Option<Shell>,
|
||||
monitors: Vec<Arc<Mutex<OutputInfo>>>
|
||||
}
|
||||
|
||||
impl StateContext {
|
||||
fn new(registry: wl_registry::WlRegistry) -> StateContext {
|
||||
StateContext {
|
||||
registry: registry,
|
||||
seat: None,
|
||||
shell: None,
|
||||
monitors: Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures a shell is available
|
||||
///
|
||||
/// If a shell is already bound, do nothing. Otherwise,
|
||||
/// try to bind wl_shell as a fallback. If this fails,
|
||||
/// panic, as this is a bug from the compositor.
|
||||
fn ensure_shell(&mut self, env: &mut EnvHandler<InnerEnv>) {
|
||||
if self.shell.is_some() {
|
||||
return;
|
||||
}
|
||||
// xdg_shell is not available, so initialize wl_shell
|
||||
for &(name, ref interface, _) in env.globals() {
|
||||
if interface == "wl_shell" {
|
||||
self.shell = Some(Shell::Wl(self.registry.bind::<wl_shell::WlShell>(1, name)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// This is a compositor bug, it _must_ at least support wl_shell
|
||||
panic!("Compositor didi not advertize xdg_shell not wl_shell.");
|
||||
}
|
||||
|
||||
pub fn monitor_id_for(&self, output: &wl_output::WlOutput) -> MonitorId {
|
||||
for info in &self.monitors {
|
||||
let guard = info.lock().unwrap();
|
||||
if guard.output.equals(output) {
|
||||
return MonitorId {
|
||||
info: info.clone()
|
||||
};
|
||||
}
|
||||
}
|
||||
panic!("Received an inexistent wl_output?!");
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn init_seat<F>(&mut self, f: F)
|
||||
where F: FnOnce(&mut EventQueueHandle, &wl_seat::WlSeat)
|
||||
{
|
||||
let mut guard = self.evq.borrow_mut();
|
||||
if guard.state().get(&self.ctxt_token).seat.is_some() {
|
||||
// seat has already been init
|
||||
return;
|
||||
}
|
||||
|
||||
// clone the token to make borrow checker happy
|
||||
let ctxt_token = self.ctxt_token.clone();
|
||||
let seat = guard.state().with_value(&self.env_token, |proxy, env| {
|
||||
let ctxt = proxy.get(&ctxt_token);
|
||||
for &(name, ref interface, _) in env.globals() {
|
||||
if interface == wl_seat::WlSeat::interface_name() {
|
||||
return Some(ctxt.registry.bind::<wl_seat::WlSeat>(5, name));
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
if let Some(seat) = seat {
|
||||
f(&mut *guard, &seat);
|
||||
guard.state().get_mut(&self.ctxt_token).seat = Some(seat)
|
||||
}
|
||||
}
|
||||
|
||||
fn post_dispatch_triggers(&mut self) {
|
||||
let mut sink = self.sink.lock().unwrap();
|
||||
let evq = self.evq.get_mut();
|
||||
// process a possible pending wakeup call
|
||||
if self.pending_wakeup.load(Ordering::Relaxed) {
|
||||
sink.send_raw_event(::Event::Awakened);
|
||||
self.pending_wakeup.store(false, Ordering::Relaxed);
|
||||
}
|
||||
// prune possible dead windows
|
||||
{
|
||||
let mut cleanup_needed = self.cleanup_needed.lock().unwrap();
|
||||
if *cleanup_needed {
|
||||
let pruned = evq.state().get_mut(&self.store).cleanup();
|
||||
*cleanup_needed = false;
|
||||
for wid in pruned {
|
||||
sink.send_event(::WindowEvent::Destroyed, wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
// process pending resize/refresh
|
||||
evq.state().get_mut(&self.store).for_each(
|
||||
|newsize, refresh, frame_refresh, closed, wid, frame| {
|
||||
if let Some(frame) = frame {
|
||||
if let Some((w, h)) = newsize {
|
||||
frame.resize(w as i32, h as i32);
|
||||
frame.refresh();
|
||||
sink.send_event(::WindowEvent::Resized(w as u32, h as u32), wid);
|
||||
} else if frame_refresh {
|
||||
frame.refresh();
|
||||
}
|
||||
}
|
||||
if refresh {
|
||||
sink.send_event(::WindowEvent::Refresh, wid);
|
||||
}
|
||||
if closed {
|
||||
sink.send_event(::WindowEvent::CloseRequested, wid);
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new window with given dimensions
|
||||
///
|
||||
/// Grabs a lock on the event queue in the process
|
||||
pub fn create_window<ID: 'static, F>(&self, width: u32, height: u32, implem: FrameImplementation<ID>, idata: F)
|
||||
-> (wl_surface::WlSurface, Frame)
|
||||
where F: FnOnce(&wl_surface::WlSurface) -> ID
|
||||
{
|
||||
let (surface, frame) = {
|
||||
let mut guard = self.evq.borrow_mut();
|
||||
let env = guard.state().get(&self.env_token).clone_inner().unwrap();
|
||||
let shell = match guard.state().get(&self.ctxt_token).shell {
|
||||
Some(Shell::Wl(ref wl_shell)) => Shell::Wl(wl_shell.clone().unwrap()),
|
||||
Some(Shell::Xdg(ref xdg_shell)) => Shell::Xdg(xdg_shell.clone().unwrap()),
|
||||
None => unreachable!()
|
||||
};
|
||||
let seat = guard.state().get(&self.ctxt_token).seat.as_ref().and_then(|s| s.clone());
|
||||
let surface = env.compositor.create_surface();
|
||||
let frame = create_frame(
|
||||
&mut guard,
|
||||
implem,
|
||||
idata(&surface),
|
||||
&surface, width as i32, height as i32,
|
||||
&env.compositor,
|
||||
&env.subcompositor,
|
||||
&env.shm,
|
||||
&shell,
|
||||
seat
|
||||
).expect("Failed to create a tmpfile buffer.");
|
||||
(surface, frame)
|
||||
};
|
||||
|
||||
(surface, frame)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Wayland protocol implementations
|
||||
*/
|
||||
|
||||
fn env_notify() -> EnvNotify<StateToken<StateContext>> {
|
||||
EnvNotify {
|
||||
new_global: |evqh, token, registry, id, interface, version| {
|
||||
use std::cmp::min;
|
||||
if interface == wl_output::WlOutput::interface_name() {
|
||||
// a new output is available
|
||||
let output = registry.bind::<wl_output::WlOutput>(min(version, 3), id);
|
||||
evqh.register(&output, output_impl(), token.clone());
|
||||
evqh.state().get_mut(&token).monitors.push(
|
||||
Arc::new(Mutex::new(OutputInfo::new(output, id)))
|
||||
);
|
||||
} else if interface == zxdg_shell_v6::ZxdgShellV6::interface_name() {
|
||||
// We have an xdg_shell, bind it
|
||||
let xdg_shell = registry.bind::<zxdg_shell_v6::ZxdgShellV6>(1, id);
|
||||
evqh.register(&xdg_shell, xdg_ping_implementation(), ());
|
||||
evqh.state().get_mut(&token).shell = Some(Shell::Xdg(xdg_shell));
|
||||
}
|
||||
},
|
||||
del_global: |evqh, token, _, id| {
|
||||
// maybe this was a monitor, cleanup
|
||||
evqh.state().get_mut(&token).monitors.retain(
|
||||
|m| m.lock().unwrap().id != id
|
||||
);
|
||||
},
|
||||
ready: |_, _, _| {}
|
||||
}
|
||||
}
|
||||
|
||||
fn xdg_ping_implementation() -> zxdg_shell_v6::Implementation<()> {
|
||||
zxdg_shell_v6::Implementation {
|
||||
ping: |_, _, shell, serial| {
|
||||
shell.pong(serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SeatIData {
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
pointer: Option<wl_pointer::WlPointer>,
|
||||
keyboard: Option<wl_keyboard::WlKeyboard>,
|
||||
touch: Option<wl_touch::WlTouch>,
|
||||
windows_token: StateToken<WindowStore>
|
||||
}
|
||||
|
||||
fn seat_implementation() -> wl_seat::Implementation<SeatIData> {
|
||||
wl_seat::Implementation {
|
||||
name: |_, _, _, _| {},
|
||||
capabilities: |evqh, idata, seat, capabilities| {
|
||||
// create pointer if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Pointer) && idata.pointer.is_none() {
|
||||
let pointer = seat.get_pointer().expect("Seat is not dead");
|
||||
let p_idata = super::pointer::PointerIData::new(
|
||||
&idata.sink,
|
||||
idata.windows_token.clone()
|
||||
);
|
||||
evqh.register(&pointer, super::pointer::pointer_implementation(), p_idata);
|
||||
idata.pointer = Some(pointer);
|
||||
}
|
||||
// destroy pointer if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Pointer) {
|
||||
if let Some(pointer) = idata.pointer.take() {
|
||||
pointer.release();
|
||||
}
|
||||
}
|
||||
// create keyboard if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Keyboard) && idata.keyboard.is_none() {
|
||||
let kbd = seat.get_keyboard().expect("Seat is not dead");
|
||||
init_keyboard(evqh, &kbd, &idata.sink);
|
||||
idata.keyboard = Some(kbd);
|
||||
}
|
||||
// destroy keyboard if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Keyboard) {
|
||||
if let Some(kbd) = idata.keyboard.take() {
|
||||
kbd.release();
|
||||
}
|
||||
}
|
||||
// create touch if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Touch) && idata.touch.is_none() {
|
||||
let touch = seat.get_touch().expect("Seat is not dead");
|
||||
let t_idata = super::touch::TouchIData::new(
|
||||
&idata.sink,
|
||||
idata.windows_token.clone()
|
||||
);
|
||||
evqh.register(&touch, super::touch::touch_implementation(), t_idata);
|
||||
idata.touch = Some(touch);
|
||||
}
|
||||
// destroy touch if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Touch) {
|
||||
if let Some(touch) = idata.touch.take() {
|
||||
touch.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Monitor stuff
|
||||
*/
|
||||
|
||||
fn output_impl() -> wl_output::Implementation<StateToken<StateContext>> {
|
||||
wl_output::Implementation {
|
||||
geometry: |evqh, token, output, x, y, _, _, _, make, model, _| {
|
||||
let ctxt = evqh.state().get_mut(token);
|
||||
for info in &ctxt.monitors {
|
||||
let mut guard = info.lock().unwrap();
|
||||
if guard.output.equals(output) {
|
||||
guard.pix_pos = (x, y);
|
||||
guard.name = format!("{} - {}", make, model);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
mode: |evqh, token, output, flags, w, h, _refresh| {
|
||||
if flags.contains(wl_output::Mode::Current) {
|
||||
let ctxt = evqh.state().get_mut(token);
|
||||
for info in &ctxt.monitors {
|
||||
let mut guard = info.lock().unwrap();
|
||||
if guard.output.equals(output) {
|
||||
guard.pix_size = (w as u32, h as u32);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
done: |_, _, _| {},
|
||||
scale: |evqh, token, output, scale| {
|
||||
let ctxt = evqh.state().get_mut(token);
|
||||
for info in &ctxt.monitors {
|
||||
let mut guard = info.lock().unwrap();
|
||||
if guard.output.equals(output) {
|
||||
guard.scale = scale as f32;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutputInfo {
|
||||
pub output: wl_output::WlOutput,
|
||||
pub id: u32,
|
||||
pub scale: f32,
|
||||
pub pix_size: (u32, u32),
|
||||
pub pix_pos: (i32, i32),
|
||||
pub name: String
|
||||
}
|
||||
|
||||
impl OutputInfo {
|
||||
fn new(output: wl_output::WlOutput, id: u32) -> OutputInfo {
|
||||
OutputInfo {
|
||||
output: output,
|
||||
id: id,
|
||||
scale: 1.0,
|
||||
pix_size: (0, 0),
|
||||
pix_pos: (0, 0),
|
||||
name: "".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId {
|
||||
pub info: Arc<Mutex<OutputInfo>>
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some(self.info.lock().unwrap().name.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> u32 {
|
||||
self.info.lock().unwrap().id
|
||||
}
|
||||
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
self.info.lock().unwrap().pix_size
|
||||
}
|
||||
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
self.info.lock().unwrap().pix_pos
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
self.info.lock().unwrap().scale
|
||||
}
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use {VirtualKeyCode, ElementState, WindowEvent as Event, KeyboardInput, ModifiersState};
|
||||
|
||||
use super::{EventsLoopSink, WindowId, make_wid, DeviceId};
|
||||
use super::wayland_kbd::{MappedKeyboardImplementation, register_kbd};
|
||||
use wayland_client::protocol::wl_keyboard;
|
||||
use wayland_client::EventQueueHandle;
|
||||
|
||||
pub fn init_keyboard(evq: &mut EventQueueHandle, keyboard: &wl_keyboard::WlKeyboard, sink: &Arc<Mutex<EventsLoopSink>>) {
|
||||
let idata = KeyboardIData {
|
||||
sink: sink.clone(),
|
||||
target: None
|
||||
};
|
||||
|
||||
if register_kbd(evq, keyboard, mapped_keyboard_impl(), idata).is_err() {
|
||||
// initializing libxkbcommon failed :(
|
||||
// fallback implementation
|
||||
let idata = KeyboardIData {
|
||||
sink: sink.clone(),
|
||||
target: None
|
||||
};
|
||||
evq.register(keyboard, raw_keyboard_impl(), idata);
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyboardIData {
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
target: Option<WindowId>
|
||||
}
|
||||
|
||||
fn mapped_keyboard_impl() -> MappedKeyboardImplementation<KeyboardIData> {
|
||||
MappedKeyboardImplementation {
|
||||
enter: |_, idata, _, _, surface, _, _, _| {
|
||||
let wid = make_wid(surface);
|
||||
idata.sink.lock().unwrap().send_event(Event::Focused(true), wid);
|
||||
idata.target = Some(wid);
|
||||
},
|
||||
leave: |_, idata, _, _, surface| {
|
||||
let wid = make_wid(surface);
|
||||
idata.sink.lock().unwrap().send_event(Event::Focused(false), wid);
|
||||
idata.target = None;
|
||||
},
|
||||
key: |_, idata, _, _, _, mods, rawkey, keysym, state, utf8| {
|
||||
if let Some(wid) = idata.target {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
};
|
||||
let vkcode = key_to_vkey(rawkey, keysym);
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
Event::KeyboardInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: ModifiersState {
|
||||
shift: mods.shift,
|
||||
ctrl: mods.ctrl,
|
||||
alt: mods.alt,
|
||||
logo: mods.logo
|
||||
},
|
||||
},
|
||||
},
|
||||
wid
|
||||
);
|
||||
// send char event only on key press, not release
|
||||
if let ElementState::Released = state { return }
|
||||
if let Some(txt) = utf8 {
|
||||
for chr in txt.chars() {
|
||||
guard.send_event(Event::ReceivedCharacter(chr), wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
repeat_info: |_, _idata, _, _rate, _delay| {
|
||||
// TODO: handle repeat info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This is fallback impl if libxkbcommon was not available
|
||||
// This case should probably never happen, as most wayland
|
||||
// compositors _need_ libxkbcommon anyway...
|
||||
//
|
||||
// In this case, we don't have the keymap information (it is
|
||||
// supposed to be serialized by the compositor using libxkbcommon)
|
||||
fn raw_keyboard_impl() -> wl_keyboard::Implementation<KeyboardIData> {
|
||||
wl_keyboard::Implementation {
|
||||
enter: |_, idata, _, _, surface, _| {
|
||||
let wid = make_wid(surface);
|
||||
idata.sink.lock().unwrap().send_event(Event::Focused(true), wid);
|
||||
idata.target = Some(wid);
|
||||
},
|
||||
leave: |_, idata, _, _, surface| {
|
||||
let wid = make_wid(surface);
|
||||
idata.sink.lock().unwrap().send_event(Event::Focused(false), wid);
|
||||
idata.target = None;
|
||||
},
|
||||
key: |_, idata, _, _, _, key, state| {
|
||||
if let Some(wid) = idata.target {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
};
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::KeyboardInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: key,
|
||||
virtual_keycode: None,
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
},
|
||||
wid
|
||||
);
|
||||
}
|
||||
},
|
||||
repeat_info: |_, _idata, _, _rate, _delay| {},
|
||||
keymap: |_, _, _, _, _, _| {},
|
||||
modifiers: |_, _, _, _, _, _, _, _| {}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_to_vkey(rawkey: u32, keysym: u32) -> Option<VirtualKeyCode> {
|
||||
match rawkey {
|
||||
1 => Some(VirtualKeyCode::Escape),
|
||||
2 => Some(VirtualKeyCode::Key1),
|
||||
3 => Some(VirtualKeyCode::Key2),
|
||||
4 => Some(VirtualKeyCode::Key3),
|
||||
5 => Some(VirtualKeyCode::Key4),
|
||||
6 => Some(VirtualKeyCode::Key5),
|
||||
7 => Some(VirtualKeyCode::Key6),
|
||||
8 => Some(VirtualKeyCode::Key7),
|
||||
9 => Some(VirtualKeyCode::Key8),
|
||||
10 => Some(VirtualKeyCode::Key9),
|
||||
11 => Some(VirtualKeyCode::Key0),
|
||||
_ => keysym_to_vkey(keysym)
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
use super::wayland_kbd::keysyms;
|
||||
match keysym {
|
||||
// letters
|
||||
keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A),
|
||||
keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B),
|
||||
keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C),
|
||||
keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D),
|
||||
keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E),
|
||||
keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F),
|
||||
keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G),
|
||||
keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H),
|
||||
keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I),
|
||||
keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J),
|
||||
keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K),
|
||||
keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L),
|
||||
keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M),
|
||||
keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N),
|
||||
keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O),
|
||||
keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P),
|
||||
keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q),
|
||||
keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R),
|
||||
keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S),
|
||||
keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T),
|
||||
keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U),
|
||||
keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V),
|
||||
keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W),
|
||||
keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X),
|
||||
keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y),
|
||||
keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z),
|
||||
// F--
|
||||
keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1),
|
||||
keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2),
|
||||
keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3),
|
||||
keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4),
|
||||
keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5),
|
||||
keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6),
|
||||
keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7),
|
||||
keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8),
|
||||
keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9),
|
||||
keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10),
|
||||
keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11),
|
||||
keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12),
|
||||
keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13),
|
||||
keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14),
|
||||
keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15),
|
||||
// flow control
|
||||
keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot),
|
||||
keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll),
|
||||
keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause),
|
||||
keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert),
|
||||
keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home),
|
||||
keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete),
|
||||
keysyms::XKB_KEY_End => Some(VirtualKeyCode::End),
|
||||
keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown),
|
||||
keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp),
|
||||
// arrows
|
||||
keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left),
|
||||
keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up),
|
||||
keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right),
|
||||
keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down),
|
||||
//
|
||||
keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back),
|
||||
keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return),
|
||||
keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space),
|
||||
// keypad
|
||||
keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock),
|
||||
keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0),
|
||||
keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1),
|
||||
keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2),
|
||||
keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3),
|
||||
keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4),
|
||||
keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5),
|
||||
keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6),
|
||||
keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7),
|
||||
keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8),
|
||||
keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9),
|
||||
// misc
|
||||
// => Some(VirtualKeyCode::AbntC1),
|
||||
// => Some(VirtualKeyCode::AbntC2),
|
||||
keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Add),
|
||||
keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe),
|
||||
// => Some(VirtualKeyCode::Apps),
|
||||
// => Some(VirtualKeyCode::At),
|
||||
// => Some(VirtualKeyCode::Ax),
|
||||
keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash),
|
||||
// => Some(VirtualKeyCode::Calculator),
|
||||
// => Some(VirtualKeyCode::Capital),
|
||||
keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon),
|
||||
keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma),
|
||||
// => Some(VirtualKeyCode::Convert),
|
||||
// => Some(VirtualKeyCode::Decimal),
|
||||
// => Some(VirtualKeyCode::Divide),
|
||||
keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals),
|
||||
// => Some(VirtualKeyCode::Grave),
|
||||
// => Some(VirtualKeyCode::Kana),
|
||||
// => Some(VirtualKeyCode::Kanji),
|
||||
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
|
||||
// => Some(VirtualKeyCode::LBracket),
|
||||
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
|
||||
// => Some(VirtualKeyCode::LMenu),
|
||||
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
|
||||
// => Some(VirtualKeyCode::LWin),
|
||||
// => Some(VirtualKeyCode::Mail),
|
||||
// => Some(VirtualKeyCode::MediaSelect),
|
||||
// => Some(VirtualKeyCode::MediaStop),
|
||||
keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus),
|
||||
keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Multiply),
|
||||
// => Some(VirtualKeyCode::Mute),
|
||||
// => Some(VirtualKeyCode::MyComputer),
|
||||
// => Some(VirtualKeyCode::NextTrack),
|
||||
// => Some(VirtualKeyCode::NoConvert),
|
||||
keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma),
|
||||
keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter),
|
||||
keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals),
|
||||
// => Some(VirtualKeyCode::OEM102),
|
||||
// => Some(VirtualKeyCode::Period),
|
||||
// => Some(VirtualKeyCode::Playpause),
|
||||
// => Some(VirtualKeyCode::Power),
|
||||
// => Some(VirtualKeyCode::Prevtrack),
|
||||
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
|
||||
// => Some(VirtualKeyCode::RBracket),
|
||||
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
|
||||
// => Some(VirtualKeyCode::RMenu),
|
||||
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
|
||||
// => Some(VirtualKeyCode::RWin),
|
||||
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
|
||||
keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash),
|
||||
// => Some(VirtualKeyCode::Sleep),
|
||||
// => Some(VirtualKeyCode::Stop),
|
||||
// => Some(VirtualKeyCode::Subtract),
|
||||
// => Some(VirtualKeyCode::Sysrq),
|
||||
keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab),
|
||||
keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab),
|
||||
// => Some(VirtualKeyCode::Underline),
|
||||
// => Some(VirtualKeyCode::Unlabeled),
|
||||
keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown),
|
||||
keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp),
|
||||
// => Some(VirtualKeyCode::Wake),
|
||||
// => Some(VirtualKeyCode::Webback),
|
||||
// => Some(VirtualKeyCode::WebFavorites),
|
||||
// => Some(VirtualKeyCode::WebForward),
|
||||
// => Some(VirtualKeyCode::WebHome),
|
||||
// => Some(VirtualKeyCode::WebRefresh),
|
||||
// => Some(VirtualKeyCode::WebSearch),
|
||||
// => Some(VirtualKeyCode::WebStop),
|
||||
// => Some(VirtualKeyCode::Yen),
|
||||
// fallback
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
|
||||
pub use self::window::Window;
|
||||
pub use self::event_loop::{EventsLoop, EventsLoopProxy, EventsLoopSink, MonitorId};
|
||||
|
||||
extern crate wayland_kbd;
|
||||
extern crate wayland_window;
|
||||
extern crate wayland_protocols;
|
||||
|
||||
use wayland_client::protocol::wl_surface;
|
||||
use wayland_client::Proxy;
|
||||
|
||||
mod event_loop;
|
||||
mod pointer;
|
||||
mod touch;
|
||||
mod keyboard;
|
||||
mod window;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(usize);
|
||||
|
||||
#[inline]
|
||||
fn make_wid(s: &wl_surface::WlSurface) -> WindowId {
|
||||
WindowId(s.ptr() as usize)
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use {WindowEvent as Event, ElementState, MouseButton, MouseScrollDelta, TouchPhase};
|
||||
use events::ModifiersState;
|
||||
|
||||
use super::{WindowId, DeviceId};
|
||||
use super::event_loop::EventsLoopSink;
|
||||
use super::window::WindowStore;
|
||||
|
||||
use wayland_client::{Proxy, StateToken};
|
||||
use wayland_client::protocol::wl_pointer;
|
||||
|
||||
pub struct PointerIData {
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
windows_token: StateToken<WindowStore>,
|
||||
mouse_focus: Option<WindowId>,
|
||||
axis_buffer: Option<(f32, f32)>,
|
||||
axis_discrete_buffer: Option<(i32, i32)>,
|
||||
axis_state: TouchPhase,
|
||||
}
|
||||
|
||||
impl PointerIData {
|
||||
pub fn new(sink: &Arc<Mutex<EventsLoopSink>>, token: StateToken<WindowStore>)
|
||||
-> PointerIData
|
||||
{
|
||||
PointerIData {
|
||||
sink: sink.clone(),
|
||||
windows_token: token,
|
||||
mouse_focus: None,
|
||||
axis_buffer: None,
|
||||
axis_discrete_buffer: None,
|
||||
axis_state: TouchPhase::Cancelled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pointer_implementation() -> wl_pointer::Implementation<PointerIData> {
|
||||
wl_pointer::Implementation {
|
||||
enter: |evqh, idata, _, _, surface, x, y| {
|
||||
let wid = evqh.state().get(&idata.windows_token).find_wid(surface);
|
||||
if let Some(wid) = wid {
|
||||
idata.mouse_focus = Some(wid);
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
Event::CursorEntered {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
guard.send_event(
|
||||
Event::CursorMoved {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
position: (x, y),
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
leave: |evqh, idata, _, _, surface| {
|
||||
idata.mouse_focus = None;
|
||||
let wid = evqh.state().get(&idata.windows_token).find_wid(surface);
|
||||
if let Some(wid) = wid {
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
Event::CursorLeft {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
motion: |_, idata, _, _, x, y| {
|
||||
if let Some(wid) = idata.mouse_focus {
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::CursorMoved {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
position: (x, y),
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid
|
||||
);
|
||||
}
|
||||
},
|
||||
button: |_, idata, _, _, _, button, state| {
|
||||
if let Some(wid) = idata.mouse_focus {
|
||||
let state = match state {
|
||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
||||
wl_pointer::ButtonState::Released => ElementState::Released
|
||||
};
|
||||
let button = match button {
|
||||
0x110 => MouseButton::Left,
|
||||
0x111 => MouseButton::Right,
|
||||
0x112 => MouseButton::Middle,
|
||||
// TODO figure out the translation ?
|
||||
_ => return
|
||||
};
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::MouseInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
state: state,
|
||||
button: button,
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid
|
||||
);
|
||||
}
|
||||
},
|
||||
axis: |_, idata, pointer, _, axis, value| {
|
||||
if let Some(wid) = idata.mouse_focus {
|
||||
if pointer.version() < 5 {
|
||||
let (mut x, mut y) = (0.0, 0.0);
|
||||
// old seat compatibility
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32
|
||||
}
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
delta: MouseScrollDelta::PixelDelta(x as f32, y as f32),
|
||||
phase: TouchPhase::Moved,
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid
|
||||
);
|
||||
} else {
|
||||
let (mut x, mut y) = idata.axis_buffer.unwrap_or((0.0, 0.0));
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32
|
||||
}
|
||||
idata.axis_buffer = Some((x,y));
|
||||
idata.axis_state = match idata.axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
frame: |_, idata, _| {
|
||||
let axis_buffer = idata.axis_buffer.take();
|
||||
let axis_discrete_buffer = idata.axis_discrete_buffer.take();
|
||||
if let Some(wid) = idata.mouse_focus {
|
||||
if let Some((x, y)) = axis_discrete_buffer {
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
delta: MouseScrollDelta::LineDelta(x as f32, y as f32),
|
||||
phase: idata.axis_state,
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid
|
||||
);
|
||||
} else if let Some((x, y)) = axis_buffer {
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
delta: MouseScrollDelta::PixelDelta(x as f32, y as f32),
|
||||
phase: idata.axis_state,
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
axis_source: |_, _, _, _| {},
|
||||
axis_stop: |_, idata, _, _, _| {
|
||||
idata.axis_state = TouchPhase::Ended;
|
||||
},
|
||||
axis_discrete: |_, idata, _, axis, discrete| {
|
||||
let (mut x, mut y) = idata.axis_discrete_buffer.unwrap_or((0,0));
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= discrete,
|
||||
wl_pointer::Axis::HorizontalScroll => x += discrete
|
||||
}
|
||||
idata.axis_discrete_buffer = Some((x,y));
|
||||
idata.axis_state = match idata.axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use {WindowEvent as Event, TouchPhase};
|
||||
|
||||
use super::{WindowId, DeviceId};
|
||||
use super::event_loop::EventsLoopSink;
|
||||
use super::window::WindowStore;
|
||||
|
||||
use wayland_client::StateToken;
|
||||
use wayland_client::protocol::wl_touch;
|
||||
|
||||
pub struct TouchIData {
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
windows_token: StateToken<WindowStore>,
|
||||
pending_ids: Vec<TouchPoint>,
|
||||
}
|
||||
|
||||
struct TouchPoint {
|
||||
wid: WindowId,
|
||||
location: (f64, f64),
|
||||
id: i32
|
||||
}
|
||||
|
||||
impl TouchIData {
|
||||
pub fn new(sink: &Arc<Mutex<EventsLoopSink>>, token: StateToken<WindowStore>)
|
||||
-> TouchIData
|
||||
{
|
||||
TouchIData {
|
||||
sink: sink.clone(),
|
||||
windows_token: token,
|
||||
pending_ids: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn touch_implementation() -> wl_touch::Implementation<TouchIData> {
|
||||
wl_touch::Implementation {
|
||||
down: |evqh, idata, _, _serial, _time, surface, touch_id, x, y| {
|
||||
let wid = evqh.state().get(&idata.windows_token).find_wid(surface);
|
||||
if let Some(wid) = wid {
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
Event::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Started,
|
||||
location: (x, y),
|
||||
id: touch_id as u64
|
||||
}),
|
||||
wid,
|
||||
);
|
||||
idata.pending_ids.push(TouchPoint {
|
||||
wid: wid,
|
||||
location: (x, y),
|
||||
id: touch_id
|
||||
});
|
||||
}
|
||||
},
|
||||
up: |_, idata, _, _serial, _time, touch_id| {
|
||||
let idx = idata.pending_ids.iter().position(|p| p.id == touch_id);
|
||||
if let Some(idx) = idx {
|
||||
let pt = idata.pending_ids.remove(idx);
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
Event::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Ended,
|
||||
location: pt.location,
|
||||
id: touch_id as u64
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
motion: |_, idata, _, _time, touch_id, x, y| {
|
||||
let pt = idata.pending_ids.iter_mut().find(|p| p.id == touch_id);
|
||||
if let Some(pt) = pt {
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
pt.location = (x, y);
|
||||
guard.send_event(
|
||||
Event::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Moved,
|
||||
location: (x, y),
|
||||
id: touch_id as u64
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
frame: |_, _, _| {},
|
||||
cancel: |_, idata, _| {
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
for pt in idata.pending_ids.drain(..) {
|
||||
guard.send_event(
|
||||
Event::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location: pt.location,
|
||||
id: pt.id as u64
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,364 +0,0 @@
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
|
||||
use wayland_client::protocol::{wl_display,wl_surface};
|
||||
use wayland_client::{Proxy, StateToken};
|
||||
|
||||
use {CreationError, MouseCursor, CursorState, WindowAttributes};
|
||||
use platform::MonitorId as PlatformMonitorId;
|
||||
use window::MonitorId as RootMonitorId;
|
||||
|
||||
use super::{EventsLoop, WindowId, make_wid, MonitorId};
|
||||
use super::wayland_window::{Frame, FrameImplementation, State as FrameState};
|
||||
use super::event_loop::StateContext;
|
||||
|
||||
pub struct Window {
|
||||
surface: wl_surface::WlSurface,
|
||||
frame: Arc<Mutex<Frame>>,
|
||||
monitors: Arc<Mutex<MonitorList>>,
|
||||
size: Arc<Mutex<(u32, u32)>>,
|
||||
kill_switch: (Arc<Mutex<bool>>, Arc<Mutex<bool>>),
|
||||
display: Arc<wl_display::WlDisplay>,
|
||||
need_frame_refresh: Arc<Mutex<bool>>
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(evlp: &EventsLoop, attributes: &WindowAttributes) -> Result<Window, CreationError>
|
||||
{
|
||||
let (width, height) = attributes.dimensions.unwrap_or((800,600));
|
||||
|
||||
// Create the decorated surface
|
||||
let size = Arc::new(Mutex::new((width, height)));
|
||||
let store_token = evlp.store.clone();
|
||||
let (surface, mut frame) = evlp.create_window(
|
||||
width, height, decorated_impl(),
|
||||
|surface| FrameIData {
|
||||
surface: surface.clone().unwrap(),
|
||||
store_token: store_token.clone()
|
||||
}
|
||||
);
|
||||
// Check for fullscreen requirements
|
||||
if let Some(RootMonitorId { inner: PlatformMonitorId::Wayland(ref monitor_id) }) = attributes.fullscreen {
|
||||
let info = monitor_id.info.lock().unwrap();
|
||||
frame.set_state(FrameState::Fullscreen(Some(&info.output)));
|
||||
} else if attributes.maximized {
|
||||
frame.set_state(FrameState::Maximized);
|
||||
}
|
||||
|
||||
// set decorations
|
||||
frame.set_decorate(attributes.decorations);
|
||||
|
||||
// min-max dimensions
|
||||
frame.set_min_size(attributes.min_dimensions.map(|(w, h)| (w as i32, h as i32)));
|
||||
frame.set_max_size(attributes.max_dimensions.map(|(w, h)| (w as i32, h as i32)));
|
||||
|
||||
// setup the monitor tracking
|
||||
let monitor_list = Arc::new(Mutex::new(MonitorList::default()));
|
||||
{
|
||||
let mut evq = evlp.evq.borrow_mut();
|
||||
let idata = (evlp.ctxt_token.clone(), monitor_list.clone());
|
||||
evq.register(&surface, surface_impl(), idata);
|
||||
}
|
||||
|
||||
let kill_switch = Arc::new(Mutex::new(false));
|
||||
let need_frame_refresh = Arc::new(Mutex::new(true));
|
||||
let frame = Arc::new(Mutex::new(frame));
|
||||
|
||||
{
|
||||
let mut evq = evlp.evq.borrow_mut();
|
||||
evq.state().get_mut(&store_token).windows.push(InternalWindow {
|
||||
closed: false,
|
||||
newsize: None,
|
||||
need_refresh: false,
|
||||
need_frame_refresh: need_frame_refresh.clone(),
|
||||
surface: surface.clone().unwrap(),
|
||||
kill_switch: kill_switch.clone(),
|
||||
frame: Arc::downgrade(&frame)
|
||||
});
|
||||
evq.sync_roundtrip().unwrap();
|
||||
}
|
||||
|
||||
Ok(Window {
|
||||
display: evlp.display.clone(),
|
||||
surface: surface,
|
||||
frame: frame,
|
||||
monitors: monitor_list,
|
||||
size: size,
|
||||
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
|
||||
need_frame_refresh: need_frame_refresh
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
make_wid(&self.surface)
|
||||
}
|
||||
|
||||
pub fn set_title(&self, title: &str) {
|
||||
self.frame.lock().unwrap().set_title(title.into());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
// Not possible with wayland
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
|
||||
// Not possible with wayland
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, _x: i32, _y: i32) {
|
||||
// Not possible with wayland
|
||||
}
|
||||
|
||||
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
|
||||
Some(self.size.lock().unwrap().clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
|
||||
let (w, h) = self.size.lock().unwrap().clone();
|
||||
let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
|
||||
Some((w as u32, h as u32))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
// NOTE: This will only resize the borders, the contents must be updated by the user
|
||||
pub fn set_inner_size(&self, x: u32, y: u32) {
|
||||
self.frame.lock().unwrap().resize(x as i32, y as i32);
|
||||
*(self.size.lock().unwrap()) = (x, y);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
self.frame.lock().unwrap().set_min_size(dimensions.map(|(w, h)| (w as i32, h as i32)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
self.frame.lock().unwrap().set_max_size(dimensions.map(|(w, h)| (w as i32, h as i32)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, _cursor: MouseCursor) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
|
||||
use CursorState::{Grab, Normal, Hide};
|
||||
// TODO : not yet possible on wayland to grab cursor
|
||||
match state {
|
||||
Grab => Err("Cursor cannot be grabbed on wayland yet.".to_string()),
|
||||
Hide => Err("Cursor cannot be hidden on wayland yet.".to_string()),
|
||||
Normal => Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f32 {
|
||||
let mut factor = 1.0;
|
||||
let guard = self.monitors.lock().unwrap();
|
||||
for monitor_id in &guard.monitors {
|
||||
let info = monitor_id.info.lock().unwrap();
|
||||
if info.scale > factor { factor = info.scale; }
|
||||
}
|
||||
factor
|
||||
}
|
||||
|
||||
pub fn set_decorations(&self, decorate: bool) {
|
||||
self.frame.lock().unwrap().set_decorate(decorate);
|
||||
*(self.need_frame_refresh.lock().unwrap()) = true;
|
||||
}
|
||||
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
if maximized {
|
||||
self.frame.lock().unwrap().set_state(FrameState::Maximized);
|
||||
} else {
|
||||
self.frame.lock().unwrap().set_state(FrameState::Regular);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
||||
if let Some(RootMonitorId { inner: PlatformMonitorId::Wayland(ref monitor_id) }) = monitor {
|
||||
let info = monitor_id.info.lock().unwrap();
|
||||
self.frame.lock().unwrap().set_state(FrameState::Fullscreen(Some(&info.output)));
|
||||
} else {
|
||||
self.frame.lock().unwrap().set_state(FrameState::Regular);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
|
||||
// TODO: not yet possible on wayland
|
||||
Err(())
|
||||
}
|
||||
|
||||
pub fn get_display(&self) -> &wl_display::WlDisplay {
|
||||
&*self.display
|
||||
}
|
||||
|
||||
pub fn get_surface(&self) -> &wl_surface::WlSurface {
|
||||
&self.surface
|
||||
}
|
||||
|
||||
pub fn get_current_monitor(&self) -> MonitorId {
|
||||
// we don't know how much each monitor sees us so...
|
||||
// just return the most recent one ?
|
||||
let guard = self.monitors.lock().unwrap();
|
||||
guard.monitors.last().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
*(self.kill_switch.0.lock().unwrap()) = true;
|
||||
*(self.kill_switch.1.lock().unwrap()) = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal store for windows
|
||||
*/
|
||||
|
||||
struct InternalWindow {
|
||||
surface: wl_surface::WlSurface,
|
||||
newsize: Option<(i32, i32)>,
|
||||
need_refresh: bool,
|
||||
need_frame_refresh: Arc<Mutex<bool>>,
|
||||
closed: bool,
|
||||
kill_switch: Arc<Mutex<bool>>,
|
||||
frame: Weak<Mutex<Frame>>
|
||||
}
|
||||
|
||||
pub struct WindowStore {
|
||||
windows: Vec<InternalWindow>
|
||||
}
|
||||
|
||||
impl WindowStore {
|
||||
pub fn new() -> WindowStore {
|
||||
WindowStore { windows: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn find_wid(&self, surface: &wl_surface::WlSurface) -> Option<WindowId> {
|
||||
for window in &self.windows {
|
||||
if surface.equals(&window.surface) {
|
||||
return Some(make_wid(surface));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn cleanup(&mut self) -> Vec<WindowId> {
|
||||
let mut pruned = Vec::new();
|
||||
self.windows.retain(|w| {
|
||||
if *w.kill_switch.lock().unwrap() {
|
||||
// window is dead, cleanup
|
||||
pruned.push(make_wid(&w.surface));
|
||||
w.surface.destroy();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
pruned
|
||||
}
|
||||
|
||||
pub fn for_each<F>(&mut self, mut f: F)
|
||||
where F: FnMut(Option<(i32, i32)>, bool, bool, bool, WindowId, Option<&mut Frame>)
|
||||
{
|
||||
for window in &mut self.windows {
|
||||
let opt_arc = window.frame.upgrade();
|
||||
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
|
||||
f(
|
||||
window.newsize.take(),
|
||||
window.need_refresh,
|
||||
::std::mem::replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
|
||||
window.closed,
|
||||
make_wid(&window.surface),
|
||||
opt_mutex_lock.as_mut().map(|m| &mut **m)
|
||||
);
|
||||
window.need_refresh = false;
|
||||
// avoid re-spamming the event
|
||||
window.closed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Protocol implementation
|
||||
*/
|
||||
|
||||
struct FrameIData {
|
||||
store_token: StateToken<WindowStore>,
|
||||
surface: wl_surface::WlSurface
|
||||
}
|
||||
|
||||
fn decorated_impl() -> FrameImplementation<FrameIData> {
|
||||
FrameImplementation {
|
||||
configure: |evqh, idata, _, newsize| {
|
||||
let store = evqh.state().get_mut(&idata.store_token);
|
||||
for window in &mut store.windows {
|
||||
if window.surface.equals(&idata.surface) {
|
||||
window.newsize = newsize;
|
||||
window.need_refresh = true;
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
close: |evqh, idata| {
|
||||
let store = evqh.state().get_mut(&idata.store_token);
|
||||
for window in &mut store.windows {
|
||||
if window.surface.equals(&idata.surface) {
|
||||
window.closed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
refresh: |evqh, idata| {
|
||||
let store = evqh.state().get_mut(&idata.store_token);
|
||||
for window in &mut store.windows {
|
||||
if window.surface.equals(&idata.surface) {
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MonitorList {
|
||||
monitors: Vec<MonitorId>
|
||||
}
|
||||
|
||||
fn surface_impl() -> wl_surface::Implementation<(StateToken<StateContext>, Arc<Mutex<MonitorList>>)> {
|
||||
wl_surface::Implementation {
|
||||
enter: |evqh, &mut (ref token, ref list), _, output| {
|
||||
let mut guard = list.lock().unwrap();
|
||||
let ctxt = evqh.state().get(token);
|
||||
let monitor = ctxt.monitor_id_for(output);
|
||||
guard.monitors.push(monitor);
|
||||
},
|
||||
leave: |evqh, &mut (ref token, ref list), _, output| {
|
||||
let mut guard = list.lock().unwrap();
|
||||
let ctxt = evqh.state().get(token);
|
||||
let monitor = ctxt.monitor_id_for(output);
|
||||
guard.monitors.retain(|m| !Arc::ptr_eq(&m.info, &monitor.info));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
pub use x11_dl::keysym::*;
|
||||
pub use x11_dl::xcursor::*;
|
||||
pub use x11_dl::xlib::*;
|
||||
pub use x11_dl::xinput::*;
|
||||
pub use x11_dl::xinput2::*;
|
||||
pub use x11_dl::xlib_xcb::*;
|
||||
pub use x11_dl::error::OpenError;
|
||||
pub use x11_dl::xrandr::*;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,129 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
use std::slice;
|
||||
|
||||
use super::XConnection;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId {
|
||||
/// The actual id
|
||||
id: u32,
|
||||
/// The name of the monitor
|
||||
name: String,
|
||||
/// The size of the monitor
|
||||
dimensions: (u32, u32),
|
||||
/// The position of the monitor in the X screen
|
||||
position: (i32, i32),
|
||||
/// If the monitor is the primary one
|
||||
primary: bool,
|
||||
/// The DPI scaling factor
|
||||
hidpi_factor: f32,
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(x: &Arc<XConnection>) -> Vec<MonitorId> {
|
||||
let mut available = Vec::new();
|
||||
unsafe {
|
||||
let root = (x.xlib.XDefaultRootWindow)(x.display);
|
||||
let resources = (x.xrandr.XRRGetScreenResources)(x.display, root);
|
||||
|
||||
if let Some(ref xrandr_1_5) = x.xrandr_1_5 {
|
||||
// We're in XRandR >= 1.5, enumerate Monitors to handle things like MST and videowalls
|
||||
let mut nmonitors = 0;
|
||||
let monitors = (xrandr_1_5.XRRGetMonitors)(x.display, root, 1, &mut nmonitors);
|
||||
for i in 0..nmonitors {
|
||||
let monitor = *(monitors.offset(i as isize));
|
||||
let output = (xrandr_1_5.XRRGetOutputInfo)(x.display, resources, *(monitor.outputs.offset(0)));
|
||||
let nameslice = slice::from_raw_parts((*output).name as *mut u8, (*output).nameLen as usize);
|
||||
let name = String::from_utf8_lossy(nameslice).into_owned();
|
||||
let hidpi_factor = {
|
||||
let x_mm = (*output).mm_width as f32;
|
||||
let y_mm = (*output).mm_height as f32;
|
||||
let x_px = monitor.width as f32;
|
||||
let y_px = monitor.height as f32;
|
||||
let ppmm = ((x_px * y_px) / (x_mm * y_mm)).sqrt();
|
||||
|
||||
// Quantize 1/12 step size
|
||||
((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0)
|
||||
};
|
||||
(xrandr_1_5.XRRFreeOutputInfo)(output);
|
||||
available.push(MonitorId{
|
||||
id: i as u32,
|
||||
name,
|
||||
hidpi_factor,
|
||||
dimensions: (monitor.width as u32, monitor.height as u32),
|
||||
position: (monitor.x as i32, monitor.y as i32),
|
||||
primary: (monitor.primary != 0),
|
||||
});
|
||||
}
|
||||
(xrandr_1_5.XRRFreeMonitors)(monitors);
|
||||
} else {
|
||||
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work but MST and
|
||||
// videowall setups will show more monitors than the logical groups the user
|
||||
// cares about
|
||||
for i in 0..(*resources).ncrtc {
|
||||
let crtcid = *((*resources).crtcs.offset(i as isize));
|
||||
let crtc = (x.xrandr.XRRGetCrtcInfo)(x.display, resources, crtcid);
|
||||
if (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0 {
|
||||
let output = (x.xrandr.XRRGetOutputInfo)(x.display, resources, *((*crtc).outputs.offset(0)));
|
||||
let nameslice = slice::from_raw_parts((*output).name as *mut u8, (*output).nameLen as usize);
|
||||
let name = String::from_utf8_lossy(nameslice).into_owned();
|
||||
|
||||
let hidpi_factor = {
|
||||
let x_mm = (*output).mm_width as f32;
|
||||
let y_mm = (*output).mm_height as f32;
|
||||
let x_px = (*crtc).width as f32;
|
||||
let y_px = (*crtc).height as f32;
|
||||
let ppmm = ((x_px * y_px) / (x_mm * y_mm)).sqrt();
|
||||
|
||||
// Quantize 1/12 step size
|
||||
((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0)
|
||||
};
|
||||
|
||||
(x.xrandr.XRRFreeOutputInfo)(output);
|
||||
available.push(MonitorId{
|
||||
id: crtcid as u32,
|
||||
name,
|
||||
hidpi_factor,
|
||||
dimensions: ((*crtc).width as u32, (*crtc).height as u32),
|
||||
position: ((*crtc).x as i32, (*crtc).y as i32),
|
||||
primary: true,
|
||||
});
|
||||
}
|
||||
(x.xrandr.XRRFreeCrtcInfo)(crtc);
|
||||
}
|
||||
}
|
||||
(x.xrandr.XRRFreeScreenResources)(resources);
|
||||
}
|
||||
available
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(x: &Arc<XConnection>) -> MonitorId {
|
||||
get_available_monitors(x).into_iter().find(|m| m.primary)
|
||||
// 'no primary' case is better handled picking some existing monitor
|
||||
.or_else(|| get_available_monitors(x).into_iter().next())
|
||||
.expect("[winit] Failed to find any x11 monitor")
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some(self.name.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> u32 {
|
||||
self.id as u32
|
||||
}
|
||||
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
self.dimensions
|
||||
}
|
||||
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
self.position
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
self.hidpi_factor
|
||||
}
|
||||
}
|
||||
@@ -1,365 +0,0 @@
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::raw::{c_char, c_double, c_int, c_long, c_short, c_uchar, c_uint, c_ulong};
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
use events::ModifiersState;
|
||||
|
||||
pub struct XSmartPointer<'a, T> {
|
||||
xconn: &'a Arc<XConnection>,
|
||||
pub ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<'a, T> XSmartPointer<'a, T> {
|
||||
// You're responsible for only passing things to this that should be XFree'd.
|
||||
// Returns None if ptr is null.
|
||||
pub fn new(xconn: &'a Arc<XConnection>, ptr: *mut T) -> Option<Self> {
|
||||
if !ptr.is_null() {
|
||||
Some(XSmartPointer {
|
||||
xconn,
|
||||
ptr,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for XSmartPointer<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for XSmartPointer<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for XSmartPointer<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFree)(self.ptr as *mut _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_atom(xconn: &Arc<XConnection>, name: &[u8]) -> Result<ffi::Atom, XError> {
|
||||
let atom_name: *const c_char = name.as_ptr() as _;
|
||||
let atom = (xconn.xlib.XInternAtom)(xconn.display, atom_name, ffi::False);
|
||||
xconn.check_errors().map(|_| atom)
|
||||
}
|
||||
|
||||
pub unsafe fn send_client_msg(
|
||||
xconn: &Arc<XConnection>,
|
||||
window: c_ulong, // the window this is "about"; not necessarily this window
|
||||
target_window: c_ulong, // the window we're sending to
|
||||
message_type: ffi::Atom,
|
||||
event_mask: Option<c_long>,
|
||||
data: (c_long, c_long, c_long, c_long, c_long),
|
||||
) -> Result<(), XError> {
|
||||
let mut event: ffi::XClientMessageEvent = mem::uninitialized();
|
||||
event.type_ = ffi::ClientMessage;
|
||||
event.display = xconn.display;
|
||||
event.window = window;
|
||||
event.message_type = message_type;
|
||||
event.format = 32;
|
||||
event.data = ffi::ClientMessageData::new();
|
||||
event.data.set_long(0, data.0);
|
||||
event.data.set_long(1, data.1);
|
||||
event.data.set_long(2, data.2);
|
||||
event.data.set_long(3, data.3);
|
||||
event.data.set_long(4, data.4);
|
||||
|
||||
let event_mask = event_mask.unwrap_or(ffi::NoEventMask);
|
||||
|
||||
(xconn.xlib.XSendEvent)(
|
||||
xconn.display,
|
||||
target_window,
|
||||
ffi::False,
|
||||
event_mask,
|
||||
&mut event.into(),
|
||||
);
|
||||
|
||||
xconn.check_errors().map(|_| ())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum GetPropertyError {
|
||||
XError(XError),
|
||||
TypeMismatch(ffi::Atom),
|
||||
FormatMismatch(c_int),
|
||||
NothingAllocated,
|
||||
}
|
||||
|
||||
impl GetPropertyError {
|
||||
pub fn is_actual_property_type(&self, t: ffi::Atom) -> bool {
|
||||
if let GetPropertyError::TypeMismatch(actual_type) = *self {
|
||||
actual_type == t
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_property<T>(
|
||||
xconn: &Arc<XConnection>,
|
||||
window: c_ulong,
|
||||
property: ffi::Atom,
|
||||
property_type: ffi::Atom,
|
||||
) -> Result<Vec<T>, GetPropertyError> {
|
||||
let mut data = Vec::new();
|
||||
|
||||
let mut done = false;
|
||||
while !done {
|
||||
let mut actual_type: ffi::Atom = mem::uninitialized();
|
||||
let mut actual_format: c_int = mem::uninitialized();
|
||||
let mut byte_count: c_ulong = mem::uninitialized();
|
||||
let mut bytes_after: c_ulong = mem::uninitialized();
|
||||
let mut buf: *mut c_uchar = ptr::null_mut();
|
||||
(xconn.xlib.XGetWindowProperty)(
|
||||
xconn.display,
|
||||
window,
|
||||
property,
|
||||
(data.len() / 4) as c_long,
|
||||
1024,
|
||||
ffi::False,
|
||||
property_type,
|
||||
&mut actual_type,
|
||||
&mut actual_format,
|
||||
&mut byte_count,
|
||||
&mut bytes_after,
|
||||
&mut buf,
|
||||
);
|
||||
|
||||
if let Err(e) = xconn.check_errors() {
|
||||
return Err(GetPropertyError::XError(e));
|
||||
}
|
||||
|
||||
if actual_type != property_type {
|
||||
return Err(GetPropertyError::TypeMismatch(actual_type));
|
||||
}
|
||||
|
||||
// Fun fact: actual_format ISN'T the size of the type; it's more like a really bad enum
|
||||
let format_mismatch = match actual_format as usize {
|
||||
8 => mem::size_of::<T>() != mem::size_of::<c_char>(),
|
||||
16 => mem::size_of::<T>() != mem::size_of::<c_short>(),
|
||||
32 => mem::size_of::<T>() != mem::size_of::<c_long>(),
|
||||
_ => true, // this won't actually be reached; the XError condition above is triggered
|
||||
};
|
||||
|
||||
if format_mismatch {
|
||||
return Err(GetPropertyError::FormatMismatch(actual_format));
|
||||
}
|
||||
|
||||
if !buf.is_null() {
|
||||
let mut buf =
|
||||
Vec::from_raw_parts(buf as *mut T, byte_count as usize, byte_count as usize);
|
||||
data.append(&mut buf);
|
||||
} else {
|
||||
return Err(GetPropertyError::NothingAllocated);
|
||||
}
|
||||
|
||||
done = bytes_after == 0;
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
impl From<ffi::XIModifierState> for ModifiersState {
|
||||
fn from(mods: ffi::XIModifierState) -> Self {
|
||||
let state = mods.effective as c_uint;
|
||||
ModifiersState {
|
||||
alt: state & ffi::Mod1Mask != 0,
|
||||
shift: state & ffi::ShiftMask != 0,
|
||||
ctrl: state & ffi::ControlMask != 0,
|
||||
logo: state & ffi::Mod4Mask != 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PointerState {
|
||||
#[allow(dead_code)]
|
||||
root: ffi::Window,
|
||||
#[allow(dead_code)]
|
||||
child: ffi::Window,
|
||||
#[allow(dead_code)]
|
||||
root_x: c_double,
|
||||
#[allow(dead_code)]
|
||||
root_y: c_double,
|
||||
#[allow(dead_code)]
|
||||
win_x: c_double,
|
||||
#[allow(dead_code)]
|
||||
win_y: c_double,
|
||||
#[allow(dead_code)]
|
||||
buttons: ffi::XIButtonState,
|
||||
modifiers: ffi::XIModifierState,
|
||||
#[allow(dead_code)]
|
||||
group: ffi::XIGroupState,
|
||||
#[allow(dead_code)]
|
||||
relative_to_window: bool,
|
||||
}
|
||||
|
||||
impl PointerState {
|
||||
pub fn get_modifier_state(&self) -> ModifiersState {
|
||||
self.modifiers.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn query_pointer(
|
||||
xconn: &Arc<XConnection>,
|
||||
window: ffi::Window,
|
||||
device_id: c_int,
|
||||
) -> Result<PointerState, XError> {
|
||||
let mut root_return = mem::uninitialized();
|
||||
let mut child_return = mem::uninitialized();
|
||||
let mut root_x_return = mem::uninitialized();
|
||||
let mut root_y_return = mem::uninitialized();
|
||||
let mut win_x_return = mem::uninitialized();
|
||||
let mut win_y_return = mem::uninitialized();
|
||||
let mut buttons_return = mem::uninitialized();
|
||||
let mut modifiers_return = mem::uninitialized();
|
||||
let mut group_return = mem::uninitialized();
|
||||
|
||||
let relative_to_window = (xconn.xinput2.XIQueryPointer)(
|
||||
xconn.display,
|
||||
device_id,
|
||||
window,
|
||||
&mut root_return,
|
||||
&mut child_return,
|
||||
&mut root_x_return,
|
||||
&mut root_y_return,
|
||||
&mut win_x_return,
|
||||
&mut win_y_return,
|
||||
&mut buttons_return,
|
||||
&mut modifiers_return,
|
||||
&mut group_return,
|
||||
) == ffi::True;
|
||||
|
||||
xconn.check_errors()?;
|
||||
|
||||
Ok(PointerState {
|
||||
root: root_return,
|
||||
child: child_return,
|
||||
root_x: root_x_return,
|
||||
root_y: root_y_return,
|
||||
win_x: win_x_return,
|
||||
win_y: win_y_return,
|
||||
buttons: buttons_return,
|
||||
modifiers: modifiers_return,
|
||||
group: group_return,
|
||||
relative_to_window,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn lookup_utf8_inner(
|
||||
xconn: &Arc<XConnection>,
|
||||
ic: ffi::XIC,
|
||||
key_event: &mut ffi::XKeyEvent,
|
||||
buffer: &mut [u8],
|
||||
) -> (ffi::KeySym, ffi::Status, c_int) {
|
||||
let mut keysym: ffi::KeySym = 0;
|
||||
let mut status: ffi::Status = 0;
|
||||
let count = (xconn.xlib.Xutf8LookupString)(
|
||||
ic,
|
||||
key_event,
|
||||
buffer.as_mut_ptr() as *mut c_char,
|
||||
buffer.len() as c_int,
|
||||
&mut keysym,
|
||||
&mut status,
|
||||
);
|
||||
(keysym, status, count)
|
||||
}
|
||||
|
||||
pub unsafe fn lookup_utf8(
|
||||
xconn: &Arc<XConnection>,
|
||||
ic: ffi::XIC,
|
||||
key_event: &mut ffi::XKeyEvent,
|
||||
) -> String {
|
||||
const INIT_BUFF_SIZE: usize = 16;
|
||||
|
||||
// Buffer allocated on heap instead of stack, due to the possible reallocation
|
||||
let mut buffer: Vec<u8> = vec![mem::uninitialized(); INIT_BUFF_SIZE];
|
||||
let (_, status, mut count) = lookup_utf8_inner(
|
||||
xconn,
|
||||
ic,
|
||||
key_event,
|
||||
&mut buffer,
|
||||
);
|
||||
|
||||
// Buffer overflowed, dynamically reallocate
|
||||
if status == ffi::XBufferOverflow {
|
||||
buffer = vec![mem::uninitialized(); count as usize];
|
||||
let (_, _, new_count) = lookup_utf8_inner(
|
||||
xconn,
|
||||
ic,
|
||||
key_event,
|
||||
&mut buffer,
|
||||
);
|
||||
count = new_count;
|
||||
}
|
||||
|
||||
str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FrameExtents {
|
||||
pub left: c_ulong,
|
||||
pub right: c_ulong,
|
||||
pub top: c_ulong,
|
||||
pub bottom: c_ulong,
|
||||
}
|
||||
|
||||
impl FrameExtents {
|
||||
pub fn new(left: c_ulong, right: c_ulong, top: c_ulong, bottom: c_ulong) -> Self {
|
||||
FrameExtents { left, right, top, bottom }
|
||||
}
|
||||
|
||||
pub fn from_border(border: c_ulong) -> Self {
|
||||
Self::new(border, border, border, border)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WindowGeometry {
|
||||
pub x: c_int,
|
||||
pub y: c_int,
|
||||
pub width: c_uint,
|
||||
pub height: c_uint,
|
||||
pub frame: FrameExtents,
|
||||
}
|
||||
|
||||
impl WindowGeometry {
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
(self.x as _, self.y as _)
|
||||
}
|
||||
|
||||
pub fn get_inner_position(&self) -> (i32, i32) {
|
||||
(
|
||||
self.x.saturating_add(self.frame.left as c_int) as _,
|
||||
self.y.saturating_add(self.frame.top as c_int) as _,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_inner_size(&self) -> (u32, u32) {
|
||||
(self.width as _, self.height as _)
|
||||
}
|
||||
|
||||
pub fn get_outer_size(&self) -> (u32, u32) {
|
||||
(
|
||||
self.width.saturating_add(
|
||||
self.frame.left.saturating_add(self.frame.right) as c_uint
|
||||
) as _,
|
||||
self.height.saturating_add(
|
||||
self.frame.top.saturating_add(self.frame.bottom) as c_uint
|
||||
) as _,
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
226
src/platform/macos.rs
Normal file
226
src/platform/macos.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
#![cfg(target_os = "macos")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event_loop::EventLoopWindowTarget,
|
||||
monitor::MonitorHandle,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Corresponds to `NSRequestUserAttentionType`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum RequestUserAttentionType {
|
||||
/// Corresponds to `NSCriticalRequest`.
|
||||
///
|
||||
/// Dock icon will bounce until the application is focused.
|
||||
Critical,
|
||||
|
||||
/// Corresponds to `NSInformationalRequest`.
|
||||
///
|
||||
/// Dock icon will bounce once.
|
||||
Informational,
|
||||
}
|
||||
|
||||
impl Default for RequestUserAttentionType {
|
||||
fn default() -> Self {
|
||||
RequestUserAttentionType::Critical
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to MacOS.
|
||||
pub trait WindowExtMacOS {
|
||||
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
fn ns_window(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the cocoa `NSView` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
fn ns_view(&self) -> *mut c_void;
|
||||
|
||||
/// Request user attention, causing the application's dock icon to bounce.
|
||||
/// Note that this has no effect if the application is already focused.
|
||||
fn request_user_attention(&self, request_type: RequestUserAttentionType);
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
impl WindowExtMacOS for Window {
|
||||
#[inline]
|
||||
fn ns_window(&self) -> *mut c_void {
|
||||
self.window.ns_window()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ns_view(&self) -> *mut c_void {
|
||||
self.window.ns_view()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn request_user_attention(&self, request_type: RequestUserAttentionType) {
|
||||
self.window.request_user_attention(request_type)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn simple_fullscreen(&self) -> bool {
|
||||
self.window.simple_fullscreen()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
||||
self.window.set_simple_fullscreen(fullscreen)
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ActivationPolicy {
|
||||
/// Corresponds to `NSApplicationActivationPolicyRegular`.
|
||||
Regular,
|
||||
/// Corresponds to `NSApplicationActivationPolicyAccessory`.
|
||||
Accessory,
|
||||
/// Corresponds to `NSApplicationActivationPolicyProhibited`.
|
||||
Prohibited,
|
||||
}
|
||||
|
||||
impl Default for ActivationPolicy {
|
||||
fn default() -> Self {
|
||||
ActivationPolicy::Regular
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to MacOS.
|
||||
///
|
||||
/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method
|
||||
/// on the base `WindowBuilder`:
|
||||
///
|
||||
/// - `with_titlebar_transparent`
|
||||
/// - `with_title_hidden`
|
||||
/// - `with_titlebar_hidden`
|
||||
/// - `with_titlebar_buttons_hidden`
|
||||
/// - `with_fullsize_content_view`
|
||||
pub trait WindowBuilderExtMacOS {
|
||||
/// Sets the activation policy for the window being built.
|
||||
fn with_activation_policy(self, activation_policy: ActivationPolicy) -> WindowBuilder;
|
||||
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
|
||||
fn with_movable_by_window_background(self, movable_by_window_background: bool)
|
||||
-> WindowBuilder;
|
||||
/// Makes the titlebar transparent and allows the content to appear behind it.
|
||||
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder;
|
||||
/// Hides the window title.
|
||||
fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder;
|
||||
/// Hides the window titlebar.
|
||||
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder;
|
||||
/// Hides the window titlebar buttons.
|
||||
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
|
||||
/// Makes the window content appear behind the titlebar.
|
||||
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
|
||||
/// Build window with `resizeIncrements` property. Values must not be 0.
|
||||
fn with_resize_increments(self, increments: LogicalSize<f64>) -> WindowBuilder;
|
||||
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder {
|
||||
self.platform_specific.activation_policy = activation_policy;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_movable_by_window_background(
|
||||
mut self,
|
||||
movable_by_window_background: bool,
|
||||
) -> WindowBuilder {
|
||||
self.platform_specific.movable_by_window_background = movable_by_window_background;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder {
|
||||
self.platform_specific.titlebar_transparent = titlebar_transparent;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.titlebar_hidden = titlebar_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.title_hidden = title_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder {
|
||||
self.platform_specific.fullsize_content_view = fullsize_content_view;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_resize_increments(mut self, increments: LogicalSize<f64>) -> WindowBuilder {
|
||||
self.platform_specific.resize_increments = Some(increments.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
|
||||
self.platform_specific.disallow_hidpi = disallow_hidpi;
|
||||
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> {
|
||||
self.inner.ns_screen().map(|s| s as *mut c_void)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to macOS.
|
||||
pub trait EventLoopWindowTargetExtMacOS {
|
||||
/// Hide the entire application. In most applications this is typically triggered with Command-H.
|
||||
fn hide_application(&self);
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
||||
fn hide_application(&self) {
|
||||
let cls = objc::runtime::Class::get("NSApplication").unwrap();
|
||||
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
|
||||
unsafe { msg_send![app, hide: 0] }
|
||||
}
|
||||
}
|
||||
@@ -1,774 +0,0 @@
|
||||
use {ControlFlow, EventsLoopClosed};
|
||||
use cocoa::{self, appkit, foundation};
|
||||
use cocoa::appkit::{NSApplication, NSEvent, NSEventMask, NSEventModifierFlags, NSEventPhase, NSView, NSWindow};
|
||||
use events::{self, ElementState, Event, MouseButton, TouchPhase, WindowEvent, DeviceEvent, ModifiersState, KeyboardInput};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use super::window::Window2;
|
||||
use std;
|
||||
use super::DeviceId;
|
||||
|
||||
|
||||
pub struct EventsLoop {
|
||||
modifiers: Modifiers,
|
||||
pub shared: Arc<Shared>,
|
||||
}
|
||||
|
||||
// State shared between the `EventsLoop` and its registered windows.
|
||||
pub struct Shared {
|
||||
pub windows: Mutex<Vec<Weak<Window2>>>,
|
||||
pub pending_events: Mutex<VecDeque<Event>>,
|
||||
// The user event callback given via either of the `poll_events` or `run_forever` methods.
|
||||
//
|
||||
// We store the user's callback here so that it may be accessed by each of the window delegate
|
||||
// callbacks (e.g. resize, close, etc) for the duration of a call to either of the
|
||||
// `poll_events` or `run_forever` methods.
|
||||
//
|
||||
// This is *only* `Some` for the duration of a call to either of these methods and will be
|
||||
// `None` otherwise.
|
||||
user_callback: UserCallback,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Proxy {}
|
||||
|
||||
struct Modifiers {
|
||||
shift_pressed: bool,
|
||||
ctrl_pressed: bool,
|
||||
win_pressed: bool,
|
||||
alt_pressed: bool,
|
||||
}
|
||||
|
||||
// Wrapping the user callback in a type allows us to:
|
||||
//
|
||||
// - ensure the callback pointer is never accidentally cloned
|
||||
// - ensure that only the `EventsLoop` can `store` and `drop` the callback pointer
|
||||
// - Share access to the user callback with the NSWindow callbacks.
|
||||
pub struct UserCallback {
|
||||
mutex: Mutex<Option<*mut FnMut(Event)>>,
|
||||
}
|
||||
|
||||
|
||||
impl Shared {
|
||||
|
||||
pub fn new() -> Self {
|
||||
Shared {
|
||||
windows: Mutex::new(Vec::new()),
|
||||
pending_events: Mutex::new(VecDeque::new()),
|
||||
user_callback: UserCallback { mutex: Mutex::new(None) },
|
||||
}
|
||||
}
|
||||
|
||||
fn call_user_callback_with_pending_events(&self) {
|
||||
loop {
|
||||
let event = match self.pending_events.lock().unwrap().pop_front() {
|
||||
Some(event) => event,
|
||||
None => return,
|
||||
};
|
||||
unsafe {
|
||||
self.user_callback.call_with_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calls the user callback if one exists.
|
||||
//
|
||||
// Otherwise, stores the event in the `pending_events` queue.
|
||||
//
|
||||
// This is necessary for the case when `WindowDelegate` callbacks are triggered during a call
|
||||
// to the user's callback.
|
||||
pub fn call_user_callback_with_event_or_store_in_pending(&self, event: Event) {
|
||||
if self.user_callback.mutex.lock().unwrap().is_some() {
|
||||
unsafe {
|
||||
self.user_callback.call_with_event(event);
|
||||
}
|
||||
} else {
|
||||
self.pending_events.lock().unwrap().push_back(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Removes the window with the given `Id` from the `windows` list.
|
||||
//
|
||||
// This is called in response to `windowWillClose`.
|
||||
pub fn find_and_remove_window(&self, id: super::window::Id) {
|
||||
if let Ok(mut windows) = self.windows.lock() {
|
||||
windows.retain(|w| match w.upgrade() {
|
||||
Some(w) => w.id() != id,
|
||||
None => true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
impl Modifiers {
|
||||
pub fn new() -> Self {
|
||||
Modifiers {
|
||||
shift_pressed: false,
|
||||
ctrl_pressed: false,
|
||||
win_pressed: false,
|
||||
alt_pressed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl UserCallback {
|
||||
|
||||
// Here we store user's `callback` behind the mutex so that they may be safely shared between
|
||||
// each of the window delegates.
|
||||
//
|
||||
// In order to make sure that the pointer is always valid, we must manually guarantee that it
|
||||
// is dropped before the callback itself is dropped. Thus, this should *only* be called at the
|
||||
// beginning of a call to `poll_events` and `run_forever`, both of which *must* drop the
|
||||
// callback at the end of their scope using the `drop` method.
|
||||
fn store<F>(&self, callback: &mut F)
|
||||
where F: FnMut(Event)
|
||||
{
|
||||
let trait_object = callback as &mut FnMut(Event);
|
||||
let trait_object_ptr = trait_object as *const FnMut(Event) as *mut FnMut(Event);
|
||||
*self.mutex.lock().unwrap() = Some(trait_object_ptr);
|
||||
}
|
||||
|
||||
// Emits the given event via the user-given callback.
|
||||
//
|
||||
// This is unsafe as it requires dereferencing the pointer to the user-given callback. We
|
||||
// guarantee this is safe by ensuring the `UserCallback` never lives longer than the user-given
|
||||
// callback.
|
||||
//
|
||||
// Note that the callback may not always be `Some`. This is because some `NSWindowDelegate`
|
||||
// callbacks can be triggered by means other than `NSApp().sendEvent`. For example, if a window
|
||||
// is destroyed or created during a call to the user's callback, the `WindowDelegate` methods
|
||||
// may be called with `windowShouldClose` or `windowDidResignKey`.
|
||||
unsafe fn call_with_event(&self, event: Event) {
|
||||
let callback = match self.mutex.lock().unwrap().take() {
|
||||
Some(callback) => callback,
|
||||
None => return,
|
||||
};
|
||||
(*callback)(event);
|
||||
*self.mutex.lock().unwrap() = Some(callback);
|
||||
}
|
||||
|
||||
// Used to drop the user callback pointer at the end of the `poll_events` and `run_forever`
|
||||
// methods. This is done to enforce our guarantee that the top callback will never live longer
|
||||
// than the call to either `poll_events` or `run_forever` to which it was given.
|
||||
fn drop(&self) {
|
||||
self.mutex.lock().unwrap().take();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
impl EventsLoop {
|
||||
|
||||
pub fn new() -> Self {
|
||||
// Mark this thread as the main thread of the Cocoa event system.
|
||||
//
|
||||
// This must be done before any worker threads get a chance to call it
|
||||
// (e.g., via `EventsLoopProxy::wakeup()`), causing a wrong thread to be
|
||||
// marked as the main thread.
|
||||
unsafe { appkit::NSApp(); }
|
||||
|
||||
EventsLoop {
|
||||
shared: Arc::new(Shared::new()),
|
||||
modifiers: Modifiers::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(Event),
|
||||
{
|
||||
unsafe {
|
||||
if !msg_send![cocoa::base::class("NSThread"), isMainThread] {
|
||||
panic!("Events can only be polled from the main thread on macOS");
|
||||
}
|
||||
}
|
||||
|
||||
self.shared.user_callback.store(&mut callback);
|
||||
|
||||
// Loop as long as we have pending events to return.
|
||||
loop {
|
||||
unsafe {
|
||||
// First, yield all pending events.
|
||||
self.shared.call_user_callback_with_pending_events();
|
||||
|
||||
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
|
||||
|
||||
// Poll for the next event, returning `nil` if there are none.
|
||||
let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_(
|
||||
NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(),
|
||||
foundation::NSDate::distantPast(cocoa::base::nil),
|
||||
foundation::NSDefaultRunLoopMode,
|
||||
cocoa::base::YES);
|
||||
|
||||
let event = self.ns_event_to_event(ns_event);
|
||||
|
||||
let _: () = msg_send![pool, release];
|
||||
|
||||
match event {
|
||||
// Call the user's callback.
|
||||
Some(event) => self.shared.user_callback.call_with_event(event),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.shared.user_callback.drop();
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(Event) -> ControlFlow
|
||||
{
|
||||
unsafe {
|
||||
if !msg_send![cocoa::base::class("NSThread"), isMainThread] {
|
||||
panic!("Events can only be polled from the main thread on macOS");
|
||||
}
|
||||
}
|
||||
|
||||
// Track whether or not control flow has changed.
|
||||
let control_flow = std::cell::Cell::new(ControlFlow::Continue);
|
||||
|
||||
let mut callback = |event| {
|
||||
if let ControlFlow::Break = callback(event) {
|
||||
control_flow.set(ControlFlow::Break);
|
||||
}
|
||||
};
|
||||
|
||||
self.shared.user_callback.store(&mut callback);
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
// First, yield all pending events.
|
||||
self.shared.call_user_callback_with_pending_events();
|
||||
if let ControlFlow::Break = control_flow.get() {
|
||||
break;
|
||||
}
|
||||
|
||||
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
|
||||
|
||||
// Wait for the next event. Note that this function blocks during resize.
|
||||
let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_(
|
||||
NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(),
|
||||
foundation::NSDate::distantFuture(cocoa::base::nil),
|
||||
foundation::NSDefaultRunLoopMode,
|
||||
cocoa::base::YES);
|
||||
|
||||
let maybe_event = self.ns_event_to_event(ns_event);
|
||||
|
||||
// Release the pool before calling the top callback in case the user calls either
|
||||
// `run_forever` or `poll_events` within the callback.
|
||||
let _: () = msg_send![pool, release];
|
||||
|
||||
if let Some(event) = maybe_event {
|
||||
self.shared.user_callback.call_with_event(event);
|
||||
if let ControlFlow::Break = control_flow.get() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.shared.user_callback.drop();
|
||||
}
|
||||
|
||||
// Convert some given `NSEvent` into a winit `Event`.
|
||||
unsafe fn ns_event_to_event(&mut self, ns_event: cocoa::base::id) -> Option<Event> {
|
||||
if ns_event == cocoa::base::nil {
|
||||
return None;
|
||||
}
|
||||
|
||||
// FIXME: Despite not being documented anywhere, an `NSEvent` is produced when a user opens
|
||||
// Spotlight while the NSApplication is in focus. This `NSEvent` produces a `NSEventType`
|
||||
// with value `21`. This causes a SEGFAULT as soon as we try to match on the `NSEventType`
|
||||
// enum as there is no variant associated with the value. Thus, we return early if this
|
||||
// sneaky event occurs. If someone does find some documentation on this, please fix this by
|
||||
// adding an appropriate variant to the `NSEventType` enum in the cocoa-rs crate.
|
||||
if ns_event.eventType() as u64 == 21 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let event_type = ns_event.eventType();
|
||||
let ns_window = ns_event.window();
|
||||
let window_id = super::window::get_window_id(ns_window);
|
||||
|
||||
// FIXME: Document this. Why do we do this? Seems like it passes on events to window/app.
|
||||
// If we don't do this, window does not become main for some reason.
|
||||
match event_type {
|
||||
appkit::NSKeyDown => (),
|
||||
_ => appkit::NSApp().sendEvent_(ns_event),
|
||||
}
|
||||
|
||||
let windows = self.shared.windows.lock().unwrap();
|
||||
let maybe_window = windows.iter()
|
||||
.filter_map(Weak::upgrade)
|
||||
.find(|window| window_id == window.id());
|
||||
|
||||
let into_event = |window_event| Event::WindowEvent {
|
||||
window_id: ::WindowId(window_id),
|
||||
event: window_event,
|
||||
};
|
||||
|
||||
// Returns `Some` window if one of our windows is the key window.
|
||||
let maybe_key_window = || windows.iter()
|
||||
.filter_map(Weak::upgrade)
|
||||
.find(|window| {
|
||||
let is_key_window: cocoa::base::BOOL = msg_send![*window.window, isKeyWindow];
|
||||
is_key_window == cocoa::base::YES
|
||||
});
|
||||
|
||||
match event_type {
|
||||
|
||||
appkit::NSKeyDown => {
|
||||
let mut events = std::collections::VecDeque::new();
|
||||
let received_c_str = foundation::NSString::UTF8String(ns_event.characters());
|
||||
let received_str = std::ffi::CStr::from_ptr(received_c_str);
|
||||
|
||||
let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event));
|
||||
let state = ElementState::Pressed;
|
||||
let code = NSEvent::keyCode(ns_event) as u32;
|
||||
let window_event = WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: code,
|
||||
virtual_keycode: vkey,
|
||||
modifiers: event_mods(ns_event),
|
||||
},
|
||||
};
|
||||
for received_char in std::str::from_utf8(received_str.to_bytes()).unwrap().chars() {
|
||||
let window_event = WindowEvent::ReceivedCharacter(received_char);
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
self.shared.pending_events.lock().unwrap().extend(events.into_iter());
|
||||
Some(into_event(window_event))
|
||||
},
|
||||
|
||||
appkit::NSKeyUp => {
|
||||
let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event));
|
||||
|
||||
let state = ElementState::Released;
|
||||
let code = NSEvent::keyCode(ns_event) as u32;
|
||||
let window_event = WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: code,
|
||||
virtual_keycode: vkey,
|
||||
modifiers: event_mods(ns_event),
|
||||
},
|
||||
};
|
||||
Some(into_event(window_event))
|
||||
},
|
||||
|
||||
appkit::NSFlagsChanged => {
|
||||
unsafe fn modifier_event(event: cocoa::base::id,
|
||||
keymask: NSEventModifierFlags,
|
||||
key: events::VirtualKeyCode,
|
||||
key_pressed: bool) -> Option<WindowEvent>
|
||||
{
|
||||
if !key_pressed && NSEvent::modifierFlags(event).contains(keymask) {
|
||||
let state = ElementState::Pressed;
|
||||
let code = NSEvent::keyCode(event) as u32;
|
||||
let window_event = WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: code,
|
||||
virtual_keycode: Some(key),
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
};
|
||||
Some(window_event)
|
||||
|
||||
} else if key_pressed && !NSEvent::modifierFlags(event).contains(keymask) {
|
||||
let state = ElementState::Released;
|
||||
let code = NSEvent::keyCode(event) as u32;
|
||||
let window_event = WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: code,
|
||||
virtual_keycode: Some(key),
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
};
|
||||
Some(window_event)
|
||||
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
let mut events = std::collections::VecDeque::new();
|
||||
if let Some(window_event) = modifier_event(ns_event,
|
||||
NSEventModifierFlags::NSShiftKeyMask,
|
||||
events::VirtualKeyCode::LShift,
|
||||
self.modifiers.shift_pressed)
|
||||
{
|
||||
self.modifiers.shift_pressed = !self.modifiers.shift_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
if let Some(window_event) = modifier_event(ns_event,
|
||||
NSEventModifierFlags::NSControlKeyMask,
|
||||
events::VirtualKeyCode::LControl,
|
||||
self.modifiers.ctrl_pressed)
|
||||
{
|
||||
self.modifiers.ctrl_pressed = !self.modifiers.ctrl_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
if let Some(window_event) = modifier_event(ns_event,
|
||||
NSEventModifierFlags::NSCommandKeyMask,
|
||||
events::VirtualKeyCode::LWin,
|
||||
self.modifiers.win_pressed)
|
||||
{
|
||||
self.modifiers.win_pressed = !self.modifiers.win_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
if let Some(window_event) = modifier_event(ns_event,
|
||||
NSEventModifierFlags::NSAlternateKeyMask,
|
||||
events::VirtualKeyCode::LAlt,
|
||||
self.modifiers.alt_pressed)
|
||||
{
|
||||
self.modifiers.alt_pressed = !self.modifiers.alt_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
let event = events.pop_front();
|
||||
self.shared.pending_events.lock().unwrap().extend(events.into_iter());
|
||||
event
|
||||
},
|
||||
|
||||
appkit::NSLeftMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Left, modifiers: event_mods(ns_event) })) },
|
||||
appkit::NSLeftMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Left, modifiers: event_mods(ns_event) })) },
|
||||
appkit::NSRightMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Right, modifiers: event_mods(ns_event) })) },
|
||||
appkit::NSRightMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Right, modifiers: event_mods(ns_event) })) },
|
||||
appkit::NSOtherMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Middle, modifiers: event_mods(ns_event) })) },
|
||||
appkit::NSOtherMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Middle, modifiers: event_mods(ns_event) })) },
|
||||
|
||||
appkit::NSMouseEntered => {
|
||||
let window = match maybe_window.or_else(maybe_key_window) {
|
||||
Some(window) => window,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let window_point = ns_event.locationInWindow();
|
||||
let view_point = if ns_window == cocoa::base::nil {
|
||||
let ns_size = foundation::NSSize::new(0.0, 0.0);
|
||||
let ns_rect = foundation::NSRect::new(window_point, ns_size);
|
||||
let window_rect = window.window.convertRectFromScreen_(ns_rect);
|
||||
window.view.convertPoint_fromView_(window_rect.origin, cocoa::base::nil)
|
||||
} else {
|
||||
window.view.convertPoint_fromView_(window_point, cocoa::base::nil)
|
||||
};
|
||||
let view_rect = NSView::frame(*window.view);
|
||||
let scale_factor = window.hidpi_factor();
|
||||
|
||||
let x = (scale_factor * view_point.x as f32) as f64;
|
||||
let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64;
|
||||
let window_event = WindowEvent::CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event_mods(ns_event) };
|
||||
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
|
||||
|
||||
self.shared.pending_events.lock().unwrap().push_back(event);
|
||||
Some(into_event(WindowEvent::CursorEntered { device_id: DEVICE_ID }))
|
||||
},
|
||||
appkit::NSMouseExited => { Some(into_event(WindowEvent::CursorLeft { device_id: DEVICE_ID })) },
|
||||
|
||||
appkit::NSMouseMoved |
|
||||
appkit::NSLeftMouseDragged |
|
||||
appkit::NSOtherMouseDragged |
|
||||
appkit::NSRightMouseDragged => {
|
||||
// If the mouse movement was on one of our windows, use it.
|
||||
// Otherwise, if one of our windows is the key window (receiving input), use it.
|
||||
// Otherwise, return `None`.
|
||||
let window = match maybe_window.or_else(maybe_key_window) {
|
||||
Some(window) => window,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let window_point = ns_event.locationInWindow();
|
||||
let view_point = if ns_window == cocoa::base::nil {
|
||||
let ns_size = foundation::NSSize::new(0.0, 0.0);
|
||||
let ns_rect = foundation::NSRect::new(window_point, ns_size);
|
||||
let window_rect = window.window.convertRectFromScreen_(ns_rect);
|
||||
window.view.convertPoint_fromView_(window_rect.origin, cocoa::base::nil)
|
||||
} else {
|
||||
window.view.convertPoint_fromView_(window_point, cocoa::base::nil)
|
||||
};
|
||||
let view_rect = NSView::frame(*window.view);
|
||||
let scale_factor = window.hidpi_factor();
|
||||
|
||||
let mut events = std::collections::VecDeque::new();
|
||||
|
||||
{
|
||||
let x = (scale_factor * view_point.x as f32) as f64;
|
||||
let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64;
|
||||
let window_event = WindowEvent::CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event_mods(ns_event) };
|
||||
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
let delta_x = (scale_factor * ns_event.deltaX() as f32) as f64;
|
||||
if delta_x != 0.0 {
|
||||
let motion_event = DeviceEvent::Motion { axis: 0, value: delta_x };
|
||||
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
let delta_y = (scale_factor * ns_event.deltaY() as f32) as f64;
|
||||
if delta_y != 0.0 {
|
||||
let motion_event = DeviceEvent::Motion { axis: 1, value: delta_y };
|
||||
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
if delta_x != 0.0 || delta_y != 0.0 {
|
||||
let motion_event = DeviceEvent::MouseMotion { delta: (delta_x, delta_y) };
|
||||
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
let event = events.pop_front();
|
||||
self.shared.pending_events.lock().unwrap().extend(events.into_iter());
|
||||
event
|
||||
},
|
||||
|
||||
appkit::NSScrollWheel => {
|
||||
// If none of the windows received the scroll, return `None`.
|
||||
let window = match maybe_window {
|
||||
Some(window) => window,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
use events::MouseScrollDelta::{LineDelta, PixelDelta};
|
||||
let scale_factor = window.hidpi_factor();
|
||||
let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
|
||||
PixelDelta(scale_factor * ns_event.scrollingDeltaX() as f32,
|
||||
scale_factor * ns_event.scrollingDeltaY() as f32)
|
||||
} else {
|
||||
LineDelta(scale_factor * ns_event.scrollingDeltaX() as f32,
|
||||
scale_factor * ns_event.scrollingDeltaY() as f32)
|
||||
};
|
||||
let phase = match ns_event.phase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
|
||||
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
|
||||
_ => TouchPhase::Moved,
|
||||
};
|
||||
self.shared.pending_events.lock().unwrap().push_back(Event::DeviceEvent {
|
||||
device_id: DEVICE_ID,
|
||||
event: DeviceEvent::MouseWheel {
|
||||
delta: if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
|
||||
PixelDelta(ns_event.scrollingDeltaX() as f32,
|
||||
ns_event.scrollingDeltaY() as f32)
|
||||
} else {
|
||||
LineDelta(ns_event.scrollingDeltaX() as f32,
|
||||
ns_event.scrollingDeltaY() as f32)
|
||||
},
|
||||
}
|
||||
});
|
||||
let window_event = WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: delta, phase: phase, modifiers: event_mods(ns_event) };
|
||||
Some(into_event(window_event))
|
||||
},
|
||||
|
||||
appkit::NSEventTypePressure => {
|
||||
let pressure = ns_event.pressure();
|
||||
let stage = ns_event.stage();
|
||||
let window_event = WindowEvent::TouchpadPressure { device_id: DEVICE_ID, pressure: pressure, stage: stage };
|
||||
Some(into_event(window_event))
|
||||
},
|
||||
|
||||
appkit::NSApplicationDefined => match ns_event.subtype() {
|
||||
appkit::NSEventSubtype::NSApplicationActivatedEventType => {
|
||||
Some(Event::Awakened)
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> Proxy {
|
||||
Proxy {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Proxy {
|
||||
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
|
||||
// Awaken the event loop by triggering `NSApplicationActivatedEventType`.
|
||||
unsafe {
|
||||
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
|
||||
let event =
|
||||
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
|
||||
cocoa::base::nil,
|
||||
appkit::NSApplicationDefined,
|
||||
foundation::NSPoint::new(0.0, 0.0),
|
||||
appkit::NSEventModifierFlags::empty(),
|
||||
0.0,
|
||||
0,
|
||||
cocoa::base::nil,
|
||||
appkit::NSEventSubtype::NSApplicationActivatedEventType,
|
||||
0,
|
||||
0);
|
||||
appkit::NSApp().postEvent_atStart_(event, cocoa::base::NO);
|
||||
foundation::NSAutoreleasePool::drain(pool);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
|
||||
Some(match code {
|
||||
0x00 => events::VirtualKeyCode::A,
|
||||
0x01 => events::VirtualKeyCode::S,
|
||||
0x02 => events::VirtualKeyCode::D,
|
||||
0x03 => events::VirtualKeyCode::F,
|
||||
0x04 => events::VirtualKeyCode::H,
|
||||
0x05 => events::VirtualKeyCode::G,
|
||||
0x06 => events::VirtualKeyCode::Z,
|
||||
0x07 => events::VirtualKeyCode::X,
|
||||
0x08 => events::VirtualKeyCode::C,
|
||||
0x09 => events::VirtualKeyCode::V,
|
||||
//0x0a => World 1,
|
||||
0x0b => events::VirtualKeyCode::B,
|
||||
0x0c => events::VirtualKeyCode::Q,
|
||||
0x0d => events::VirtualKeyCode::W,
|
||||
0x0e => events::VirtualKeyCode::E,
|
||||
0x0f => events::VirtualKeyCode::R,
|
||||
0x10 => events::VirtualKeyCode::Y,
|
||||
0x11 => events::VirtualKeyCode::T,
|
||||
0x12 => events::VirtualKeyCode::Key1,
|
||||
0x13 => events::VirtualKeyCode::Key2,
|
||||
0x14 => events::VirtualKeyCode::Key3,
|
||||
0x15 => events::VirtualKeyCode::Key4,
|
||||
0x16 => events::VirtualKeyCode::Key6,
|
||||
0x17 => events::VirtualKeyCode::Key5,
|
||||
0x18 => events::VirtualKeyCode::Equals,
|
||||
0x19 => events::VirtualKeyCode::Key9,
|
||||
0x1a => events::VirtualKeyCode::Key7,
|
||||
0x1b => events::VirtualKeyCode::Minus,
|
||||
0x1c => events::VirtualKeyCode::Key8,
|
||||
0x1d => events::VirtualKeyCode::Key0,
|
||||
0x1e => events::VirtualKeyCode::RBracket,
|
||||
0x1f => events::VirtualKeyCode::O,
|
||||
0x20 => events::VirtualKeyCode::U,
|
||||
0x21 => events::VirtualKeyCode::LBracket,
|
||||
0x22 => events::VirtualKeyCode::I,
|
||||
0x23 => events::VirtualKeyCode::P,
|
||||
0x24 => events::VirtualKeyCode::Return,
|
||||
0x25 => events::VirtualKeyCode::L,
|
||||
0x26 => events::VirtualKeyCode::J,
|
||||
0x27 => events::VirtualKeyCode::Apostrophe,
|
||||
0x28 => events::VirtualKeyCode::K,
|
||||
0x29 => events::VirtualKeyCode::Semicolon,
|
||||
0x2a => events::VirtualKeyCode::Backslash,
|
||||
0x2b => events::VirtualKeyCode::Comma,
|
||||
0x2c => events::VirtualKeyCode::Slash,
|
||||
0x2d => events::VirtualKeyCode::N,
|
||||
0x2e => events::VirtualKeyCode::M,
|
||||
0x2f => events::VirtualKeyCode::Period,
|
||||
0x30 => events::VirtualKeyCode::Tab,
|
||||
0x31 => events::VirtualKeyCode::Space,
|
||||
0x32 => events::VirtualKeyCode::Grave,
|
||||
0x33 => events::VirtualKeyCode::Back,
|
||||
//0x34 => unkown,
|
||||
0x35 => events::VirtualKeyCode::Escape,
|
||||
0x36 => events::VirtualKeyCode::RWin,
|
||||
0x37 => events::VirtualKeyCode::LWin,
|
||||
0x38 => events::VirtualKeyCode::LShift,
|
||||
//0x39 => Caps lock,
|
||||
//0x3a => Left alt,
|
||||
0x3b => events::VirtualKeyCode::LControl,
|
||||
0x3c => events::VirtualKeyCode::RShift,
|
||||
//0x3d => Right alt,
|
||||
0x3e => events::VirtualKeyCode::RControl,
|
||||
//0x3f => Fn key,
|
||||
//0x40 => F17 Key,
|
||||
0x41 => events::VirtualKeyCode::Decimal,
|
||||
//0x42 -> unkown,
|
||||
0x43 => events::VirtualKeyCode::Multiply,
|
||||
//0x44 => unkown,
|
||||
0x45 => events::VirtualKeyCode::Add,
|
||||
//0x46 => unkown,
|
||||
0x47 => events::VirtualKeyCode::Numlock,
|
||||
//0x48 => KeypadClear,
|
||||
0x49 => events::VirtualKeyCode::VolumeUp,
|
||||
0x4a => events::VirtualKeyCode::VolumeDown,
|
||||
0x4b => events::VirtualKeyCode::Divide,
|
||||
0x4c => events::VirtualKeyCode::NumpadEnter,
|
||||
//0x4d => unkown,
|
||||
0x4e => events::VirtualKeyCode::Subtract,
|
||||
//0x4f => F18 key,
|
||||
//0x50 => F19 Key,
|
||||
0x51 => events::VirtualKeyCode::NumpadEquals,
|
||||
0x52 => events::VirtualKeyCode::Numpad0,
|
||||
0x53 => events::VirtualKeyCode::Numpad1,
|
||||
0x54 => events::VirtualKeyCode::Numpad2,
|
||||
0x55 => events::VirtualKeyCode::Numpad3,
|
||||
0x56 => events::VirtualKeyCode::Numpad4,
|
||||
0x57 => events::VirtualKeyCode::Numpad5,
|
||||
0x58 => events::VirtualKeyCode::Numpad6,
|
||||
0x59 => events::VirtualKeyCode::Numpad7,
|
||||
//0x5a => F20 Key,
|
||||
0x5b => events::VirtualKeyCode::Numpad8,
|
||||
0x5c => events::VirtualKeyCode::Numpad9,
|
||||
//0x5d => unkown,
|
||||
//0x5e => unkown,
|
||||
//0x5f => unkown,
|
||||
0x60 => events::VirtualKeyCode::F5,
|
||||
0x61 => events::VirtualKeyCode::F6,
|
||||
0x62 => events::VirtualKeyCode::F7,
|
||||
0x63 => events::VirtualKeyCode::F3,
|
||||
0x64 => events::VirtualKeyCode::F8,
|
||||
0x65 => events::VirtualKeyCode::F9,
|
||||
//0x66 => unkown,
|
||||
0x67 => events::VirtualKeyCode::F11,
|
||||
//0x68 => unkown,
|
||||
0x69 => events::VirtualKeyCode::F13,
|
||||
//0x6a => F16 Key,
|
||||
0x6b => events::VirtualKeyCode::F14,
|
||||
//0x6c => unkown,
|
||||
0x6d => events::VirtualKeyCode::F10,
|
||||
//0x6e => unkown,
|
||||
0x6f => events::VirtualKeyCode::F12,
|
||||
//0x70 => unkown,
|
||||
0x71 => events::VirtualKeyCode::F15,
|
||||
0x72 => events::VirtualKeyCode::Insert,
|
||||
0x73 => events::VirtualKeyCode::Home,
|
||||
0x74 => events::VirtualKeyCode::PageUp,
|
||||
0x75 => events::VirtualKeyCode::Delete,
|
||||
0x76 => events::VirtualKeyCode::F4,
|
||||
0x77 => events::VirtualKeyCode::End,
|
||||
0x78 => events::VirtualKeyCode::F2,
|
||||
0x79 => events::VirtualKeyCode::PageDown,
|
||||
0x7a => events::VirtualKeyCode::F1,
|
||||
0x7b => events::VirtualKeyCode::Left,
|
||||
0x7c => events::VirtualKeyCode::Right,
|
||||
0x7d => events::VirtualKeyCode::Down,
|
||||
0x7e => events::VirtualKeyCode::Up,
|
||||
//0x7f => unkown,
|
||||
|
||||
0xa => events::VirtualKeyCode::Caret,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn event_mods(event: cocoa::base::id) -> ModifiersState {
|
||||
let flags = unsafe {
|
||||
NSEvent::modifierFlags(event)
|
||||
};
|
||||
ModifiersState {
|
||||
shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask),
|
||||
ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask),
|
||||
alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
|
||||
logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask),
|
||||
}
|
||||
}
|
||||
|
||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
||||
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
|
||||
@@ -1,42 +0,0 @@
|
||||
#![cfg(target_os = "macos")]
|
||||
|
||||
pub use self::events_loop::{EventsLoop, Proxy as EventsLoopProxy};
|
||||
pub use self::monitor::MonitorId;
|
||||
pub use self::window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, Window2};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
use {CreationError};
|
||||
|
||||
pub struct Window {
|
||||
pub window: Arc<Window2>,
|
||||
}
|
||||
|
||||
impl ::std::ops::Deref for Window {
|
||||
type Target = Window2;
|
||||
#[inline]
|
||||
fn deref(&self) -> &Window2 {
|
||||
&*self.window
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
|
||||
pub fn new(events_loop: &EventsLoop,
|
||||
attributes: &::WindowAttributes,
|
||||
pl_attribs: &PlatformSpecificWindowBuilderAttributes) -> Result<Self, CreationError>
|
||||
{
|
||||
let weak_shared = Arc::downgrade(&events_loop.shared);
|
||||
let window = Arc::new(try!(Window2::new(weak_shared, attributes, pl_attribs)));
|
||||
let weak_window = Arc::downgrade(&window);
|
||||
events_loop.shared.windows.lock().unwrap().push(weak_window);
|
||||
Ok(Window { window: window })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mod events_loop;
|
||||
mod monitor;
|
||||
mod window;
|
||||
@@ -1,94 +0,0 @@
|
||||
use cocoa::appkit::NSScreen;
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::foundation::{NSString, NSUInteger};
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplay};
|
||||
use std::collections::VecDeque;
|
||||
use super::EventsLoop;
|
||||
use super::window::IdRef;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct MonitorId(CGDirectDisplayID);
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut monitors = VecDeque::new();
|
||||
if let Ok(displays) = CGDisplay::active_displays() {
|
||||
for d in displays {
|
||||
monitors.push_back(MonitorId(d));
|
||||
}
|
||||
}
|
||||
monitors
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
let id = MonitorId(CGDisplay::main().id);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn make_monitor_from_display(id: CGDirectDisplayID) -> MonitorId {
|
||||
let id = MonitorId(id);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
let MonitorId(display_id) = *self;
|
||||
let screen_num = CGDisplay::new(display_id).model_number();
|
||||
Some(format!("Monitor #{}", screen_num))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
let MonitorId(display_id) = *self;
|
||||
let display = CGDisplay::new(display_id);
|
||||
let dimension = {
|
||||
let height = display.pixels_high();
|
||||
let width = display.pixels_wide();
|
||||
(width as u32, height as u32)
|
||||
};
|
||||
dimension
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
let screen = match self.get_nsscreen() {
|
||||
Some(screen) => screen,
|
||||
None => return 1.0, // default to 1.0 when we can't find the screen
|
||||
};
|
||||
|
||||
unsafe { NSScreen::backingScaleFactor(screen) as f32 }
|
||||
}
|
||||
|
||||
pub(crate) fn get_nsscreen(&self) -> Option<id> {
|
||||
unsafe {
|
||||
let native_id = self.get_native_identifier();
|
||||
let screens = NSScreen::screens(nil);
|
||||
let count: NSUInteger = msg_send![screens, count];
|
||||
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
||||
let mut matching_screen: Option<id> = None;
|
||||
for i in 0..count {
|
||||
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
|
||||
let device_description = NSScreen::deviceDescription(screen);
|
||||
let value: id = msg_send![device_description, objectForKey:*key];
|
||||
if value != nil {
|
||||
let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue];
|
||||
if screen_number as u32 == native_id {
|
||||
matching_screen = Some(screen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
matching_screen
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,25 @@
|
||||
pub use self::platform::*;
|
||||
//! Contains traits with platform-specific methods in them.
|
||||
//!
|
||||
//! Contains the follow OS-specific modules:
|
||||
//!
|
||||
//! - `android`
|
||||
//! - `ios`
|
||||
//! - `macos`
|
||||
//! - `unix`
|
||||
//! - `windows`
|
||||
//! - `web`
|
||||
//!
|
||||
//! And the following platform-specific module:
|
||||
//!
|
||||
//! - `desktop` (available on `windows`, `unix`, and `macos`)
|
||||
//!
|
||||
//! However only the module corresponding to the platform you're compiling to will be available.
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[path="windows/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
#[path="linux/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(target_os = "macos")]
|
||||
#[path="macos/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(target_os = "android")]
|
||||
#[path="android/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(target_os = "ios")]
|
||||
#[path="ios/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(target_os = "emscripten")]
|
||||
#[path="emscripten/mod.rs"]
|
||||
mod platform;
|
||||
pub mod android;
|
||||
pub mod ios;
|
||||
pub mod macos;
|
||||
pub mod unix;
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(all(not(target_os = "ios"), not(target_os = "windows"), not(target_os = "linux"),
|
||||
not(target_os = "macos"), not(target_os = "android"), not(target_os = "dragonfly"),
|
||||
not(target_os = "freebsd"), not(target_os = "openbsd"), not(target_os = "emscripten")))]
|
||||
compile_error!("The platform you're compiling for is not supported by winit");
|
||||
pub mod desktop;
|
||||
pub mod web;
|
||||
|
||||
489
src/platform/unix.rs
Normal file
489
src/platform/unix.rs
Normal file
@@ -0,0 +1,489 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
|
||||
use std::{os::raw, ptr, sync::Arc};
|
||||
|
||||
use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme};
|
||||
|
||||
use crate::{
|
||||
dpi::Size,
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
use crate::platform_impl::{
|
||||
x11::{ffi::XVisualInfo, XConnection},
|
||||
EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
|
||||
Window as LinuxWindow,
|
||||
};
|
||||
|
||||
// TODO: stupid hack so that glutin can do its work
|
||||
#[doc(hidden)]
|
||||
pub use crate::platform_impl::x11;
|
||||
|
||||
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
|
||||
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
|
||||
pub trait EventLoopWindowTargetExtUnix {
|
||||
/// True if the `EventLoopWindowTarget` uses Wayland.
|
||||
fn is_wayland(&self) -> bool;
|
||||
///
|
||||
/// True if the `EventLoopWindowTarget` uses X11.
|
||||
fn is_x11(&self) -> bool;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
|
||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this
|
||||
/// `EventLoopWindowTarget`.
|
||||
///
|
||||
/// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the winit `EventLoop` is destroyed.
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
|
||||
#[inline]
|
||||
fn is_wayland(&self) -> bool {
|
||||
self.p.is_wayland()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_x11(&self) -> bool {
|
||||
!self.p.is_wayland()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
match self.p {
|
||||
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.p {
|
||||
LinuxEventLoopWindowTarget::Wayland(ref p) => {
|
||||
Some(p.display().get_display_ptr() as *mut _)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Unix.
|
||||
pub trait EventLoopExtUnix {
|
||||
/// Builds a new `EventLoop` that is forced to use X11.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If called outside the main thread. To initialize an X11 event loop outside
|
||||
/// the main thread, use [`new_x11_any_thread`](#tymethod.new_x11_any_thread).
|
||||
fn new_x11() -> Result<Self, XNotSupported>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Builds a new `EventLoop` that is forced to use Wayland.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If called outside the main thread. To initialize a Wayland event loop outside
|
||||
/// the main thread, use [`new_wayland_any_thread`](#tymethod.new_wayland_any_thread).
|
||||
fn new_wayland() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Builds a new `EventLoop` on any thread.
|
||||
///
|
||||
/// This method bypasses the cross-platform compatibility requirement
|
||||
/// that `EventLoop` be created on the main thread.
|
||||
fn new_any_thread() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Builds a new X11 `EventLoop` on any thread.
|
||||
///
|
||||
/// This method bypasses the cross-platform compatibility requirement
|
||||
/// that `EventLoop` be created on the main thread.
|
||||
fn new_x11_any_thread() -> Result<Self, XNotSupported>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Builds a new Wayland `EventLoop` on any thread.
|
||||
///
|
||||
/// This method bypasses the cross-platform compatibility requirement
|
||||
/// that `EventLoop` be created on the main thread.
|
||||
fn new_wayland_any_thread() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
fn wrap_ev<T>(event_loop: LinuxEventLoop<T>) -> EventLoop<T> {
|
||||
EventLoop {
|
||||
event_loop,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtUnix for EventLoop<T> {
|
||||
#[inline]
|
||||
fn new_any_thread() -> Self {
|
||||
wrap_ev(LinuxEventLoop::new_any_thread())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_x11_any_thread() -> Result<Self, XNotSupported> {
|
||||
LinuxEventLoop::new_x11_any_thread().map(wrap_ev)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_wayland_any_thread() -> Self {
|
||||
wrap_ev(
|
||||
LinuxEventLoop::new_wayland_any_thread()
|
||||
// TODO: propagate
|
||||
.expect("failed to open Wayland connection"),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_x11() -> Result<Self, XNotSupported> {
|
||||
LinuxEventLoop::new_x11().map(wrap_ev)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_wayland() -> Self {
|
||||
wrap_ev(
|
||||
LinuxEventLoop::new_wayland()
|
||||
// TODO: propagate
|
||||
.expect("failed to open Wayland connection"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Unix.
|
||||
pub trait WindowExtUnix {
|
||||
/// Returns the ID of the `Window` xlib object that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
fn xlib_window(&self) -> Option<raw::c_ulong>;
|
||||
|
||||
/// Returns a pointer to the `Display` object of xlib that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn xlib_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
fn xlib_screen_id(&self) -> Option<raw::c_int>;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
|
||||
/// Set window urgency hint (`XUrgencyHint`). Only relevant on X.
|
||||
fn set_urgent(&self, is_urgent: bool);
|
||||
|
||||
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Sets the color theme of the client side window decorations on wayland
|
||||
fn set_wayland_theme<T: Theme>(&self, theme: T);
|
||||
|
||||
/// Check if the window is ready for drawing
|
||||
///
|
||||
/// It is a remnant of a previous implementation detail for the
|
||||
/// wayland backend, and is no longer relevant.
|
||||
///
|
||||
/// Always return true.
|
||||
#[deprecated]
|
||||
fn is_ready(&self) -> bool;
|
||||
}
|
||||
|
||||
impl WindowExtUnix for Window {
|
||||
#[inline]
|
||||
fn xlib_window(&self) -> Option<raw::c_ulong> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_window()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn xlib_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_display()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn xlib_screen_id(&self) -> Option<raw::c_int> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_xconnection()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_urgent(&self, is_urgent: bool) {
|
||||
if let LinuxWindow::X(ref w) = self.window {
|
||||
w.set_urgent(is_urgent);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_wayland_theme<T: Theme>(&self, theme: T) {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_ready(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Unix.
|
||||
pub trait WindowBuilderExtUnix {
|
||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
|
||||
fn with_x11_screen(self, screen_id: i32) -> Self;
|
||||
|
||||
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
|
||||
fn with_class(self, class: String, instance: String) -> Self;
|
||||
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
|
||||
fn with_override_redirect(self, override_redirect: bool) -> Self;
|
||||
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
|
||||
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
|
||||
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
|
||||
fn with_gtk_theme_variant(self, variant: String) -> Self;
|
||||
/// Build window with resize increment hint. Only implemented on X11.
|
||||
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
|
||||
/// Build window with base size hint. Only implemented on X11.
|
||||
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
|
||||
|
||||
/// Build window with a given application ID. It should match the `.desktop` file distributed with
|
||||
/// your program. Only relevant on Wayland.
|
||||
///
|
||||
/// 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_app_id(self, app_id: String) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtUnix for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
|
||||
self.platform_specific.visual_infos =
|
||||
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_x11_screen(mut self, screen_id: i32) -> Self {
|
||||
self.platform_specific.screen_id = Some(screen_id);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_class(mut self, instance: String, class: String) -> Self {
|
||||
self.platform_specific.class = Some((instance, class));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
|
||||
self.platform_specific.override_redirect = override_redirect;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
|
||||
self.platform_specific.x11_window_types = x11_window_types;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_gtk_theme_variant(mut self, variant: String) -> Self {
|
||||
self.platform_specific.gtk_theme_variant = Some(variant);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self {
|
||||
self.platform_specific.resize_increments = Some(increments.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
|
||||
self.platform_specific.base_size = Some(base_size.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_app_id(mut self, app_id: String) -> Self {
|
||||
self.platform_specific.app_id = Some(app_id);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorHandle` that are specific to Linux.
|
||||
pub trait MonitorHandleExtUnix {
|
||||
/// Returns the inner identifier of the monitor.
|
||||
fn native_id(&self) -> u32;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtUnix for MonitorHandle {
|
||||
#[inline]
|
||||
fn native_id(&self) -> u32 {
|
||||
self.inner.native_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for implementing SCTK's theme trait.
|
||||
struct WaylandTheme<T: Theme>(T);
|
||||
|
||||
pub trait Theme: Send + 'static {
|
||||
/// Primary color of the scheme.
|
||||
fn primary_color(&self, window_active: bool) -> [u8; 4];
|
||||
|
||||
/// Secondary color of the scheme.
|
||||
fn secondary_color(&self, window_active: bool) -> [u8; 4];
|
||||
|
||||
/// Color for the close button.
|
||||
fn close_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the close button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
}
|
||||
|
||||
/// Background color for the maximize button.
|
||||
fn maximize_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the maximize button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
}
|
||||
|
||||
/// Background color for the minimize button.
|
||||
fn minimize_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the minimize button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Theme> SCTKTheme for WaylandTheme<T> {
|
||||
fn get_primary_color(&self, active: bool) -> [u8; 4] {
|
||||
self.0.primary_color(active)
|
||||
}
|
||||
|
||||
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
|
||||
self.0.secondary_color(active)
|
||||
}
|
||||
|
||||
fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.close_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0
|
||||
.close_button_icon_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.maximize_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0
|
||||
.maximize_button_icon_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.minimize_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0
|
||||
.minimize_button_icon_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ButtonState {
|
||||
/// Button is being hovered over by pointer.
|
||||
Hovered,
|
||||
/// Button is not being hovered over by pointer.
|
||||
Idle,
|
||||
/// Button is disabled.
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl ButtonState {
|
||||
fn from_sctk(button_state: SCTKButtonState) -> Self {
|
||||
match button_state {
|
||||
SCTKButtonState::Hovered => Self::Hovered,
|
||||
SCTKButtonState::Idle => Self::Idle,
|
||||
SCTKButtonState::Disabled => Self::Disabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/platform/web.rs
Normal file
59
src/platform/web.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
//! The web target does not automatically insert the canvas element object into the web page, to
|
||||
//! allow end users to determine how the page should be laid out. Use the `WindowExtStdweb` or
|
||||
//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the
|
||||
//! Window. Alternatively, use the `WindowBuilderExtStdweb` or `WindowBuilderExtWebSys` to provide
|
||||
//! your own canvas.
|
||||
|
||||
use crate::window::WindowBuilder;
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
use stdweb::web::html_element::CanvasElement;
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
pub trait WindowExtStdweb {
|
||||
fn canvas(&self) -> CanvasElement;
|
||||
|
||||
/// Whether the browser reports the preferred color scheme to be "dark".
|
||||
fn is_dark_mode(&self) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
pub trait WindowExtWebSys {
|
||||
fn canvas(&self) -> HtmlCanvasElement;
|
||||
|
||||
/// Whether the browser reports the preferred color scheme to be "dark".
|
||||
fn is_dark_mode(&self) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
pub trait WindowBuilderExtStdweb {
|
||||
fn with_canvas(self, canvas: Option<CanvasElement>) -> Self;
|
||||
}
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
impl WindowBuilderExtStdweb for WindowBuilder {
|
||||
fn with_canvas(mut self, canvas: Option<CanvasElement>) -> Self {
|
||||
self.platform_specific.canvas = canvas;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
pub trait WindowBuilderExtWebSys {
|
||||
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
|
||||
}
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
impl WindowBuilderExtWebSys for WindowBuilder {
|
||||
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
|
||||
self.platform_specific.canvas = canvas;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
213
src/platform/windows.rs
Normal file
213
src/platform/windows.rs
Normal file
@@ -0,0 +1,213 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
use std::path::Path;
|
||||
|
||||
use libc;
|
||||
use winapi::shared::minwindef::WORD;
|
||||
use winapi::shared::windef::HWND;
|
||||
|
||||
use crate::{
|
||||
dpi::PhysicalSize,
|
||||
event::DeviceId,
|
||||
event_loop::EventLoop,
|
||||
monitor::MonitorHandle,
|
||||
platform_impl::{EventLoop as WindowsEventLoop, WinIcon},
|
||||
window::{BadIcon, Icon, Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Windows.
|
||||
pub trait EventLoopExtWindows {
|
||||
/// Creates an event loop off of the main thread.
|
||||
///
|
||||
/// # `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 new_any_thread() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's
|
||||
/// undesirable, you can create an `EventLoop` using this function instead.
|
||||
fn new_dpi_unaware() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Creates a DPI-unaware event loop off of the main thread.
|
||||
///
|
||||
/// The `Window` caveats in [`new_any_thread`](EventLoopExtWindows::new_any_thread) also apply here.
|
||||
fn new_dpi_unaware_any_thread() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtWindows for EventLoop<T> {
|
||||
#[inline]
|
||||
fn new_any_thread() -> Self {
|
||||
EventLoop {
|
||||
event_loop: WindowsEventLoop::new_any_thread(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_dpi_unaware() -> Self {
|
||||
EventLoop {
|
||||
event_loop: WindowsEventLoop::new_dpi_unaware(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_dpi_unaware_any_thread() -> Self {
|
||||
EventLoop {
|
||||
event_loop: WindowsEventLoop::new_dpi_unaware_any_thread(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Windows.
|
||||
pub trait WindowExtWindows {
|
||||
/// Returns the HINSTANCE of the window
|
||||
fn hinstance(&self) -> *mut libc::c_void;
|
||||
/// Returns the native handle that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the native window was destroyed.
|
||||
fn hwnd(&self) -> *mut libc::c_void;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
|
||||
|
||||
/// Whether the system theme is currently Windows 10's "Dark Mode".
|
||||
fn is_dark_mode(&self) -> bool;
|
||||
}
|
||||
|
||||
impl WindowExtWindows for Window {
|
||||
#[inline]
|
||||
fn hinstance(&self) -> *mut libc::c_void {
|
||||
self.window.hinstance() as *mut _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hwnd(&self) -> *mut libc::c_void {
|
||||
self.window.hwnd() as *mut _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
|
||||
self.window.set_taskbar_icon(taskbar_icon)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_dark_mode(&self) -> bool {
|
||||
self.window.is_dark_mode()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Windows.
|
||||
pub trait WindowBuilderExtWindows {
|
||||
/// Sets a parent to the window to be created.
|
||||
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
|
||||
|
||||
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
|
||||
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtWindows for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
|
||||
self.platform_specific.parent = Some(parent);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> WindowBuilder {
|
||||
self.platform_specific.taskbar_icon = taskbar_icon;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder {
|
||||
self.platform_specific.no_redirection_bitmap = flag;
|
||||
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) -> *mut c_void;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtWindows for MonitorHandle {
|
||||
#[inline]
|
||||
fn native_id(&self) -> String {
|
||||
self.inner.native_identifier()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hmonitor(&self) -> *mut c_void {
|
||||
self.inner.hmonitor() as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
/// 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: WORD, 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 = WinIcon::from_path(path, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
|
||||
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
|
||||
let win_icon = WinIcon::from_resource(ordinal, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
}
|
||||
@@ -1,949 +0,0 @@
|
||||
//! An events loop on Win32 is a background thread.
|
||||
//!
|
||||
//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop.
|
||||
//! Destroying the events loop stops the thread.
|
||||
//!
|
||||
//! You can use the `execute_in_thread` method to execute some code in the background thread.
|
||||
//! Since Win32 requires you to create a window in the right thread, you must use this method
|
||||
//! to create a window.
|
||||
//!
|
||||
//! If you create a window whose class is set to `callback`, the window's events will be
|
||||
//! propagated with `run_forever` and `poll_events`.
|
||||
//! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to
|
||||
//! add a `WindowState` entry to a list of window to be used by the callback.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::mem;
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use std::ptr;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Barrier;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::Condvar;
|
||||
use std::thread;
|
||||
|
||||
use winapi::shared::minwindef::{LOWORD, HIWORD, DWORD, WPARAM, LPARAM, INT, UINT, LRESULT, MAX_PATH};
|
||||
use winapi::shared::windef::{HWND, POINT, RECT};
|
||||
use winapi::shared::windowsx;
|
||||
use winapi::um::{winuser, shellapi, processthreadsapi};
|
||||
use winapi::um::winnt::LONG;
|
||||
|
||||
use platform::platform::event;
|
||||
use platform::platform::window::adjust_size;
|
||||
use platform::platform::Cursor;
|
||||
use platform::platform::WindowId;
|
||||
use platform::platform::DEVICE_ID;
|
||||
|
||||
use ControlFlow;
|
||||
use CursorState;
|
||||
use Event;
|
||||
use EventsLoopClosed;
|
||||
use KeyboardInput;
|
||||
use WindowAttributes;
|
||||
use WindowEvent;
|
||||
use WindowId as SuperWindowId;
|
||||
use events::{Touch, TouchPhase};
|
||||
|
||||
/// Contains saved window info for switching between fullscreen
|
||||
#[derive(Clone)]
|
||||
pub struct SavedWindowInfo {
|
||||
/// Window style
|
||||
pub style: LONG,
|
||||
/// Window ex-style
|
||||
pub ex_style: LONG,
|
||||
/// Window position and size
|
||||
pub rect: RECT,
|
||||
}
|
||||
|
||||
/// Contains information about states and the window that the callback is going to use.
|
||||
#[derive(Clone)]
|
||||
pub struct WindowState {
|
||||
/// Cursor to set at the next `WM_SETCURSOR` event received.
|
||||
pub cursor: Cursor,
|
||||
/// Cursor state to set at the next `WM_SETCURSOR` event received.
|
||||
pub cursor_state: CursorState,
|
||||
/// Used by `WM_GETMINMAXINFO`.
|
||||
pub attributes: WindowAttributes,
|
||||
/// Will contain `true` if the mouse is hovering the window.
|
||||
pub mouse_in_window: bool,
|
||||
/// Saved window info for fullscreen restored
|
||||
pub saved_window_info: Option<SavedWindowInfo>,
|
||||
}
|
||||
|
||||
/// Dummy object that allows inserting a window's state.
|
||||
// We store a pointer in order to !impl Send and Sync.
|
||||
pub struct Inserter(*mut u8);
|
||||
|
||||
impl Inserter {
|
||||
/// Inserts a window's state for the callback to use. The state is removed automatically if the
|
||||
/// callback receives a `WM_CLOSE` message for the window.
|
||||
pub fn insert(&self, window: HWND, state: Arc<Mutex<WindowState>>) {
|
||||
CONTEXT_STASH.with(|context_stash| {
|
||||
let mut context_stash = context_stash.borrow_mut();
|
||||
let was_in = context_stash.as_mut().unwrap().windows.insert(window, state);
|
||||
assert!(was_in.is_none());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventsLoop {
|
||||
// Id of the background thread from the Win32 API.
|
||||
thread_id: DWORD,
|
||||
// Receiver for the events. The sender is in the background thread.
|
||||
receiver: mpsc::Receiver<Event>,
|
||||
// Variable that contains the block state of the win32 event loop thread during a WM_SIZE event.
|
||||
// The mutex's value is `true` when it's blocked, and should be set to false when it's done
|
||||
// blocking. That's done by the parent thread when it receives a Resized event.
|
||||
win32_block_loop: Arc<(Mutex<bool>, Condvar)>
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn new() -> EventsLoop {
|
||||
// The main events transfer channel.
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let win32_block_loop = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
let win32_block_loop_child = win32_block_loop.clone();
|
||||
|
||||
// Local barrier in order to block the `new()` function until the background thread has
|
||||
// an events queue.
|
||||
let barrier = Arc::new(Barrier::new(2));
|
||||
let barrier_clone = barrier.clone();
|
||||
|
||||
let thread = thread::spawn(move || {
|
||||
CONTEXT_STASH.with(|context_stash| {
|
||||
*context_stash.borrow_mut() = Some(ThreadLocalData {
|
||||
sender: tx,
|
||||
windows: HashMap::with_capacity(4),
|
||||
win32_block_loop: win32_block_loop_child,
|
||||
mouse_buttons_down: 0
|
||||
});
|
||||
});
|
||||
|
||||
unsafe {
|
||||
// Calling `PostThreadMessageA` on a thread that does not have an events queue yet
|
||||
// will fail. In order to avoid this situation, we call `IsGuiThread` to initialize
|
||||
// it.
|
||||
winuser::IsGUIThread(1);
|
||||
// Then only we unblock the `new()` function. We are sure that we don't call
|
||||
// `PostThreadMessageA()` before `new()` returns.
|
||||
barrier_clone.wait();
|
||||
drop(barrier_clone);
|
||||
|
||||
let mut msg = mem::uninitialized();
|
||||
|
||||
loop {
|
||||
if winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 {
|
||||
// Only happens if the message is `WM_QUIT`.
|
||||
debug_assert_eq!(msg.message, winuser::WM_QUIT);
|
||||
break;
|
||||
}
|
||||
|
||||
match msg.message {
|
||||
x if x == *EXEC_MSG_ID => {
|
||||
let mut function: Box<Box<FnMut(Inserter)>> = Box::from_raw(msg.wParam as usize as *mut _);
|
||||
function(Inserter(ptr::null_mut()));
|
||||
},
|
||||
x if x == *WAKEUP_MSG_ID => {
|
||||
send_event(Event::Awakened);
|
||||
},
|
||||
_ => {
|
||||
// Calls `callback` below.
|
||||
winuser::TranslateMessage(&msg);
|
||||
winuser::DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Blocks this function until the background thread has an events loop. See other comments.
|
||||
barrier.wait();
|
||||
|
||||
let thread_id = unsafe {
|
||||
let handle = mem::transmute(thread.as_raw_handle());
|
||||
processthreadsapi::GetThreadId(handle)
|
||||
};
|
||||
|
||||
EventsLoop {
|
||||
thread_id,
|
||||
receiver: rx,
|
||||
win32_block_loop
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(Event)
|
||||
{
|
||||
loop {
|
||||
let event = match self.receiver.try_recv() {
|
||||
Ok(e) => e,
|
||||
Err(_) => return
|
||||
};
|
||||
let is_resize = match event {
|
||||
Event::WindowEvent{ event: WindowEvent::Resized(..), .. } => true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
callback(event);
|
||||
if is_resize {
|
||||
let (ref mutex, ref cvar) = *self.win32_block_loop;
|
||||
let mut block_thread = mutex.lock().unwrap();
|
||||
*block_thread = false;
|
||||
cvar.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(Event) -> ControlFlow
|
||||
{
|
||||
loop {
|
||||
let event = match self.receiver.recv() {
|
||||
Ok(e) => e,
|
||||
Err(_) => return
|
||||
};
|
||||
let is_resize = match event {
|
||||
Event::WindowEvent{ event: WindowEvent::Resized(..), .. } => true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
let flow = callback(event);
|
||||
if is_resize {
|
||||
let (ref mutex, ref cvar) = *self.win32_block_loop;
|
||||
let mut block_thread = mutex.lock().unwrap();
|
||||
*block_thread = false;
|
||||
cvar.notify_all();
|
||||
}
|
||||
match flow {
|
||||
ControlFlow::Continue => continue,
|
||||
ControlFlow::Break => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
EventsLoopProxy {
|
||||
thread_id: self.thread_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a function in the background thread.
|
||||
///
|
||||
/// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent
|
||||
/// to the unstable FnBox.
|
||||
///
|
||||
/// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is
|
||||
/// removed automatically if the callback receives a `WM_CLOSE` message for the window.
|
||||
pub(super) fn execute_in_thread<F>(&self, function: F)
|
||||
where F: FnMut(Inserter) + Send + 'static
|
||||
{
|
||||
self.create_proxy().execute_in_thread(function)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventsLoop {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// Posting `WM_QUIT` will cause `GetMessage` to stop.
|
||||
winuser::PostThreadMessageA(self.thread_id, winuser::WM_QUIT, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventsLoopProxy {
|
||||
thread_id: DWORD,
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
|
||||
unsafe {
|
||||
if winuser::PostThreadMessageA(self.thread_id, *WAKEUP_MSG_ID, 0, 0) != 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
// https://msdn.microsoft.com/fr-fr/library/windows/desktop/ms644946(v=vs.85).aspx
|
||||
// > If the function fails, the return value is zero. To get extended error
|
||||
// > information, call GetLastError. GetLastError returns ERROR_INVALID_THREAD_ID
|
||||
// > if idThread is not a valid thread identifier, or if the thread specified by
|
||||
// > idThread does not have a message queue. GetLastError returns
|
||||
// > ERROR_NOT_ENOUGH_QUOTA when the message limit is hit.
|
||||
// TODO: handle ERROR_NOT_ENOUGH_QUOTA
|
||||
Err(EventsLoopClosed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a function in the background thread.
|
||||
///
|
||||
/// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent
|
||||
/// to the unstable FnBox.
|
||||
///
|
||||
/// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is
|
||||
/// removed automatically if the callback receives a `WM_CLOSE` message for the window.
|
||||
pub fn execute_in_thread<F>(&self, function: F)
|
||||
where
|
||||
F: FnMut(Inserter) + Send + 'static,
|
||||
{
|
||||
unsafe {
|
||||
// We are using double-boxing here because it make casting back much easier
|
||||
let boxed = Box::new(function) as Box<FnMut(_)>;
|
||||
let boxed2 = Box::new(boxed);
|
||||
let raw = Box::into_raw(boxed2);
|
||||
|
||||
let res = winuser::PostThreadMessageA(
|
||||
self.thread_id,
|
||||
*EXEC_MSG_ID,
|
||||
raw as *mut () as usize as WPARAM,
|
||||
0,
|
||||
);
|
||||
// PostThreadMessage can only fail if the thread ID is invalid (which shouldn't happen
|
||||
// as the events loop is still alive) or if the queue is full.
|
||||
assert!(
|
||||
res != 0,
|
||||
"PostThreadMessage failed ; is the messages queue full?"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
// Message sent by the `EventsLoopProxy` when we want to wake up the thread.
|
||||
// WPARAM and LPARAM are unused.
|
||||
static ref WAKEUP_MSG_ID: u32 = {
|
||||
unsafe {
|
||||
winuser::RegisterWindowMessageA("Winit::WakeupMsg\0".as_ptr() as *const i8)
|
||||
}
|
||||
};
|
||||
// Message sent when we want to execute a closure in the thread.
|
||||
// WPARAM contains a Box<Box<FnMut()>> that must be retreived with `Box::from_raw`,
|
||||
// and LPARAM is unused.
|
||||
static ref EXEC_MSG_ID: u32 = {
|
||||
unsafe {
|
||||
winuser::RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr() as *const i8)
|
||||
}
|
||||
};
|
||||
// Message sent by a `Window` when it wants to be destroyed by the main thread.
|
||||
// WPARAM and LPARAM are unused.
|
||||
pub static ref DESTROY_MSG_ID: u32 = {
|
||||
unsafe {
|
||||
winuser::RegisterWindowMessageA("Winit::DestroyMsg\0".as_ptr() as *const i8)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// There's no parameters passed to the callback function, so it needs to get its context stashed
|
||||
// in a thread-local variable.
|
||||
thread_local!(static CONTEXT_STASH: RefCell<Option<ThreadLocalData>> = RefCell::new(None));
|
||||
struct ThreadLocalData {
|
||||
sender: mpsc::Sender<Event>,
|
||||
windows: HashMap<HWND, Arc<Mutex<WindowState>>>,
|
||||
win32_block_loop: Arc<(Mutex<bool>, Condvar)>,
|
||||
mouse_buttons_down: u32
|
||||
}
|
||||
|
||||
// Utility function that dispatches an event on the current thread.
|
||||
fn send_event(event: Event) {
|
||||
CONTEXT_STASH.with(|context_stash| {
|
||||
let context_stash = context_stash.borrow();
|
||||
|
||||
let _ = context_stash.as_ref().unwrap().sender.send(event); // Ignoring if closed
|
||||
});
|
||||
}
|
||||
|
||||
/// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of
|
||||
/// the window.
|
||||
unsafe fn capture_mouse(window: HWND) {
|
||||
CONTEXT_STASH.with(|context_stash| {
|
||||
let mut context_stash = context_stash.borrow_mut();
|
||||
if let Some(context_stash) = context_stash.as_mut() {
|
||||
context_stash.mouse_buttons_down += 1;
|
||||
winuser::SetCapture(window);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Release mouse input, stopping windows on this thread from receiving mouse input when the cursor
|
||||
/// is outside the window.
|
||||
unsafe fn release_mouse() {
|
||||
CONTEXT_STASH.with(|context_stash| {
|
||||
let mut context_stash = context_stash.borrow_mut();
|
||||
if let Some(context_stash) = context_stash.as_mut() {
|
||||
context_stash.mouse_buttons_down = context_stash.mouse_buttons_down.saturating_sub(1);
|
||||
if context_stash.mouse_buttons_down == 0 {
|
||||
winuser::ReleaseCapture();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Any window whose callback is configured to this function will have its events propagated
|
||||
/// through the events loop of the thread the window was created in.
|
||||
//
|
||||
// This is the callback that is called by `DispatchMessage` in the events loop.
|
||||
//
|
||||
// Returning 0 tells the Win32 API that the message has been processed.
|
||||
// FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary
|
||||
pub unsafe extern "system" fn callback(window: HWND, msg: UINT,
|
||||
wparam: WPARAM, lparam: LPARAM)
|
||||
-> LRESULT
|
||||
{
|
||||
match msg {
|
||||
winuser::WM_CLOSE => {
|
||||
use events::WindowEvent::CloseRequested;
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: CloseRequested
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_DESTROY => {
|
||||
use events::WindowEvent::Destroyed;
|
||||
CONTEXT_STASH.with(|context_stash| {
|
||||
let mut context_stash = context_stash.borrow_mut();
|
||||
context_stash.as_mut().unwrap().windows.remove(&window);
|
||||
});
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: Destroyed
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_PAINT => {
|
||||
use events::WindowEvent::Refresh;
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: Refresh,
|
||||
});
|
||||
winuser::DefWindowProcW(window, msg, wparam, lparam)
|
||||
},
|
||||
|
||||
winuser::WM_SIZE => {
|
||||
use events::WindowEvent::Resized;
|
||||
let w = LOWORD(lparam as DWORD) as u32;
|
||||
let h = HIWORD(lparam as DWORD) as u32;
|
||||
|
||||
// Wait for the parent thread to process the resize event before returning from the
|
||||
// callback.
|
||||
CONTEXT_STASH.with(|context_stash| {
|
||||
let mut context_stash = context_stash.borrow_mut();
|
||||
let cstash = context_stash.as_mut().unwrap();
|
||||
|
||||
let event = Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: Resized(w, h),
|
||||
};
|
||||
|
||||
// If this window has been inserted into the window map, the resize event happened
|
||||
// during the event loop. If it hasn't, the event happened on window creation and
|
||||
// should be ignored.
|
||||
if cstash.windows.get(&window).is_some() {
|
||||
let (ref mutex, ref cvar) = *cstash.win32_block_loop;
|
||||
let mut block_thread = mutex.lock().unwrap();
|
||||
*block_thread = true;
|
||||
|
||||
// The event needs to be sent after the lock to ensure that `notify_all` is
|
||||
// called after `wait`.
|
||||
cstash.sender.send(event).ok();
|
||||
|
||||
while *block_thread {
|
||||
block_thread = cvar.wait(block_thread).unwrap();
|
||||
}
|
||||
} else {
|
||||
cstash.sender.send(event).ok();
|
||||
}
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_MOVE => {
|
||||
use events::WindowEvent::Moved;
|
||||
let x = LOWORD(lparam as DWORD) as i32;
|
||||
let y = HIWORD(lparam as DWORD) as i32;
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: Moved(x, y),
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_CHAR => {
|
||||
use std::mem;
|
||||
use events::WindowEvent::ReceivedCharacter;
|
||||
let chr: char = mem::transmute(wparam as u32);
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: ReceivedCharacter(chr),
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
// Prevents default windows menu hotkeys playing unwanted
|
||||
// "ding" sounds. Alternatively could check for WM_SYSCOMMAND
|
||||
// with wparam being SC_KEYMENU, but this may prevent some
|
||||
// other unwanted default hotkeys as well.
|
||||
winuser::WM_SYSCHAR => {
|
||||
0
|
||||
}
|
||||
|
||||
winuser::WM_MOUSEMOVE => {
|
||||
use events::WindowEvent::{CursorEntered, CursorMoved};
|
||||
let mouse_outside_window = CONTEXT_STASH.with(|context_stash| {
|
||||
let mut context_stash = context_stash.borrow_mut();
|
||||
if let Some(context_stash) = context_stash.as_mut() {
|
||||
if let Some(w) = context_stash.windows.get_mut(&window) {
|
||||
let mut w = w.lock().unwrap();
|
||||
if !w.mouse_in_window {
|
||||
w.mouse_in_window = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
});
|
||||
|
||||
if mouse_outside_window {
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: CursorEntered { device_id: DEVICE_ID },
|
||||
});
|
||||
|
||||
// Calling TrackMouseEvent in order to receive mouse leave events.
|
||||
winuser::TrackMouseEvent(&mut winuser::TRACKMOUSEEVENT {
|
||||
cbSize: mem::size_of::<winuser::TRACKMOUSEEVENT>() as DWORD,
|
||||
dwFlags: winuser::TME_LEAVE,
|
||||
hwndTrack: window,
|
||||
dwHoverTime: winuser::HOVER_DEFAULT,
|
||||
});
|
||||
}
|
||||
|
||||
let x = windowsx::GET_X_LPARAM(lparam) as f64;
|
||||
let y = windowsx::GET_Y_LPARAM(lparam) as f64;
|
||||
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event::get_key_mods() },
|
||||
});
|
||||
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_MOUSELEAVE => {
|
||||
use events::WindowEvent::CursorLeft;
|
||||
let mouse_in_window = CONTEXT_STASH.with(|context_stash| {
|
||||
let mut context_stash = context_stash.borrow_mut();
|
||||
if let Some(context_stash) = context_stash.as_mut() {
|
||||
if let Some(w) = context_stash.windows.get_mut(&window) {
|
||||
let mut w = w.lock().unwrap();
|
||||
if w.mouse_in_window {
|
||||
w.mouse_in_window = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
});
|
||||
|
||||
if mouse_in_window {
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: CursorLeft { device_id: DEVICE_ID }
|
||||
});
|
||||
}
|
||||
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_MOUSEWHEEL => {
|
||||
use events::{DeviceEvent, WindowEvent};
|
||||
use events::MouseScrollDelta::LineDelta;
|
||||
use events::TouchPhase;
|
||||
|
||||
let value = (wparam >> 16) as i16;
|
||||
let value = value as i32;
|
||||
let value = value as f32 / winuser::WHEEL_DELTA as f32;
|
||||
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, modifiers: event::get_key_mods() },
|
||||
});
|
||||
|
||||
send_event(Event::DeviceEvent {
|
||||
device_id: DEVICE_ID,
|
||||
event: DeviceEvent::MouseWheel { delta: LineDelta(0.0, value) },
|
||||
});
|
||||
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => {
|
||||
use events::ElementState::Pressed;
|
||||
use events::VirtualKeyCode;
|
||||
if msg == winuser::WM_SYSKEYDOWN && wparam as i32 == winuser::VK_F4 {
|
||||
winuser::DefWindowProcW(window, msg, wparam, lparam)
|
||||
} else {
|
||||
let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam);
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: Pressed,
|
||||
scancode: scancode,
|
||||
virtual_keycode: vkey,
|
||||
modifiers: event::get_key_mods(),
|
||||
}
|
||||
}
|
||||
});
|
||||
// Windows doesn't emit a delete character by default, but in order to make it
|
||||
// consistent with the other platforms we'll emit a delete character here.
|
||||
if vkey == Some(VirtualKeyCode::Delete) {
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: WindowEvent::ReceivedCharacter('\u{7F}'),
|
||||
});
|
||||
}
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
winuser::WM_KEYUP | winuser::WM_SYSKEYUP => {
|
||||
use events::ElementState::Released;
|
||||
let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam);
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: Released,
|
||||
scancode: scancode,
|
||||
virtual_keycode: vkey,
|
||||
modifiers: event::get_key_mods(),
|
||||
},
|
||||
}
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_LBUTTONDOWN => {
|
||||
use events::WindowEvent::MouseInput;
|
||||
use events::MouseButton::Left;
|
||||
use events::ElementState::Pressed;
|
||||
|
||||
capture_mouse(window);
|
||||
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left, modifiers: event::get_key_mods() }
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_LBUTTONUP => {
|
||||
use events::WindowEvent::MouseInput;
|
||||
use events::MouseButton::Left;
|
||||
use events::ElementState::Released;
|
||||
|
||||
release_mouse();
|
||||
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: MouseInput { device_id: DEVICE_ID, state: Released, button: Left, modifiers: event::get_key_mods() }
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_RBUTTONDOWN => {
|
||||
use events::WindowEvent::MouseInput;
|
||||
use events::MouseButton::Right;
|
||||
use events::ElementState::Pressed;
|
||||
|
||||
capture_mouse(window);
|
||||
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right, modifiers: event::get_key_mods() }
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_RBUTTONUP => {
|
||||
use events::WindowEvent::MouseInput;
|
||||
use events::MouseButton::Right;
|
||||
use events::ElementState::Released;
|
||||
|
||||
release_mouse();
|
||||
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: MouseInput { device_id: DEVICE_ID, state: Released, button: Right, modifiers: event::get_key_mods() }
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_MBUTTONDOWN => {
|
||||
use events::WindowEvent::MouseInput;
|
||||
use events::MouseButton::Middle;
|
||||
use events::ElementState::Pressed;
|
||||
|
||||
capture_mouse(window);
|
||||
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle, modifiers: event::get_key_mods() }
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_MBUTTONUP => {
|
||||
use events::WindowEvent::MouseInput;
|
||||
use events::MouseButton::Middle;
|
||||
use events::ElementState::Released;
|
||||
|
||||
release_mouse();
|
||||
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: MouseInput { device_id: DEVICE_ID, state: Released, button: Middle, modifiers: event::get_key_mods() }
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_XBUTTONDOWN => {
|
||||
use events::WindowEvent::MouseInput;
|
||||
use events::MouseButton::Other;
|
||||
use events::ElementState::Pressed;
|
||||
let xbutton = winuser::GET_XBUTTON_WPARAM(wparam);
|
||||
|
||||
capture_mouse(window);
|
||||
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Other(xbutton as u8), modifiers: event::get_key_mods() }
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_XBUTTONUP => {
|
||||
use events::WindowEvent::MouseInput;
|
||||
use events::MouseButton::Other;
|
||||
use events::ElementState::Released;
|
||||
let xbutton = winuser::GET_XBUTTON_WPARAM(wparam);
|
||||
|
||||
release_mouse();
|
||||
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: MouseInput { device_id: DEVICE_ID, state: Released, button: Other(xbutton as u8), modifiers: event::get_key_mods() }
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_INPUT => {
|
||||
use events::DeviceEvent::{Motion, MouseMotion};
|
||||
let mut data: winuser::RAWINPUT = mem::uninitialized();
|
||||
let mut data_size = mem::size_of::<winuser::RAWINPUT>() as UINT;
|
||||
winuser::GetRawInputData(mem::transmute(lparam), winuser::RID_INPUT,
|
||||
mem::transmute(&mut data), &mut data_size,
|
||||
mem::size_of::<winuser::RAWINPUTHEADER>() as UINT);
|
||||
|
||||
if data.header.dwType == winuser::RIM_TYPEMOUSE {
|
||||
let mouse = data.data.mouse();
|
||||
if mouse.usFlags & winuser::MOUSE_MOVE_RELATIVE == winuser::MOUSE_MOVE_RELATIVE {
|
||||
let x = mouse.lLastX as f64;
|
||||
let y = mouse.lLastY as f64;
|
||||
|
||||
if x != 0.0 {
|
||||
send_event(Event::DeviceEvent {
|
||||
device_id: DEVICE_ID,
|
||||
event: Motion { axis: 0, value: x }
|
||||
});
|
||||
}
|
||||
|
||||
if y != 0.0 {
|
||||
send_event(Event::DeviceEvent {
|
||||
device_id: DEVICE_ID,
|
||||
event: Motion { axis: 1, value: y }
|
||||
});
|
||||
}
|
||||
|
||||
if x != 0.0 || y != 0.0 {
|
||||
send_event(Event::DeviceEvent {
|
||||
device_id: DEVICE_ID,
|
||||
event: MouseMotion { delta: (x, y) }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
} else {
|
||||
winuser::DefWindowProcW(window, msg, wparam, lparam)
|
||||
}
|
||||
},
|
||||
|
||||
winuser::WM_TOUCH => {
|
||||
let pcount = LOWORD( wparam as DWORD ) as usize;
|
||||
let mut inputs = Vec::with_capacity( pcount );
|
||||
inputs.set_len( pcount );
|
||||
let htouch = lparam as winuser::HTOUCHINPUT;
|
||||
if winuser::GetTouchInputInfo( htouch, pcount as UINT,
|
||||
inputs.as_mut_ptr(),
|
||||
mem::size_of::<winuser::TOUCHINPUT>() as INT ) > 0 {
|
||||
for input in &inputs {
|
||||
send_event( Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
phase:
|
||||
if input.dwFlags & winuser::TOUCHEVENTF_DOWN != 0 {
|
||||
TouchPhase::Started
|
||||
} else if input.dwFlags & winuser::TOUCHEVENTF_UP != 0 {
|
||||
TouchPhase::Ended
|
||||
} else if input.dwFlags & winuser::TOUCHEVENTF_MOVE != 0 {
|
||||
TouchPhase::Moved
|
||||
} else {
|
||||
continue;
|
||||
},
|
||||
location: ((input.x as f64) / 100f64,
|
||||
(input.y as f64) / 100f64),
|
||||
id: input.dwID as u64,
|
||||
device_id: DEVICE_ID,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
winuser::CloseTouchInputHandle( htouch );
|
||||
0
|
||||
}
|
||||
|
||||
winuser::WM_SETFOCUS => {
|
||||
use events::WindowEvent::{Focused, CursorMoved};
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: Focused(true)
|
||||
});
|
||||
|
||||
let x = windowsx::GET_X_LPARAM(lparam) as f64;
|
||||
let y = windowsx::GET_Y_LPARAM(lparam) as f64;
|
||||
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event::get_key_mods() },
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_KILLFOCUS => {
|
||||
use events::WindowEvent::Focused;
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: Focused(false)
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_SETCURSOR => {
|
||||
let call_def_window_proc = CONTEXT_STASH.with(|context_stash| {
|
||||
let cstash = context_stash.borrow();
|
||||
let mut call_def_window_proc = false;
|
||||
if let Some(cstash) = cstash.as_ref() {
|
||||
if let Some(w_stash) = cstash.windows.get(&window) {
|
||||
if let Ok(window_state) = w_stash.lock() {
|
||||
if window_state.mouse_in_window {
|
||||
match window_state.cursor_state {
|
||||
CursorState::Normal => {
|
||||
winuser::SetCursor(winuser::LoadCursorW(
|
||||
ptr::null_mut(),
|
||||
window_state.cursor));
|
||||
},
|
||||
CursorState::Grab | CursorState::Hide => {
|
||||
winuser::SetCursor(ptr::null_mut());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
call_def_window_proc = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
call_def_window_proc
|
||||
});
|
||||
|
||||
if call_def_window_proc {
|
||||
winuser::DefWindowProcW(window, msg, wparam, lparam)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
winuser::WM_DROPFILES => {
|
||||
use events::WindowEvent::DroppedFile;
|
||||
|
||||
let hdrop = wparam as shellapi::HDROP;
|
||||
let mut pathbuf: [u16; MAX_PATH] = mem::uninitialized();
|
||||
let num_drops = shellapi::DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0);
|
||||
|
||||
for i in 0..num_drops {
|
||||
let nch = shellapi::DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(),
|
||||
MAX_PATH as u32) as usize;
|
||||
if nch > 0 {
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: DroppedFile(OsString::from_wide(&pathbuf[0..nch]).into())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
shellapi::DragFinish(hdrop);
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_GETMINMAXINFO => {
|
||||
let mmi = lparam as *mut winuser::MINMAXINFO;
|
||||
//(*mmi).max_position = winapi::shared::windef::POINT { x: -8, y: -8 }; // The upper left corner of the window if it were maximized on the primary monitor.
|
||||
//(*mmi).max_size = winapi::shared::windef::POINT { x: .., y: .. }; // The dimensions of the primary monitor.
|
||||
|
||||
CONTEXT_STASH.with(|context_stash| {
|
||||
if let Some(cstash) = context_stash.borrow().as_ref() {
|
||||
if let Some(wstash) = cstash.windows.get(&window) {
|
||||
let window_state = wstash.lock().unwrap();
|
||||
|
||||
if window_state.attributes.min_dimensions.is_some() ||
|
||||
window_state.attributes.max_dimensions.is_some() {
|
||||
|
||||
let style = winuser::GetWindowLongA(window, winuser::GWL_STYLE) as DWORD;
|
||||
let ex_style = winuser::GetWindowLongA(window, winuser::GWL_EXSTYLE) as DWORD;
|
||||
|
||||
if let Some(min_dimensions) = window_state.attributes.min_dimensions {
|
||||
let (width, height) = adjust_size(min_dimensions, style, ex_style);
|
||||
(*mmi).ptMinTrackSize = POINT { x: width as i32, y: height as i32 };
|
||||
}
|
||||
if let Some(max_dimensions) = window_state.attributes.max_dimensions {
|
||||
let (width, height) = adjust_size(max_dimensions, style, ex_style);
|
||||
(*mmi).ptMaxTrackSize = POINT { x: width as i32, y: height as i32 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
0
|
||||
},
|
||||
|
||||
_ => {
|
||||
if msg == *DESTROY_MSG_ID {
|
||||
winuser::DestroyWindow(window);
|
||||
0
|
||||
} else {
|
||||
winuser::DefWindowProcW(window, msg, wparam, lparam)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use winapi;
|
||||
use winapi::shared::windef::HWND;
|
||||
|
||||
pub use self::events_loop::{EventsLoop, EventsLoopProxy};
|
||||
pub use self::monitor::MonitorId;
|
||||
pub use self::window::Window;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub parent: Option<HWND>,
|
||||
}
|
||||
|
||||
unsafe impl Send for PlatformSpecificWindowBuilderAttributes {}
|
||||
unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
|
||||
|
||||
// TODO: document what this means
|
||||
pub type Cursor = *const winapi::ctypes::wchar_t;
|
||||
|
||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(HWND);
|
||||
unsafe impl Send for WindowId {}
|
||||
unsafe impl Sync for WindowId {}
|
||||
|
||||
mod event;
|
||||
mod events_loop;
|
||||
mod monitor;
|
||||
mod window;
|
||||
@@ -1,178 +0,0 @@
|
||||
use winapi::ctypes::wchar_t;
|
||||
use winapi::shared::minwindef::{DWORD, LPARAM, BOOL, TRUE};
|
||||
use winapi::shared::windef::{HMONITOR, HDC, LPRECT, HWND};
|
||||
use winapi::um::winuser;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::{mem, ptr};
|
||||
|
||||
use super::EventsLoop;
|
||||
|
||||
/// Win32 implementation of the main `MonitorId` object.
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId {
|
||||
/// The system name of the adapter.
|
||||
adapter_name: [wchar_t; 32],
|
||||
|
||||
/// Monitor handle.
|
||||
hmonitor: HMonitor,
|
||||
|
||||
/// The system name of the monitor.
|
||||
monitor_name: String,
|
||||
|
||||
/// True if this is the primary monitor.
|
||||
primary: bool,
|
||||
|
||||
/// The position of the monitor in pixels on the desktop.
|
||||
///
|
||||
/// A window that is positioned at these coordinates will overlap the monitor.
|
||||
position: (i32, i32),
|
||||
|
||||
/// The current resolution in pixels on the monitor.
|
||||
dimensions: (u32, u32),
|
||||
|
||||
/// DPI scaling factor.
|
||||
hidpi_factor: f32,
|
||||
}
|
||||
|
||||
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// https://github.com/retep998/winapi-rs/issues/396
|
||||
#[derive(Clone)]
|
||||
struct HMonitor(HMONITOR);
|
||||
|
||||
unsafe impl Send for HMonitor {}
|
||||
|
||||
fn wchar_as_string(wchar: &[wchar_t]) -> String {
|
||||
String::from_utf16_lossy(wchar)
|
||||
.trim_right_matches(0 as char)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
unsafe extern "system" fn monitor_enum_proc(hmonitor: HMONITOR, _: HDC, place: LPRECT, data: LPARAM) -> BOOL {
|
||||
let monitors = data as *mut VecDeque<MonitorId>;
|
||||
|
||||
let place = *place;
|
||||
let position = (place.left as i32, place.top as i32);
|
||||
let dimensions = ((place.right - place.left) as u32, (place.bottom - place.top) as u32);
|
||||
|
||||
let mut monitor_info: winuser::MONITORINFOEXW = mem::zeroed();
|
||||
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
|
||||
if winuser::GetMonitorInfoW(hmonitor, &mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO) == 0 {
|
||||
// Some error occurred, just skip this monitor and go on.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
(*monitors).push_back(MonitorId {
|
||||
adapter_name: monitor_info.szDevice,
|
||||
hmonitor: HMonitor(hmonitor),
|
||||
monitor_name: wchar_as_string(&monitor_info.szDevice),
|
||||
primary: monitor_info.dwFlags & winuser::MONITORINFOF_PRIMARY != 0,
|
||||
position,
|
||||
dimensions,
|
||||
hidpi_factor: 1.0,
|
||||
});
|
||||
|
||||
// TRUE means continue enumeration.
|
||||
TRUE
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
unsafe {
|
||||
let mut result: VecDeque<MonitorId> = VecDeque::new();
|
||||
winuser::EnumDisplayMonitors(ptr::null_mut(), ptr::null_mut(), Some(monitor_enum_proc), &mut result as *mut _ as LPARAM);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_current_monitor(handle: HWND) -> MonitorId {
|
||||
unsafe {
|
||||
let mut monitor_info: winuser::MONITORINFOEXW = mem::zeroed();
|
||||
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
|
||||
|
||||
let hmonitor = winuser::MonitorFromWindow(handle, winuser::MONITOR_DEFAULTTONEAREST);
|
||||
|
||||
winuser::GetMonitorInfoW(
|
||||
hmonitor,
|
||||
&mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO,
|
||||
);
|
||||
|
||||
let place = monitor_info.rcMonitor;
|
||||
let position = (place.left as i32, place.top as i32);
|
||||
let dimensions = (
|
||||
(place.right - place.left) as u32,
|
||||
(place.bottom - place.top) as u32,
|
||||
);
|
||||
|
||||
MonitorId {
|
||||
adapter_name: monitor_info.szDevice,
|
||||
hmonitor: super::monitor::HMonitor(hmonitor),
|
||||
monitor_name: wchar_as_string(&monitor_info.szDevice),
|
||||
primary: monitor_info.dwFlags & winuser::MONITORINFOF_PRIMARY != 0,
|
||||
position,
|
||||
dimensions,
|
||||
hidpi_factor: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
// we simply get all available monitors and return the one with the `MONITORINFOF_PRIMARY` flag
|
||||
// TODO: it is possible to query the win32 API for the primary monitor, this should be done
|
||||
// instead
|
||||
for monitor in self.get_available_monitors().into_iter() {
|
||||
if monitor.primary {
|
||||
return monitor;
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Failed to find the primary monitor")
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
/// See the docs if the crate root file.
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some(self.monitor_name.clone())
|
||||
}
|
||||
|
||||
/// See the docs of the crate root file.
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> String {
|
||||
self.monitor_name.clone()
|
||||
}
|
||||
|
||||
/// See the docs of the crate root file.
|
||||
#[inline]
|
||||
pub fn get_hmonitor(&self) -> HMONITOR {
|
||||
self.hmonitor.0
|
||||
}
|
||||
|
||||
/// See the docs of the crate root file.
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
// TODO: retrieve the dimensions every time this is called
|
||||
self.dimensions
|
||||
}
|
||||
|
||||
/// This is a Win32-only function for `MonitorId` that returns the system name of the adapter
|
||||
/// device.
|
||||
#[inline]
|
||||
pub fn get_adapter_name(&self) -> &[wchar_t] {
|
||||
&self.adapter_name
|
||||
}
|
||||
|
||||
/// A window that is positioned at these coordinates will overlap the monitor.
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
self.position
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
self.hidpi_factor
|
||||
}
|
||||
}
|
||||
@@ -1,942 +0,0 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::os::raw;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::cell::Cell;
|
||||
|
||||
use platform::platform::events_loop::{self, DESTROY_MSG_ID};
|
||||
use platform::platform::EventsLoop;
|
||||
use platform::platform::PlatformSpecificWindowBuilderAttributes;
|
||||
use platform::platform::WindowId;
|
||||
|
||||
use CreationError;
|
||||
use CursorState;
|
||||
use MouseCursor;
|
||||
use WindowAttributes;
|
||||
use MonitorId as RootMonitorId;
|
||||
|
||||
use winapi::shared::minwindef::{UINT, DWORD, BOOL};
|
||||
use winapi::shared::windef::{HWND, HDC, RECT, POINT};
|
||||
use winapi::shared::hidusage;
|
||||
use winapi::um::{winuser, dwmapi, libloaderapi, processthreadsapi};
|
||||
use winapi::um::winnt::{LPCWSTR, LONG, HRESULT};
|
||||
use winapi::um::combaseapi;
|
||||
use winapi::um::objbase::{COINIT_MULTITHREADED};
|
||||
use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl};
|
||||
|
||||
/// The Win32 implementation of the main `Window` object.
|
||||
pub struct Window {
|
||||
/// Main handle for the window.
|
||||
window: WindowWrapper,
|
||||
|
||||
/// The current window state.
|
||||
window_state: Arc<Mutex<events_loop::WindowState>>,
|
||||
|
||||
// The events loop proxy.
|
||||
events_loop_proxy: events_loop::EventsLoopProxy,
|
||||
}
|
||||
|
||||
unsafe impl Send for Window {}
|
||||
unsafe impl Sync for Window {}
|
||||
|
||||
// https://blogs.msdn.microsoft.com/oldnewthing/20131017-00/?p=2903
|
||||
// The idea here is that we use the AdjustWindowRectEx function to calculate how much additional
|
||||
// non-client area gets added due to the styles we passed. To make the math simple,
|
||||
// we ask for a zero client rectangle, so that the resulting window is all non-client.
|
||||
// And then we pass in the empty rectangle represented by the dot in the middle,
|
||||
// and the AdjustWindowRectEx expands the rectangle in all dimensions.
|
||||
// We see that it added ten pixels to the left, right, and bottom,
|
||||
// and it added fifty pixels to the top.
|
||||
// From this we can perform the reverse calculation: Instead of expanding the rectangle, we shrink it.
|
||||
unsafe fn unjust_window_rect(prc: &mut RECT, style: DWORD, ex_style: DWORD) -> BOOL {
|
||||
let mut rc: RECT = mem::zeroed();
|
||||
|
||||
winuser::SetRectEmpty(&mut rc);
|
||||
|
||||
let frc = winuser::AdjustWindowRectEx(&mut rc, style, 0, ex_style);
|
||||
if frc != 0 {
|
||||
prc.left -= rc.left;
|
||||
prc.top -= rc.top;
|
||||
prc.right -= rc.right;
|
||||
prc.bottom -= rc.bottom;
|
||||
}
|
||||
|
||||
frc
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(events_loop: &EventsLoop, w_attr: &WindowAttributes,
|
||||
pl_attr: &PlatformSpecificWindowBuilderAttributes) -> Result<Window, CreationError>
|
||||
{
|
||||
let mut w_attr = Some(w_attr.clone());
|
||||
let mut pl_attr = Some(pl_attr.clone());
|
||||
|
||||
let (tx, rx) = channel();
|
||||
|
||||
let proxy = events_loop.create_proxy();
|
||||
|
||||
events_loop.execute_in_thread(move |inserter| {
|
||||
// We dispatch an `init` function because of code style.
|
||||
let win = unsafe { init(w_attr.take().unwrap(), pl_attr.take().unwrap(), inserter, proxy.clone()) };
|
||||
let _ = tx.send(win);
|
||||
});
|
||||
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
|
||||
pub fn set_title(&self, text: &str) {
|
||||
unsafe {
|
||||
let text = OsStr::new(text).encode_wide().chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
winuser::SetWindowTextW(self.window.0, text.as_ptr() as LPCWSTR);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
unsafe {
|
||||
winuser::ShowWindow(self.window.0, winuser::SW_SHOW);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
unsafe {
|
||||
winuser::ShowWindow(self.window.0, winuser::SW_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
/// See the docs in the crate root file.
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
use std::mem;
|
||||
|
||||
let mut placement: winuser::WINDOWPLACEMENT = unsafe { mem::zeroed() };
|
||||
placement.length = mem::size_of::<winuser::WINDOWPLACEMENT>() as UINT;
|
||||
|
||||
if unsafe { winuser::GetWindowPlacement(self.window.0, &mut placement) } == 0 {
|
||||
return None
|
||||
}
|
||||
|
||||
let ref rect = placement.rcNormalPosition;
|
||||
Some((rect.left as i32, rect.top as i32))
|
||||
}
|
||||
|
||||
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
|
||||
use std::mem;
|
||||
|
||||
let mut position: POINT = unsafe{ mem::zeroed() };
|
||||
if unsafe{ winuser::ClientToScreen(self.window.0, &mut position) } == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((position.x, position.y))
|
||||
}
|
||||
|
||||
/// See the docs in the crate root file.
|
||||
pub fn set_position(&self, x: i32, y: i32) {
|
||||
unsafe {
|
||||
winuser::SetWindowPos(self.window.0, ptr::null_mut(), x as raw::c_int, y as raw::c_int,
|
||||
0, 0, winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOSIZE);
|
||||
winuser::UpdateWindow(self.window.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// See the docs in the crate root file.
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
|
||||
let mut rect: RECT = unsafe { mem::uninitialized() };
|
||||
|
||||
if unsafe { winuser::GetClientRect(self.window.0, &mut rect) } == 0 {
|
||||
return None
|
||||
}
|
||||
|
||||
Some((
|
||||
(rect.right - rect.left) as u32,
|
||||
(rect.bottom - rect.top) as u32
|
||||
))
|
||||
}
|
||||
|
||||
/// See the docs in the crate root file.
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
|
||||
let mut rect: RECT = unsafe { mem::uninitialized() };
|
||||
|
||||
if unsafe { winuser::GetWindowRect(self.window.0, &mut rect) } == 0 {
|
||||
return None
|
||||
}
|
||||
|
||||
Some((
|
||||
(rect.right - rect.left) as u32,
|
||||
(rect.bottom - rect.top) as u32
|
||||
))
|
||||
}
|
||||
|
||||
/// See the docs in the crate root file.
|
||||
pub fn set_inner_size(&self, x: u32, y: u32) {
|
||||
unsafe {
|
||||
// Calculate the outer size based upon the specified inner size
|
||||
let mut rect = RECT { top: 0, left: 0, bottom: y as LONG, right: x as LONG };
|
||||
let dw_style = winuser::GetWindowLongA(self.window.0, winuser::GWL_STYLE) as DWORD;
|
||||
let b_menu = !winuser::GetMenu(self.window.0).is_null() as BOOL;
|
||||
let dw_style_ex = winuser::GetWindowLongA(self.window.0, winuser::GWL_EXSTYLE) as DWORD;
|
||||
winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex);
|
||||
let outer_x = (rect.right - rect.left).abs() as raw::c_int;
|
||||
let outer_y = (rect.top - rect.bottom).abs() as raw::c_int;
|
||||
|
||||
winuser::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y,
|
||||
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOREPOSITION | winuser::SWP_NOMOVE);
|
||||
winuser::UpdateWindow(self.window.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// See the docs in the crate root file.
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
window_state.attributes.min_dimensions = dimensions;
|
||||
|
||||
// Make windows re-check the window size bounds.
|
||||
if let Some(inner_size) = self.get_inner_size() {
|
||||
unsafe {
|
||||
let mut rect = RECT { top: 0, left: 0, bottom: inner_size.1 as LONG, right: inner_size.0 as LONG };
|
||||
let dw_style = winuser::GetWindowLongA(self.window.0, winuser::GWL_STYLE) as DWORD;
|
||||
let b_menu = !winuser::GetMenu(self.window.0).is_null() as BOOL;
|
||||
let dw_style_ex = winuser::GetWindowLongA(self.window.0, winuser::GWL_EXSTYLE) as DWORD;
|
||||
winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex);
|
||||
let outer_x = (rect.right - rect.left).abs() as raw::c_int;
|
||||
let outer_y = (rect.top - rect.bottom).abs() as raw::c_int;
|
||||
|
||||
winuser::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y,
|
||||
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOREPOSITION | winuser::SWP_NOMOVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// See the docs in the crate root file.
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
window_state.attributes.max_dimensions = dimensions;
|
||||
|
||||
// Make windows re-check the window size bounds.
|
||||
if let Some(inner_size) = self.get_inner_size() {
|
||||
unsafe {
|
||||
let mut rect = RECT { top: 0, left: 0, bottom: inner_size.1 as LONG, right: inner_size.0 as LONG };
|
||||
let dw_style = winuser::GetWindowLongA(self.window.0, winuser::GWL_STYLE) as DWORD;
|
||||
let b_menu = !winuser::GetMenu(self.window.0).is_null() as BOOL;
|
||||
let dw_style_ex = winuser::GetWindowLongA(self.window.0, winuser::GWL_EXSTYLE) as DWORD;
|
||||
winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex);
|
||||
let outer_x = (rect.right - rect.left).abs() as raw::c_int;
|
||||
let outer_y = (rect.top - rect.bottom).abs() as raw::c_int;
|
||||
|
||||
winuser::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y,
|
||||
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOREPOSITION | winuser::SWP_NOMOVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
pub fn platform_display(&self) -> *mut ::libc::c_void {
|
||||
panic!() // Deprecated function ; we don't care anymore
|
||||
}
|
||||
// TODO: remove
|
||||
pub fn platform_window(&self) -> *mut ::libc::c_void {
|
||||
self.window.0 as *mut ::libc::c_void
|
||||
}
|
||||
|
||||
/// Returns the `hwnd` of this window.
|
||||
#[inline]
|
||||
pub fn hwnd(&self) -> HWND {
|
||||
self.window.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, cursor: MouseCursor) {
|
||||
let cursor_id = match cursor {
|
||||
MouseCursor::Arrow | MouseCursor::Default => winuser::IDC_ARROW,
|
||||
MouseCursor::Hand => winuser::IDC_HAND,
|
||||
MouseCursor::Crosshair => winuser::IDC_CROSS,
|
||||
MouseCursor::Text | MouseCursor::VerticalText => winuser::IDC_IBEAM,
|
||||
MouseCursor::NotAllowed | MouseCursor::NoDrop => winuser::IDC_NO,
|
||||
MouseCursor::Grab | MouseCursor::Grabbing |
|
||||
MouseCursor::Move | MouseCursor::AllScroll => winuser::IDC_SIZEALL,
|
||||
MouseCursor::EResize | MouseCursor::WResize |
|
||||
MouseCursor::EwResize | MouseCursor::ColResize => winuser::IDC_SIZEWE,
|
||||
MouseCursor::NResize | MouseCursor::SResize |
|
||||
MouseCursor::NsResize | MouseCursor::RowResize => winuser::IDC_SIZENS,
|
||||
MouseCursor::NeResize | MouseCursor::SwResize |
|
||||
MouseCursor::NeswResize => winuser::IDC_SIZENESW,
|
||||
MouseCursor::NwResize | MouseCursor::SeResize |
|
||||
MouseCursor::NwseResize => winuser::IDC_SIZENWSE,
|
||||
MouseCursor::Wait => winuser::IDC_WAIT,
|
||||
MouseCursor::Progress => winuser::IDC_APPSTARTING,
|
||||
MouseCursor::Help => winuser::IDC_HELP,
|
||||
_ => winuser::IDC_ARROW, // use arrow for the missing cases.
|
||||
};
|
||||
|
||||
let mut cur = self.window_state.lock().unwrap();
|
||||
cur.cursor = cursor_id;
|
||||
}
|
||||
|
||||
// TODO: it should be possible to rework this function by using the `execute_in_thread` method
|
||||
// of the events loop.
|
||||
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
|
||||
let mut current_state = self.window_state.lock().unwrap();
|
||||
|
||||
let foreground_thread_id = unsafe { winuser::GetWindowThreadProcessId(self.window.0, ptr::null_mut()) };
|
||||
let current_thread_id = unsafe { processthreadsapi::GetCurrentThreadId() };
|
||||
|
||||
unsafe { winuser::AttachThreadInput(foreground_thread_id, current_thread_id, 1) };
|
||||
|
||||
let res = match (state, current_state.cursor_state) {
|
||||
(CursorState::Normal, CursorState::Normal) => Ok(()),
|
||||
(CursorState::Hide, CursorState::Hide) => Ok(()),
|
||||
(CursorState::Grab, CursorState::Grab) => Ok(()),
|
||||
|
||||
(CursorState::Hide, CursorState::Normal) => {
|
||||
current_state.cursor_state = CursorState::Hide;
|
||||
Ok(())
|
||||
},
|
||||
|
||||
(CursorState::Normal, CursorState::Hide) => {
|
||||
current_state.cursor_state = CursorState::Normal;
|
||||
Ok(())
|
||||
},
|
||||
|
||||
(CursorState::Grab, CursorState::Normal) | (CursorState::Grab, CursorState::Hide) => {
|
||||
unsafe {
|
||||
let mut rect = mem::uninitialized();
|
||||
if winuser::GetClientRect(self.window.0, &mut rect) == 0 {
|
||||
return Err(format!("GetWindowRect failed"));
|
||||
}
|
||||
winuser::ClientToScreen(self.window.0, mem::transmute(&mut rect.left));
|
||||
winuser::ClientToScreen(self.window.0, mem::transmute(&mut rect.right));
|
||||
if winuser::ClipCursor(&rect) == 0 {
|
||||
return Err(format!("ClipCursor failed"));
|
||||
}
|
||||
current_state.cursor_state = CursorState::Grab;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
|
||||
(CursorState::Normal, CursorState::Grab) => {
|
||||
unsafe {
|
||||
if winuser::ClipCursor(ptr::null()) == 0 {
|
||||
return Err(format!("ClipCursor failed"));
|
||||
}
|
||||
current_state.cursor_state = CursorState::Normal;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
unsafe { winuser::AttachThreadInput(foreground_thread_id, current_thread_id, 0) };
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> {
|
||||
let mut point = POINT {
|
||||
x: x,
|
||||
y: y,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
if winuser::ClientToScreen(self.window.0, &mut point) == 0 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
if winuser::SetCursorPos(point.x, point.y) == 0 {
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId(self.window.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
|
||||
window_state.attributes.maximized = maximized;
|
||||
// we only maximized if we are not in fullscreen
|
||||
if window_state.attributes.fullscreen.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let window = self.window.clone();
|
||||
unsafe {
|
||||
// And because ShowWindow will resize the window
|
||||
// We call it in the main thread
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
winuser::ShowWindow(
|
||||
window.0,
|
||||
if maximized {
|
||||
winuser::SW_MAXIMIZE
|
||||
} else {
|
||||
winuser::SW_RESTORE
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn set_fullscreen_style(&self) -> (LONG, LONG) {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
|
||||
if window_state.attributes.fullscreen.is_none() || window_state.saved_window_info.is_none() {
|
||||
let mut rect: RECT = mem::zeroed();
|
||||
|
||||
winuser::GetWindowRect(self.window.0, &mut rect);
|
||||
|
||||
window_state.saved_window_info = Some(events_loop::SavedWindowInfo {
|
||||
style: winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE),
|
||||
ex_style: winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE),
|
||||
rect,
|
||||
});
|
||||
}
|
||||
|
||||
// We sync the system maximized state here, it will be used when restoring
|
||||
let mut placement: winuser::WINDOWPLACEMENT = mem::zeroed();
|
||||
placement.length = mem::size_of::<winuser::WINDOWPLACEMENT>() as u32;
|
||||
winuser::GetWindowPlacement(self.window.0, &mut placement);
|
||||
window_state.attributes.maximized =
|
||||
placement.showCmd == (winuser::SW_SHOWMAXIMIZED as u32);
|
||||
let saved_window_info = window_state.saved_window_info.as_ref().unwrap();
|
||||
|
||||
(saved_window_info.style, saved_window_info.ex_style)
|
||||
}
|
||||
|
||||
unsafe fn restore_saved_window(&self) {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
// Reset original window style and size. The multiple window size/moves
|
||||
// here are ugly, but if SetWindowPos() doesn't redraw, the taskbar won't be
|
||||
// repainted. Better-looking methods welcome.
|
||||
let saved_window_info = window_state.saved_window_info.as_ref().unwrap();
|
||||
|
||||
let rect = saved_window_info.rect.clone();
|
||||
let window = self.window.clone();
|
||||
let (style, ex_style) = (saved_window_info.style, saved_window_info.ex_style);
|
||||
|
||||
let maximized = window_state.attributes.maximized;
|
||||
|
||||
// On restore, resize to the previous saved rect size.
|
||||
// And because SetWindowPos will resize the window
|
||||
// We call it in the main thread
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style);
|
||||
winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style);
|
||||
|
||||
winuser::SetWindowPos(
|
||||
window.0,
|
||||
ptr::null_mut(),
|
||||
rect.left,
|
||||
rect.top,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top,
|
||||
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE
|
||||
| winuser::SWP_FRAMECHANGED,
|
||||
);
|
||||
|
||||
// if it was set to maximized when it were fullscreened, we restore it as well
|
||||
winuser::ShowWindow(
|
||||
window.0,
|
||||
if maximized {
|
||||
winuser::SW_MAXIMIZE
|
||||
} else {
|
||||
winuser::SW_RESTORE
|
||||
},
|
||||
);
|
||||
|
||||
mark_fullscreen(window.0, false);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
||||
unsafe {
|
||||
match &monitor {
|
||||
&Some(RootMonitorId { ref inner }) => {
|
||||
let pos = inner.get_position();
|
||||
let dim = inner.get_dimensions();
|
||||
let window = self.window.clone();
|
||||
|
||||
let (style, ex_style) = self.set_fullscreen_style();
|
||||
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
winuser::SetWindowLongW(
|
||||
window.0,
|
||||
winuser::GWL_STYLE,
|
||||
((style as DWORD) & !(winuser::WS_CAPTION | winuser::WS_THICKFRAME))
|
||||
as LONG,
|
||||
);
|
||||
|
||||
winuser::SetWindowLongW(
|
||||
window.0,
|
||||
winuser::GWL_EXSTYLE,
|
||||
((ex_style as DWORD)
|
||||
& !(winuser::WS_EX_DLGMODALFRAME | winuser::WS_EX_WINDOWEDGE
|
||||
| winuser::WS_EX_CLIENTEDGE
|
||||
| winuser::WS_EX_STATICEDGE))
|
||||
as LONG,
|
||||
);
|
||||
|
||||
winuser::SetWindowPos(
|
||||
window.0,
|
||||
ptr::null_mut(),
|
||||
pos.0,
|
||||
pos.1,
|
||||
dim.0 as i32,
|
||||
dim.1 as i32,
|
||||
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOACTIVATE
|
||||
| winuser::SWP_FRAMECHANGED,
|
||||
);
|
||||
|
||||
mark_fullscreen(window.0, true);
|
||||
});
|
||||
}
|
||||
&None => {
|
||||
self.restore_saved_window();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
window_state.attributes.fullscreen = monitor;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
if let Ok(mut window_state) = self.window_state.lock() {
|
||||
if window_state.attributes.decorations == decorations {
|
||||
return;
|
||||
}
|
||||
|
||||
let style_flags = (winuser::WS_CAPTION | winuser::WS_THICKFRAME) as LONG;
|
||||
let ex_style_flags = (winuser::WS_EX_WINDOWEDGE) as LONG;
|
||||
|
||||
// if we are in fullscreen mode, we only change the saved window info
|
||||
if window_state.attributes.fullscreen.is_some() {
|
||||
{
|
||||
let mut saved = window_state.saved_window_info.as_mut().unwrap();
|
||||
|
||||
unsafe {
|
||||
unjust_window_rect(&mut saved.rect, saved.style as _, saved.ex_style as _);
|
||||
}
|
||||
|
||||
if decorations {
|
||||
saved.style = saved.style | style_flags;
|
||||
saved.ex_style = saved.ex_style | ex_style_flags;
|
||||
} else {
|
||||
saved.style = saved.style & !style_flags;
|
||||
saved.ex_style = saved.ex_style & !ex_style_flags;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
winuser::AdjustWindowRectEx(
|
||||
&mut saved.rect,
|
||||
saved.style as _,
|
||||
0,
|
||||
saved.ex_style as _,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
window_state.attributes.decorations = decorations;
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let mut rect: RECT = mem::zeroed();
|
||||
winuser::GetWindowRect(self.window.0, &mut rect);
|
||||
|
||||
let mut style = winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE);
|
||||
let mut ex_style = winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE);
|
||||
unjust_window_rect(&mut rect, style as _, ex_style as _);
|
||||
|
||||
if decorations {
|
||||
style = style | style_flags;
|
||||
ex_style = ex_style | ex_style_flags;
|
||||
} else {
|
||||
style = style & !style_flags;
|
||||
ex_style = ex_style & !ex_style_flags;
|
||||
}
|
||||
|
||||
let window = self.window.clone();
|
||||
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style);
|
||||
winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style);
|
||||
winuser::AdjustWindowRectEx(&mut rect, style as _, 0, ex_style as _);
|
||||
|
||||
winuser::SetWindowPos(
|
||||
window.0,
|
||||
ptr::null_mut(),
|
||||
rect.left,
|
||||
rect.top,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top,
|
||||
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOACTIVATE
|
||||
| winuser::SWP_FRAMECHANGED,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
window_state.attributes.decorations = decorations;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
RootMonitorId {
|
||||
inner: EventsLoop::get_current_monitor(self.window.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// The window must be destroyed from the same thread that created it, so we send a
|
||||
// custom message to be handled by our callback to do the actual work.
|
||||
winuser::PostMessageW(self.window.0, *DESTROY_MSG_ID, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple non-owning wrapper around a window.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
pub struct WindowWrapper(HWND, HDC);
|
||||
|
||||
// Send is not implemented for HWND and HDC, we have to wrap it and implement it manually.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// https://github.com/retep998/winapi-rs/issues/396
|
||||
unsafe impl Send for WindowWrapper {}
|
||||
|
||||
pub unsafe fn adjust_size(
|
||||
(x, y): (u32, u32), style: DWORD, ex_style: DWORD,
|
||||
) -> (LONG, LONG) {
|
||||
let mut rect = RECT { left: 0, right: x as LONG, top: 0, bottom: y as LONG };
|
||||
winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style);
|
||||
(rect.right - rect.left, rect.bottom - rect.top)
|
||||
}
|
||||
|
||||
unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
inserter: events_loop::Inserter, events_loop_proxy: events_loop::EventsLoopProxy) -> Result<Window, CreationError> {
|
||||
let title = OsStr::new(&window.title).encode_wide().chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// registering the window class
|
||||
let class_name = register_window_class();
|
||||
|
||||
// building a RECT object with coordinates
|
||||
let mut rect = RECT {
|
||||
left: 0, right: window.dimensions.unwrap_or((1024, 768)).0 as LONG,
|
||||
top: 0, bottom: window.dimensions.unwrap_or((1024, 768)).1 as LONG,
|
||||
};
|
||||
|
||||
// computing the style and extended style of the window
|
||||
let (ex_style, style) = if !window.decorations {
|
||||
(winuser::WS_EX_APPWINDOW,
|
||||
//winapi::WS_POPUP is incompatible with winapi::WS_CHILD
|
||||
if pl_attribs.parent.is_some() {
|
||||
winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN
|
||||
}
|
||||
else {
|
||||
winuser::WS_POPUP | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN
|
||||
}
|
||||
)
|
||||
} else {
|
||||
(winuser::WS_EX_APPWINDOW | winuser::WS_EX_WINDOWEDGE,
|
||||
winuser::WS_OVERLAPPEDWINDOW | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN)
|
||||
};
|
||||
|
||||
// adjusting the window coordinates using the style
|
||||
winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style);
|
||||
|
||||
// creating the real window this time, by using the functions in `extra_functions`
|
||||
let real_window = {
|
||||
let (width, height) = if window.dimensions.is_some() {
|
||||
let min_dimensions = window.min_dimensions
|
||||
.map(|d| adjust_size(d, style, ex_style))
|
||||
.unwrap_or((0, 0));
|
||||
let max_dimensions = window.max_dimensions
|
||||
.map(|d| adjust_size(d, style, ex_style))
|
||||
.unwrap_or((raw::c_int::max_value(), raw::c_int::max_value()));
|
||||
|
||||
(
|
||||
Some((rect.right - rect.left).min(max_dimensions.0).max(min_dimensions.0)),
|
||||
Some((rect.bottom - rect.top).min(max_dimensions.1).max(min_dimensions.1))
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let mut style = if !window.visible {
|
||||
style
|
||||
} else {
|
||||
style | winuser::WS_VISIBLE
|
||||
};
|
||||
|
||||
if pl_attribs.parent.is_some() {
|
||||
style |= winuser::WS_CHILD;
|
||||
}
|
||||
|
||||
let handle = winuser::CreateWindowExW(ex_style | winuser::WS_EX_ACCEPTFILES,
|
||||
class_name.as_ptr(),
|
||||
title.as_ptr() as LPCWSTR,
|
||||
style | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN,
|
||||
winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT,
|
||||
width.unwrap_or(winuser::CW_USEDEFAULT), height.unwrap_or(winuser::CW_USEDEFAULT),
|
||||
pl_attribs.parent.unwrap_or(ptr::null_mut()),
|
||||
ptr::null_mut(), libloaderapi::GetModuleHandleW(ptr::null()),
|
||||
ptr::null_mut());
|
||||
|
||||
if handle.is_null() {
|
||||
return Err(CreationError::OsError(format!("CreateWindowEx function failed: {}",
|
||||
format!("{}", io::Error::last_os_error()))));
|
||||
}
|
||||
|
||||
let hdc = winuser::GetDC(handle);
|
||||
if hdc.is_null() {
|
||||
return Err(CreationError::OsError(format!("GetDC function failed: {}",
|
||||
format!("{}", io::Error::last_os_error()))));
|
||||
}
|
||||
|
||||
WindowWrapper(handle, hdc)
|
||||
};
|
||||
|
||||
// Set up raw mouse input
|
||||
{
|
||||
let mut rid: winuser::RAWINPUTDEVICE = mem::uninitialized();
|
||||
rid.usUsagePage = hidusage::HID_USAGE_PAGE_GENERIC;
|
||||
rid.usUsage = hidusage::HID_USAGE_GENERIC_MOUSE;
|
||||
rid.dwFlags = 0;
|
||||
rid.hwndTarget = real_window.0;
|
||||
|
||||
winuser::RegisterRawInputDevices(&rid, 1, mem::size_of::<winuser::RAWINPUTDEVICE>() as u32);
|
||||
}
|
||||
|
||||
// Register for touch events if applicable
|
||||
{
|
||||
let digitizer = winuser::GetSystemMetrics( winuser::SM_DIGITIZER ) as u32;
|
||||
if digitizer & winuser::NID_READY != 0 {
|
||||
winuser::RegisterTouchWindow( real_window.0, winuser::TWF_WANTPALM );
|
||||
}
|
||||
}
|
||||
|
||||
// Creating a mutex to track the current window state
|
||||
let window_state = Arc::new(Mutex::new(events_loop::WindowState {
|
||||
cursor: winuser::IDC_ARROW, // use arrow by default
|
||||
cursor_state: CursorState::Normal,
|
||||
attributes: window.clone(),
|
||||
mouse_in_window: false,
|
||||
saved_window_info: None,
|
||||
}));
|
||||
|
||||
// making the window transparent
|
||||
if window.transparent {
|
||||
let bb = dwmapi::DWM_BLURBEHIND {
|
||||
dwFlags: 0x1, // FIXME: DWM_BB_ENABLE;
|
||||
fEnable: 1,
|
||||
hRgnBlur: ptr::null_mut(),
|
||||
fTransitionOnMaximized: 0,
|
||||
};
|
||||
|
||||
dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb);
|
||||
}
|
||||
|
||||
let win = Window {
|
||||
window: real_window,
|
||||
window_state: window_state,
|
||||
events_loop_proxy
|
||||
};
|
||||
|
||||
win.set_maximized(window.maximized);
|
||||
if let Some(_) = window.fullscreen {
|
||||
win.set_fullscreen(window.fullscreen);
|
||||
force_window_active(win.window.0);
|
||||
}
|
||||
|
||||
inserter.insert(win.window.0, win.window_state.clone());
|
||||
|
||||
Ok(win)
|
||||
}
|
||||
|
||||
unsafe fn register_window_class() -> Vec<u16> {
|
||||
let class_name = OsStr::new("Window Class").encode_wide().chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let class = winuser::WNDCLASSEXW {
|
||||
cbSize: mem::size_of::<winuser::WNDCLASSEXW>() as UINT,
|
||||
style: winuser::CS_HREDRAW | winuser::CS_VREDRAW | winuser::CS_OWNDC,
|
||||
lpfnWndProc: Some(events_loop::callback),
|
||||
cbClsExtra: 0,
|
||||
cbWndExtra: 0,
|
||||
hInstance: libloaderapi::GetModuleHandleW(ptr::null()),
|
||||
hIcon: ptr::null_mut(),
|
||||
hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly
|
||||
hbrBackground: ptr::null_mut(),
|
||||
lpszMenuName: ptr::null(),
|
||||
lpszClassName: class_name.as_ptr(),
|
||||
hIconSm: ptr::null_mut(),
|
||||
};
|
||||
|
||||
// We ignore errors because registering the same window class twice would trigger
|
||||
// an error, and because errors here are detected during CreateWindowEx anyway.
|
||||
// Also since there is no weird element in the struct, there is no reason for this
|
||||
// call to fail.
|
||||
winuser::RegisterClassExW(&class);
|
||||
|
||||
class_name
|
||||
}
|
||||
|
||||
|
||||
struct ComInitialized(*mut ());
|
||||
impl Drop for ComInitialized {
|
||||
fn drop(&mut self) {
|
||||
unsafe { combaseapi::CoUninitialize() };
|
||||
}
|
||||
}
|
||||
|
||||
thread_local!{
|
||||
static COM_INITIALIZED: ComInitialized = {
|
||||
unsafe {
|
||||
combaseapi::CoInitializeEx(ptr::null_mut(), COINIT_MULTITHREADED);
|
||||
ComInitialized(ptr::null_mut())
|
||||
}
|
||||
};
|
||||
|
||||
static TASKBAR_LIST: Cell<*mut taskbar::ITaskbarList2> = Cell::new(ptr::null_mut());
|
||||
}
|
||||
|
||||
pub fn com_initialized() {
|
||||
COM_INITIALIZED.with(|_| {});
|
||||
}
|
||||
|
||||
// TODO: remove these when they get added to winapi
|
||||
// https://github.com/retep998/winapi-rs/pull/592
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(dead_code)]
|
||||
mod taskbar {
|
||||
use super::{IUnknown,IUnknownVtbl,HRESULT, HWND,BOOL};
|
||||
DEFINE_GUID!{CLSID_TaskbarList,
|
||||
0x56fdf344, 0xfd6d, 0x11d0, 0x95, 0x8a, 0x00, 0x60, 0x97, 0xc9, 0xa0, 0x90}
|
||||
|
||||
RIDL!(#[uuid(0x56fdf342, 0xfd6d, 0x11d0, 0x95, 0x8a, 0x00, 0x60, 0x97, 0xc9, 0xa0, 0x90)]
|
||||
interface ITaskbarList(ITaskbarListVtbl): IUnknown(IUnknownVtbl) {
|
||||
fn HrInit() -> HRESULT,
|
||||
fn AddTab(
|
||||
hwnd: HWND,
|
||||
) -> HRESULT,
|
||||
fn DeleteTab(
|
||||
hwnd: HWND,
|
||||
) -> HRESULT,
|
||||
fn ActivateTab(
|
||||
hwnd: HWND,
|
||||
) -> HRESULT,
|
||||
fn SetActiveAlt(
|
||||
hwnd: HWND,
|
||||
) -> HRESULT,
|
||||
});
|
||||
|
||||
RIDL!(#[uuid(0x602d4995, 0xb13a, 0x429b, 0xa6, 0x6e, 0x19, 0x35, 0xe4, 0x4f, 0x43, 0x17)]
|
||||
interface ITaskbarList2(ITaskbarList2Vtbl): ITaskbarList(ITaskbarListVtbl) {
|
||||
fn MarkFullscreenWindow(
|
||||
hwnd: HWND,
|
||||
fFullscreen: BOOL,
|
||||
) -> HRESULT,
|
||||
});
|
||||
}
|
||||
|
||||
// Reference Implementation:
|
||||
// https://github.com/chromium/chromium/blob/f18e79d901f56154f80eea1e2218544285e62623/ui/views/win/fullscreen_handler.cc
|
||||
//
|
||||
// As per MSDN marking the window as fullscreen should ensure that the
|
||||
// taskbar is moved to the bottom of the Z-order when the fullscreen window
|
||||
// is activated. If the window is not fullscreen, the Shell falls back to
|
||||
// heuristics to determine how the window should be treated, which means
|
||||
// that it could still consider the window as fullscreen. :(
|
||||
unsafe fn mark_fullscreen(handle: HWND, fullscreen: bool) {
|
||||
com_initialized();
|
||||
|
||||
TASKBAR_LIST.with(|task_bar_list_ptr| {
|
||||
let mut task_bar_list = task_bar_list_ptr.get();
|
||||
|
||||
if task_bar_list == ptr::null_mut() {
|
||||
use winapi::shared::winerror::S_OK;
|
||||
use winapi::Interface;
|
||||
|
||||
let hr = combaseapi::CoCreateInstance(
|
||||
&taskbar::CLSID_TaskbarList,
|
||||
ptr::null_mut(),
|
||||
combaseapi::CLSCTX_ALL,
|
||||
&taskbar::ITaskbarList2::uuidof(),
|
||||
&mut task_bar_list as *mut _ as *mut _,
|
||||
);
|
||||
|
||||
if hr != S_OK || (*task_bar_list).HrInit() != S_OK {
|
||||
// In some old windows, the taskbar object could not be created, we just ignore it
|
||||
return;
|
||||
}
|
||||
task_bar_list_ptr.set(task_bar_list)
|
||||
}
|
||||
|
||||
task_bar_list = task_bar_list_ptr.get();
|
||||
(*task_bar_list).MarkFullscreenWindow(handle, if fullscreen { 1 } else { 0 });
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn force_window_active(handle: HWND) {
|
||||
// In some situation, calling SetForegroundWindow could not bring up the window,
|
||||
// This is a little hack which can "steal" the foreground window permission
|
||||
// We only call this function in the window creation, so it should be fine.
|
||||
// See : https://stackoverflow.com/questions/10740346/setforegroundwindow-only-working-while-visual-studio-is-open
|
||||
let alt_sc = winuser::MapVirtualKeyW(winuser::VK_MENU as _, winuser::MAPVK_VK_TO_VSC);
|
||||
|
||||
let mut inputs: [winuser::INPUT; 2] = mem::zeroed();
|
||||
inputs[0].type_ = winuser::INPUT_KEYBOARD;
|
||||
inputs[0].u.ki_mut().wVk = winuser::VK_LMENU as _;
|
||||
inputs[0].u.ki_mut().wScan = alt_sc as _;
|
||||
inputs[0].u.ki_mut().dwFlags = winuser::KEYEVENTF_EXTENDEDKEY;
|
||||
|
||||
inputs[1].type_ = winuser::INPUT_KEYBOARD;
|
||||
inputs[1].u.ki_mut().wVk = winuser::VK_LMENU as _;
|
||||
inputs[1].u.ki_mut().wScan = alt_sc as _;
|
||||
inputs[1].u.ki_mut().dwFlags = winuser::KEYEVENTF_EXTENDEDKEY | winuser::KEYEVENTF_KEYUP;
|
||||
|
||||
// Simulate a key press and release
|
||||
winuser::SendInput(
|
||||
inputs.len() as _,
|
||||
inputs.as_mut_ptr(),
|
||||
mem::size_of::<winuser::INPUT>() as _,
|
||||
);
|
||||
|
||||
winuser::SetForegroundWindow(handle);
|
||||
}
|
||||
519
src/platform_impl/android/mod.rs
Normal file
519
src/platform_impl/android/mod.rs
Normal file
@@ -0,0 +1,519 @@
|
||||
#![cfg(target_os = "android")]
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error, event,
|
||||
event_loop::{self, ControlFlow},
|
||||
monitor, window,
|
||||
};
|
||||
use ndk::{
|
||||
configuration::Configuration,
|
||||
event::{InputEvent, MotionAction},
|
||||
looper::{ForeignLooper, Poll, ThreadLooper},
|
||||
};
|
||||
use ndk_glue::{Event, Rect};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref CONFIG: RwLock<Configuration> = RwLock::new(Configuration::new());
|
||||
}
|
||||
|
||||
enum EventSource {
|
||||
Callback,
|
||||
InputQueue,
|
||||
User,
|
||||
}
|
||||
|
||||
fn poll(poll: Poll) -> Option<EventSource> {
|
||||
match poll {
|
||||
Poll::Event { data, .. } => match data as usize {
|
||||
0 => Some(EventSource::Callback),
|
||||
1 => Some(EventSource::InputQueue),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Poll::Timeout => None,
|
||||
Poll::Wake => Some(EventSource::User),
|
||||
Poll::Callback => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
window_target: event_loop::EventLoopWindowTarget<T>,
|
||||
user_queue: Arc<Mutex<VecDeque<T>>>,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
window_target: event_loop::EventLoopWindowTarget {
|
||||
p: EventLoopWindowTarget {
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
user_queue: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run<F>(self, mut event_handler: F) -> !
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
let mut cf = ControlFlow::default();
|
||||
let mut first_event = None;
|
||||
let mut start_cause = event::StartCause::Init;
|
||||
let looper = ThreadLooper::for_thread().unwrap();
|
||||
let mut running = false;
|
||||
|
||||
loop {
|
||||
event_handler(
|
||||
event::Event::NewEvents(start_cause),
|
||||
self.window_target(),
|
||||
&mut cf,
|
||||
);
|
||||
|
||||
let mut redraw = false;
|
||||
let mut resized = false;
|
||||
|
||||
match first_event.take() {
|
||||
Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() {
|
||||
Event::WindowCreated => {
|
||||
event_handler(event::Event::Resumed, self.window_target(), &mut cf);
|
||||
}
|
||||
Event::WindowResized => resized = true,
|
||||
Event::WindowRedrawNeeded => redraw = true,
|
||||
Event::WindowDestroyed => {
|
||||
event_handler(event::Event::Suspended, self.window_target(), &mut cf);
|
||||
}
|
||||
Event::Pause => running = false,
|
||||
Event::Resume => running = true,
|
||||
Event::ConfigChanged => {
|
||||
let am = ndk_glue::native_activity().asset_manager();
|
||||
let config = Configuration::from_asset_manager(&am);
|
||||
let old_scale_factor = MonitorHandle.scale_factor();
|
||||
*CONFIG.write().unwrap() = config;
|
||||
let scale_factor = MonitorHandle.scale_factor();
|
||||
if (scale_factor - old_scale_factor).abs() < f64::EPSILON {
|
||||
let mut size = MonitorHandle.size();
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::ScaleFactorChanged {
|
||||
new_inner_size: &mut size,
|
||||
scale_factor,
|
||||
},
|
||||
};
|
||||
event_handler(event, self.window_target(), &mut cf);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(EventSource::InputQueue) => {
|
||||
if let Some(input_queue) = ndk_glue::input_queue().as_ref() {
|
||||
while let Some(event) = input_queue.get_event() {
|
||||
println!("event {:?}", event);
|
||||
if let Some(event) = input_queue.pre_dispatch(event) {
|
||||
let window_id = window::WindowId(WindowId);
|
||||
let device_id = event::DeviceId(DeviceId);
|
||||
match &event {
|
||||
InputEvent::MotionEvent(motion_event) => {
|
||||
let phase = match motion_event.action() {
|
||||
MotionAction::Down => Some(event::TouchPhase::Started),
|
||||
MotionAction::Up => Some(event::TouchPhase::Ended),
|
||||
MotionAction::Move => Some(event::TouchPhase::Moved),
|
||||
MotionAction::Cancel => {
|
||||
Some(event::TouchPhase::Cancelled)
|
||||
}
|
||||
_ => None, // TODO mouse events
|
||||
};
|
||||
let pointer = motion_event.pointer_at_index(0);
|
||||
let location = PhysicalPosition {
|
||||
x: pointer.x() as _,
|
||||
y: pointer.y() as _,
|
||||
};
|
||||
|
||||
if let Some(phase) = phase {
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id,
|
||||
event: event::WindowEvent::Touch(event::Touch {
|
||||
device_id,
|
||||
phase,
|
||||
location,
|
||||
id: 0,
|
||||
force: None,
|
||||
}),
|
||||
};
|
||||
event_handler(event, self.window_target(), &mut cf);
|
||||
}
|
||||
}
|
||||
InputEvent::KeyEvent(_) => {} // TODO
|
||||
};
|
||||
input_queue.finish_event(event, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(EventSource::User) => {
|
||||
let mut user_queue = self.user_queue.lock().unwrap();
|
||||
while let Some(event) = user_queue.pop_front() {
|
||||
event_handler(
|
||||
event::Event::UserEvent(event),
|
||||
self.window_target(),
|
||||
&mut cf,
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
event_handler(
|
||||
event::Event::MainEventsCleared,
|
||||
self.window_target(),
|
||||
&mut cf,
|
||||
);
|
||||
|
||||
if resized && running {
|
||||
let size = MonitorHandle.size();
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::Resized(size),
|
||||
};
|
||||
event_handler(event, self.window_target(), &mut cf);
|
||||
}
|
||||
|
||||
if redraw && running {
|
||||
let event = event::Event::RedrawRequested(window::WindowId(WindowId));
|
||||
event_handler(event, self.window_target(), &mut cf);
|
||||
}
|
||||
|
||||
event_handler(
|
||||
event::Event::RedrawEventsCleared,
|
||||
self.window_target(),
|
||||
&mut cf,
|
||||
);
|
||||
|
||||
match cf {
|
||||
ControlFlow::Exit => panic!(),
|
||||
ControlFlow::Poll => {
|
||||
start_cause = event::StartCause::Poll;
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
first_event = poll(looper.poll_all().unwrap());
|
||||
start_cause = event::StartCause::WaitCancelled {
|
||||
start: Instant::now(),
|
||||
requested_resume: None,
|
||||
}
|
||||
}
|
||||
ControlFlow::WaitUntil(instant) => {
|
||||
let start = Instant::now();
|
||||
let duration = if instant <= start {
|
||||
Duration::default()
|
||||
} else {
|
||||
instant - start
|
||||
};
|
||||
first_event = poll(looper.poll_all_timeout(duration).unwrap());
|
||||
start_cause = if first_event.is_some() {
|
||||
event::StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(instant),
|
||||
}
|
||||
} else {
|
||||
event::StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume: instant,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget<T> {
|
||||
&self.window_target
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut v = VecDeque::with_capacity(1);
|
||||
v.push_back(self.primary_monitor());
|
||||
v
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy {
|
||||
queue: self.user_queue.clone(),
|
||||
looper: ForeignLooper::for_thread().expect("called from event loop thread"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
queue: Arc<Mutex<VecDeque<T>>>,
|
||||
looper: ForeignLooper,
|
||||
}
|
||||
|
||||
impl<T> EventLoopProxy<T> {
|
||||
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
|
||||
self.queue.lock().unwrap().push_back(event);
|
||||
self.looper.wake();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
EventLoopProxy {
|
||||
queue: self.queue.clone(),
|
||||
looper: self.looper.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct WindowId;
|
||||
|
||||
impl WindowId {
|
||||
pub fn dummy() -> Self {
|
||||
WindowId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes;
|
||||
|
||||
pub struct Window;
|
||||
|
||||
impl Window {
|
||||
pub fn new<T: 'static>(
|
||||
_el: &EventLoopWindowTarget<T>,
|
||||
_window_attrs: window::WindowAttributes,
|
||||
_: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Self, error::OsError> {
|
||||
// FIXME this ignores requested window attributes
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut v = VecDeque::with_capacity(1);
|
||||
v.push_back(MonitorHandle);
|
||||
v
|
||||
}
|
||||
|
||||
pub fn current_monitor(&self) -> monitor::MonitorHandle {
|
||||
monitor::MonitorHandle {
|
||||
inner: MonitorHandle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
MonitorHandle.scale_factor()
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
|
||||
Err(error::NotSupportedError::new())
|
||||
}
|
||||
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
|
||||
Err(error::NotSupportedError::new())
|
||||
}
|
||||
|
||||
pub fn set_outer_position(&self, _position: Position) {
|
||||
// no effect
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
self.outer_size()
|
||||
}
|
||||
|
||||
pub fn set_inner_size(&self, _size: Size) {
|
||||
panic!("Cannot set window size on Android");
|
||||
}
|
||||
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
MonitorHandle.size()
|
||||
}
|
||||
|
||||
pub fn set_min_inner_size(&self, _: Option<Size>) {}
|
||||
|
||||
pub fn set_max_inner_size(&self, _: Option<Size>) {}
|
||||
|
||||
pub fn set_title(&self, _title: &str) {}
|
||||
|
||||
pub fn set_visible(&self, _visibility: bool) {}
|
||||
|
||||
pub fn set_resizable(&self, _resizeable: bool) {}
|
||||
|
||||
pub fn set_minimized(&self, _minimized: bool) {}
|
||||
|
||||
pub fn set_maximized(&self, _maximized: bool) {}
|
||||
|
||||
pub fn set_fullscreen(&self, _monitor: Option<window::Fullscreen>) {
|
||||
panic!("Cannot set fullscreen on Android");
|
||||
}
|
||||
|
||||
pub fn fullscreen(&self) -> Option<window::Fullscreen> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_decorations(&self, _decorations: bool) {}
|
||||
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {}
|
||||
|
||||
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
|
||||
|
||||
pub fn set_ime_position(&self, _position: Position) {}
|
||||
|
||||
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
|
||||
|
||||
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&self, _: bool) {}
|
||||
|
||||
pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
|
||||
let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() {
|
||||
unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ }
|
||||
} else {
|
||||
panic!("native window null");
|
||||
};
|
||||
let mut handle = raw_window_handle::android::AndroidHandle::empty();
|
||||
handle.a_native_window = a_native_window;
|
||||
raw_window_handle::RawWindowHandle::Android(handle)
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Configuration {
|
||||
CONFIG.read().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn content_rect(&self) -> Rect {
|
||||
ndk_glue::content_rect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct OsError;
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
impl Display for OsError {
|
||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(fmt, "Android OS Error")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct MonitorHandle;
|
||||
|
||||
impl MonitorHandle {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
Some("Android Device".to_owned())
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
if let Some(native_window) = ndk_glue::native_window().as_ref() {
|
||||
let width = native_window.width() as _;
|
||||
let height = native_window.height() as _;
|
||||
PhysicalSize::new(width, height)
|
||||
} else {
|
||||
PhysicalSize::new(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
let config = CONFIG.read().unwrap();
|
||||
config
|
||||
.density()
|
||||
.map(|dpi| dpi as f64 / 160.0)
|
||||
.unwrap_or(1.0)
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = monitor::VideoMode> {
|
||||
let size = self.size().into();
|
||||
let mut v = Vec::new();
|
||||
// FIXME this is not the real refresh rate
|
||||
// (it is guarunteed to support 32 bit color though)
|
||||
v.push(monitor::VideoMode {
|
||||
video_mode: VideoMode {
|
||||
size,
|
||||
bit_depth: 32,
|
||||
refresh_rate: 60,
|
||||
monitor: self.clone(),
|
||||
},
|
||||
});
|
||||
v.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct VideoMode {
|
||||
size: (u32, u32),
|
||||
bit_depth: u16,
|
||||
refresh_rate: u16,
|
||||
monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size.into()
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> u16 {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.refresh_rate
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> monitor::MonitorHandle {
|
||||
monitor::MonitorHandle {
|
||||
inner: self.monitor.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
1046
src/platform_impl/ios/app_state.rs
Normal file
1046
src/platform_impl/ios/app_state.rs
Normal file
File diff suppressed because it is too large
Load Diff
338
src/platform_impl/ios/event_loop.rs
Normal file
338
src/platform_impl/ios/event_loop.rs
Normal file
@@ -0,0 +1,338 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
ffi::c_void,
|
||||
fmt::{self, Debug},
|
||||
marker::PhantomData,
|
||||
mem, ptr,
|
||||
sync::mpsc::{self, Receiver, Sender},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::Event,
|
||||
event_loop::{
|
||||
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
|
||||
},
|
||||
platform::ios::Idiom,
|
||||
};
|
||||
|
||||
use crate::platform_impl::platform::{
|
||||
app_state,
|
||||
ffi::{
|
||||
id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes,
|
||||
kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease,
|
||||
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
|
||||
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext,
|
||||
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
|
||||
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSString, UIApplicationMain, UIUserInterfaceIdiom,
|
||||
},
|
||||
monitor, view, MonitorHandle,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventWrapper {
|
||||
StaticEvent(Event<'static, Never>),
|
||||
EventProxy(EventProxy),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum EventProxy {
|
||||
DpiChangedProxy {
|
||||
window_id: id,
|
||||
suggested_size: LogicalSize<f64>,
|
||||
scale_factor: f64,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
receiver: Receiver<T>,
|
||||
sender_to_clone: Sender<T>,
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
window_target: RootEventLoopWindowTarget<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> EventLoop<T> {
|
||||
static mut SINGLETON_INIT: bool = false;
|
||||
unsafe {
|
||||
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
|
||||
assert!(
|
||||
!SINGLETON_INIT,
|
||||
"Only one `EventLoop` is supported on iOS. \
|
||||
`EventLoopProxy` might be helpful"
|
||||
);
|
||||
SINGLETON_INIT = true;
|
||||
view::create_delegate_class();
|
||||
}
|
||||
|
||||
let (sender_to_clone, receiver) = mpsc::channel();
|
||||
|
||||
// this line sets up the main run loop before `UIApplicationMain`
|
||||
setup_control_flow_observers();
|
||||
|
||||
EventLoop {
|
||||
window_target: RootEventLoopWindowTarget {
|
||||
p: EventLoopWindowTarget {
|
||||
receiver,
|
||||
sender_to_clone,
|
||||
},
|
||||
_marker: PhantomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run<F>(self, event_handler: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
unsafe {
|
||||
let application: *mut c_void = msg_send![class!(UIApplication), sharedApplication];
|
||||
assert_eq!(
|
||||
application,
|
||||
ptr::null_mut(),
|
||||
"\
|
||||
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
|
||||
Note: `EventLoop::run` calls `UIApplicationMain` on iOS"
|
||||
);
|
||||
app_state::will_launch(Box::new(EventLoopHandler {
|
||||
f: event_handler,
|
||||
event_loop: self.window_target,
|
||||
}));
|
||||
|
||||
UIApplicationMain(
|
||||
0,
|
||||
ptr::null(),
|
||||
nil,
|
||||
NSString::alloc(nil).init_str("AppDelegate"),
|
||||
);
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
// guaranteed to be on main thread
|
||||
unsafe { monitor::uiscreens() }
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
// guaranteed to be on main thread
|
||||
unsafe { monitor::main_uiscreen() }
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
|
||||
&self.window_target
|
||||
}
|
||||
}
|
||||
|
||||
// EventLoopExtIOS
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn idiom(&self) -> Idiom {
|
||||
// guaranteed to be on main thread
|
||||
unsafe { self::get_idiom() }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopProxy<T> {
|
||||
sender: Sender<T>,
|
||||
source: CFRunLoopSourceRef,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send 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(_: *mut 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();
|
||||
// we want all the members of context to be zero/null, except one
|
||||
let mut context: CFRunLoopSourceContext = mem::zeroed();
|
||||
context.perform = Some(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,
|
||||
) {
|
||||
unsafe {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
|
||||
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
|
||||
_ => 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 MainEventsCleared 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 `MainEventsCleared`.
|
||||
//
|
||||
// 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,
|
||||
) {
|
||||
unsafe {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(),
|
||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||
_ => 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,
|
||||
) {
|
||||
unsafe {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
|
||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let main_loop = CFRunLoopGetMain();
|
||||
|
||||
let begin_observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopEntry | 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);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Never {}
|
||||
|
||||
pub trait EventHandler: Debug {
|
||||
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow);
|
||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
|
||||
}
|
||||
|
||||
struct EventLoopHandler<F, T: 'static> {
|
||||
f: F,
|
||||
event_loop: RootEventLoopWindowTarget<T>,
|
||||
}
|
||||
|
||||
impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("EventLoopHandler")
|
||||
.field("event_loop", &self.event_loop)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T> EventHandler for EventLoopHandler<F, T>
|
||||
where
|
||||
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
T: 'static,
|
||||
{
|
||||
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
|
||||
(self.f)(
|
||||
event.map_nonuser_event().unwrap(),
|
||||
&self.event_loop,
|
||||
control_flow,
|
||||
);
|
||||
}
|
||||
|
||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
|
||||
for event in self.event_loop.p.receiver.try_iter() {
|
||||
(self.f)(Event::UserEvent(event), &self.event_loop, control_flow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// must be called on main thread
|
||||
pub unsafe fn get_idiom() -> Idiom {
|
||||
let device: id = msg_send![class!(UIDevice), currentDevice];
|
||||
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
|
||||
raw_idiom.into()
|
||||
}
|
||||
390
src/platform_impl/ios/ffi.rs
Normal file
390
src/platform_impl/ios/ffi.rs
Normal file
@@ -0,0 +1,390 @@
|
||||
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||
|
||||
use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
|
||||
|
||||
use objc::{runtime::Object, Encode, Encoding};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
platform::ios::{Idiom, ScreenEdge, ValidOrientations},
|
||||
};
|
||||
|
||||
pub type id = *mut Object;
|
||||
pub const nil: id = 0 as id;
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
pub type CGFloat = f32;
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub type CGFloat = f64;
|
||||
|
||||
pub type NSInteger = isize;
|
||||
pub type NSUInteger = usize;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NSOperatingSystemVersion {
|
||||
pub major: NSInteger,
|
||||
pub minor: NSInteger,
|
||||
pub patch: NSInteger,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGPoint {
|
||||
pub x: CGFloat,
|
||||
pub y: CGFloat,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGSize {
|
||||
pub width: CGFloat,
|
||||
pub height: CGFloat,
|
||||
}
|
||||
|
||||
impl CGSize {
|
||||
pub fn new(size: LogicalSize<f64>) -> CGSize {
|
||||
CGSize {
|
||||
width: size.width as _,
|
||||
height: size.height as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGRect {
|
||||
pub origin: CGPoint,
|
||||
pub size: CGSize,
|
||||
}
|
||||
|
||||
impl CGRect {
|
||||
pub fn new(origin: CGPoint, size: CGSize) -> CGRect {
|
||||
CGRect { origin, size }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for CGRect {
|
||||
fn encode() -> Encoding {
|
||||
unsafe {
|
||||
if cfg!(target_pointer_width = "32") {
|
||||
Encoding::from_str("{CGRect={CGPoint=ff}{CGSize=ff}}")
|
||||
} else if cfg!(target_pointer_width = "64") {
|
||||
Encoding::from_str("{CGRect={CGPoint=dd}{CGSize=dd}}")
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UITouchPhase {
|
||||
Began = 0,
|
||||
Moved,
|
||||
Stationary,
|
||||
Ended,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UIForceTouchCapability {
|
||||
Unknown = 0,
|
||||
Unavailable,
|
||||
Available,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UITouchType {
|
||||
Direct = 0,
|
||||
Indirect,
|
||||
Pencil,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UIEdgeInsets {
|
||||
pub top: CGFloat,
|
||||
pub left: CGFloat,
|
||||
pub bottom: CGFloat,
|
||||
pub right: CGFloat,
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIUserInterfaceIdiom(NSInteger);
|
||||
|
||||
unsafe impl Encode for UIUserInterfaceIdiom {
|
||||
fn encode() -> Encoding {
|
||||
NSInteger::encode()
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
impl From<Idiom> for UIUserInterfaceIdiom {
|
||||
fn from(idiom: Idiom) -> UIUserInterfaceIdiom {
|
||||
match idiom {
|
||||
Idiom::Unspecified => UIUserInterfaceIdiom::Unspecified,
|
||||
Idiom::Phone => UIUserInterfaceIdiom::Phone,
|
||||
Idiom::Pad => UIUserInterfaceIdiom::Pad,
|
||||
Idiom::TV => UIUserInterfaceIdiom::TV,
|
||||
Idiom::CarPlay => UIUserInterfaceIdiom::CarPlay,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Idiom> for UIUserInterfaceIdiom {
|
||||
fn into(self) -> Idiom {
|
||||
match self {
|
||||
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
|
||||
UIUserInterfaceIdiom::Phone => Idiom::Phone,
|
||||
UIUserInterfaceIdiom::Pad => Idiom::Pad,
|
||||
UIUserInterfaceIdiom::TV => Idiom::TV,
|
||||
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UIInterfaceOrientationMask(NSUInteger);
|
||||
|
||||
unsafe impl Encode for UIInterfaceOrientationMask {
|
||||
fn encode() -> Encoding {
|
||||
NSUInteger::encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl UIInterfaceOrientationMask {
|
||||
pub const Portrait: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 1);
|
||||
pub const PortraitUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 2);
|
||||
pub const LandscapeLeft: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 4);
|
||||
pub const LandscapeRight: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 3);
|
||||
pub const Landscape: UIInterfaceOrientationMask =
|
||||
UIInterfaceOrientationMask(Self::LandscapeLeft.0 | Self::LandscapeRight.0);
|
||||
pub const AllButUpsideDown: UIInterfaceOrientationMask =
|
||||
UIInterfaceOrientationMask(Self::Landscape.0 | Self::Portrait.0);
|
||||
pub const All: UIInterfaceOrientationMask =
|
||||
UIInterfaceOrientationMask(Self::AllButUpsideDown.0 | Self::PortraitUpsideDown.0);
|
||||
}
|
||||
|
||||
impl BitOr for UIInterfaceOrientationMask {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
UIInterfaceOrientationMask(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl UIInterfaceOrientationMask {
|
||||
pub fn from_valid_orientations_idiom(
|
||||
valid_orientations: ValidOrientations,
|
||||
idiom: Idiom,
|
||||
) -> UIInterfaceOrientationMask {
|
||||
match (valid_orientations, idiom) {
|
||||
(ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => {
|
||||
UIInterfaceOrientationMask::AllButUpsideDown
|
||||
}
|
||||
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
|
||||
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
|
||||
(ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait,
|
||||
(ValidOrientations::Portrait, _) => {
|
||||
UIInterfaceOrientationMask::Portrait
|
||||
| UIInterfaceOrientationMask::PortraitUpsideDown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIRectEdge(NSUInteger);
|
||||
|
||||
unsafe impl Encode for UIRectEdge {
|
||||
fn encode() -> Encoding {
|
||||
NSUInteger::encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScreenEdge> for UIRectEdge {
|
||||
fn from(screen_edge: ScreenEdge) -> UIRectEdge {
|
||||
assert_eq!(
|
||||
screen_edge.bits() & !ScreenEdge::ALL.bits(),
|
||||
0,
|
||||
"invalid `ScreenEdge`"
|
||||
);
|
||||
UIRectEdge(screen_edge.bits().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ScreenEdge> for UIRectEdge {
|
||||
fn into(self) -> ScreenEdge {
|
||||
let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`");
|
||||
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIScreenOverscanCompensation(NSInteger);
|
||||
|
||||
unsafe impl Encode for UIScreenOverscanCompensation {
|
||||
fn encode() -> Encoding {
|
||||
NSInteger::encode()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl UIScreenOverscanCompensation {
|
||||
pub const Scale: UIScreenOverscanCompensation = UIScreenOverscanCompensation(0);
|
||||
pub const InsetBounds: UIScreenOverscanCompensation = UIScreenOverscanCompensation(1);
|
||||
pub const None: UIScreenOverscanCompensation = UIScreenOverscanCompensation(2);
|
||||
}
|
||||
|
||||
#[link(name = "UIKit", kind = "framework")]
|
||||
#[link(name = "CoreFoundation", kind = "framework")]
|
||||
extern "C" {
|
||||
pub static kCFRunLoopDefaultMode: CFRunLoopMode;
|
||||
pub static kCFRunLoopCommonModes: CFRunLoopMode;
|
||||
|
||||
pub fn UIApplicationMain(
|
||||
argc: c_int,
|
||||
argv: *const c_char,
|
||||
principalClassName: id,
|
||||
delegateClassName: id,
|
||||
) -> c_int;
|
||||
|
||||
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
|
||||
pub fn CFRunLoopWakeUp(rl: CFRunLoopRef);
|
||||
|
||||
pub fn CFRunLoopObserverCreate(
|
||||
allocator: CFAllocatorRef,
|
||||
activities: CFOptionFlags,
|
||||
repeats: Boolean,
|
||||
order: CFIndex,
|
||||
callout: CFRunLoopObserverCallBack,
|
||||
context: *mut CFRunLoopObserverContext,
|
||||
) -> CFRunLoopObserverRef;
|
||||
pub fn CFRunLoopAddObserver(
|
||||
rl: CFRunLoopRef,
|
||||
observer: CFRunLoopObserverRef,
|
||||
mode: CFRunLoopMode,
|
||||
);
|
||||
|
||||
pub fn CFRunLoopTimerCreate(
|
||||
allocator: CFAllocatorRef,
|
||||
fireDate: CFAbsoluteTime,
|
||||
interval: CFTimeInterval,
|
||||
flags: CFOptionFlags,
|
||||
order: CFIndex,
|
||||
callout: CFRunLoopTimerCallBack,
|
||||
context: *mut CFRunLoopTimerContext,
|
||||
) -> CFRunLoopTimerRef;
|
||||
pub fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFRunLoopMode);
|
||||
pub fn CFRunLoopTimerSetNextFireDate(timer: CFRunLoopTimerRef, fireDate: CFAbsoluteTime);
|
||||
pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef);
|
||||
|
||||
pub fn CFRunLoopSourceCreate(
|
||||
allocator: CFAllocatorRef,
|
||||
order: CFIndex,
|
||||
context: *mut CFRunLoopSourceContext,
|
||||
) -> CFRunLoopSourceRef;
|
||||
pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode);
|
||||
pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef);
|
||||
pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef);
|
||||
|
||||
pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime;
|
||||
pub fn CFRelease(cftype: *const c_void);
|
||||
}
|
||||
|
||||
pub type Boolean = u8;
|
||||
pub enum CFAllocator {}
|
||||
pub type CFAllocatorRef = *mut CFAllocator;
|
||||
pub enum CFRunLoop {}
|
||||
pub type CFRunLoopRef = *mut CFRunLoop;
|
||||
pub type CFRunLoopMode = CFStringRef;
|
||||
pub enum CFRunLoopObserver {}
|
||||
pub type CFRunLoopObserverRef = *mut CFRunLoopObserver;
|
||||
pub enum CFRunLoopTimer {}
|
||||
pub type CFRunLoopTimerRef = *mut CFRunLoopTimer;
|
||||
pub enum CFRunLoopSource {}
|
||||
pub type CFRunLoopSourceRef = *mut CFRunLoopSource;
|
||||
pub enum CFString {}
|
||||
pub type CFStringRef = *const CFString;
|
||||
|
||||
pub type CFHashCode = c_ulong;
|
||||
pub type CFIndex = c_long;
|
||||
pub type CFOptionFlags = c_ulong;
|
||||
pub type CFRunLoopActivity = CFOptionFlags;
|
||||
|
||||
pub type CFAbsoluteTime = CFTimeInterval;
|
||||
pub type CFTimeInterval = f64;
|
||||
|
||||
pub const kCFRunLoopEntry: CFRunLoopActivity = 0;
|
||||
pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5;
|
||||
pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6;
|
||||
pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7;
|
||||
|
||||
pub type CFRunLoopObserverCallBack =
|
||||
extern "C" fn(observer: CFRunLoopObserverRef, activity: CFRunLoopActivity, info: *mut c_void);
|
||||
pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void);
|
||||
|
||||
pub enum CFRunLoopObserverContext {}
|
||||
pub enum CFRunLoopTimerContext {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct CFRunLoopSourceContext {
|
||||
pub version: CFIndex,
|
||||
pub info: *mut c_void,
|
||||
pub retain: Option<extern "C" fn(*const c_void) -> *const c_void>,
|
||||
pub release: Option<extern "C" fn(*const c_void)>,
|
||||
pub copyDescription: Option<extern "C" fn(*const c_void) -> CFStringRef>,
|
||||
pub equal: Option<extern "C" fn(*const c_void, *const c_void) -> Boolean>,
|
||||
pub hash: Option<extern "C" fn(*const c_void) -> CFHashCode>,
|
||||
pub schedule: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
|
||||
pub cancel: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
|
||||
pub perform: Option<extern "C" fn(*mut c_void)>,
|
||||
}
|
||||
|
||||
pub trait NSString: Sized {
|
||||
unsafe fn alloc(_: Self) -> id {
|
||||
msg_send![class!(NSString), alloc]
|
||||
}
|
||||
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id;
|
||||
unsafe fn stringByAppendingString_(self, other: id) -> id;
|
||||
unsafe fn init_str(self, string: &str) -> Self;
|
||||
unsafe fn UTF8String(self) -> *const c_char;
|
||||
}
|
||||
|
||||
impl NSString for id {
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
|
||||
msg_send![self, initWithUTF8String: c_string as id]
|
||||
}
|
||||
|
||||
unsafe fn stringByAppendingString_(self, other: id) -> id {
|
||||
msg_send![self, stringByAppendingString: other]
|
||||
}
|
||||
|
||||
unsafe fn init_str(self, string: &str) -> id {
|
||||
let cstring = CString::new(string).unwrap();
|
||||
self.initWithUTF8String_(cstring.as_ptr())
|
||||
}
|
||||
|
||||
unsafe fn UTF8String(self) -> *const c_char {
|
||||
msg_send![self, UTF8String]
|
||||
}
|
||||
}
|
||||
113
src/platform_impl/ios/mod.rs
Normal file
113
src/platform_impl/ios/mod.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
//! iOS support
|
||||
//!
|
||||
//! # 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 LoopDestroyed
|
||||
//!
|
||||
//! Keep in mind that after LoopDestroyed event is received every attempt to draw with
|
||||
//! opengl will result in segfault.
|
||||
//!
|
||||
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
|
||||
|
||||
#![cfg(target_os = "ios")]
|
||||
|
||||
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
|
||||
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
|
||||
// window size/position.
|
||||
macro_rules! assert_main_thread {
|
||||
($($t:tt)*) => {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
panic!($($t)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod app_state;
|
||||
mod event_loop;
|
||||
mod ffi;
|
||||
mod monitor;
|
||||
mod view;
|
||||
mod window;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
pub use self::{
|
||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||
};
|
||||
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId {
|
||||
uiscreen: ffi::id,
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId {
|
||||
uiscreen: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for DeviceId {}
|
||||
unsafe impl Sync for DeviceId {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OsError {}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
299
src/platform_impl/ios/monitor.rs
Normal file
299
src/platform_impl/ios/monitor.rs
Normal file
@@ -0,0 +1,299 @@
|
||||
use std::{
|
||||
collections::{BTreeSet, VecDeque},
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
platform_impl::platform::{
|
||||
app_state,
|
||||
ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: (u32, u32),
|
||||
pub(crate) bit_depth: u16,
|
||||
pub(crate) refresh_rate: u16,
|
||||
pub(crate) screen_mode: NativeDisplayMode,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NativeDisplayMode(pub id);
|
||||
|
||||
unsafe impl Send for NativeDisplayMode {}
|
||||
|
||||
impl Drop for NativeDisplayMode {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let () = msg_send![self.0, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for NativeDisplayMode {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let _: id = msg_send![self.0, retain];
|
||||
}
|
||||
NativeDisplayMode(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for VideoMode {
|
||||
fn clone(&self) -> VideoMode {
|
||||
VideoMode {
|
||||
size: self.size,
|
||||
bit_depth: self.bit_depth,
|
||||
refresh_rate: self.refresh_rate,
|
||||
screen_mode: self.screen_mode.clone(),
|
||||
monitor: self.monitor.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
|
||||
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second {
|
||||
msg_send![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
|
||||
};
|
||||
let size: CGSize = msg_send![screen_mode, size];
|
||||
let screen_mode: id = msg_send![screen_mode, retain];
|
||||
let screen_mode = NativeDisplayMode(screen_mode);
|
||||
VideoMode {
|
||||
size: (size.width as u32, size.height as u32),
|
||||
bit_depth: 32,
|
||||
refresh_rate: refresh_rate as u16,
|
||||
screen_mode,
|
||||
monitor: MonitorHandle::retained_new(uiscreen),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size.into()
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> u16 {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.refresh_rate
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> RootMonitorHandle {
|
||||
RootMonitorHandle {
|
||||
inner: self.monitor.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Inner {
|
||||
uiscreen: id,
|
||||
}
|
||||
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let () = msg_send![self.uiscreen, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct MonitorHandle {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
impl Deref for MonitorHandle {
|
||||
type Target = Inner;
|
||||
|
||||
fn deref(&self) -> &Inner {
|
||||
unsafe {
|
||||
assert_main_thread!(
|
||||
"`MonitorHandle` methods can only be run on the main thread on iOS"
|
||||
);
|
||||
}
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for MonitorHandle {
|
||||
fn deref_mut(&mut self) -> &mut Inner {
|
||||
unsafe {
|
||||
assert_main_thread!(
|
||||
"`MonitorHandle` methods can only be run on the main thread on iOS"
|
||||
);
|
||||
}
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for MonitorHandle {}
|
||||
unsafe impl Sync for MonitorHandle {}
|
||||
|
||||
impl Clone for MonitorHandle {
|
||||
fn clone(&self) -> MonitorHandle {
|
||||
MonitorHandle::retained_new(self.uiscreen)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MonitorHandle {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorHandle {
|
||||
name: Option<String>,
|
||||
size: PhysicalSize<u32>,
|
||||
position: PhysicalPosition<i32>,
|
||||
scale_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorHandle {
|
||||
name: self.name(),
|
||||
size: self.size(),
|
||||
position: self.position(),
|
||||
scale_factor: self.scale_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
pub fn retained_new(uiscreen: id) -> MonitorHandle {
|
||||
unsafe {
|
||||
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS");
|
||||
let () = msg_send![uiscreen, retain];
|
||||
}
|
||||
MonitorHandle {
|
||||
inner: Inner { uiscreen },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
unsafe {
|
||||
let main = main_uiscreen();
|
||||
if self.uiscreen == main.uiscreen {
|
||||
Some("Primary".to_string())
|
||||
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
|
||||
Some("Mirrored".to_string())
|
||||
} else {
|
||||
uiscreens()
|
||||
.iter()
|
||||
.position(|rhs| rhs.uiscreen == self.uiscreen)
|
||||
.map(|idx| idx.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
unsafe {
|
||||
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
|
||||
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
unsafe {
|
||||
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
|
||||
(bounds.origin.x as f64, bounds.origin.y as f64).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
unsafe {
|
||||
let scale: CGFloat = msg_send![self.ui_screen(), nativeScale];
|
||||
scale as f64
|
||||
}
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||
let mut modes = BTreeSet::new();
|
||||
unsafe {
|
||||
let available_modes: id = msg_send![self.uiscreen, availableModes];
|
||||
let available_mode_count: NSUInteger = msg_send![available_modes, count];
|
||||
|
||||
for i in 0..available_mode_count {
|
||||
let mode: id = msg_send![available_modes, objectAtIndex: i];
|
||||
modes.insert(RootVideoMode {
|
||||
video_mode: VideoMode::retained_new(self.uiscreen, mode),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
modes.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
// MonitorHandleExtIOS
|
||||
impl Inner {
|
||||
pub fn ui_screen(&self) -> id {
|
||||
self.uiscreen
|
||||
}
|
||||
|
||||
pub fn preferred_video_mode(&self) -> RootVideoMode {
|
||||
unsafe {
|
||||
let mode: id = msg_send![self.uiscreen, preferredMode];
|
||||
RootVideoMode {
|
||||
video_mode: VideoMode::retained_new(self.uiscreen, mode),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// requires being run on main thread
|
||||
pub unsafe fn main_uiscreen() -> MonitorHandle {
|
||||
let uiscreen: id = msg_send![class!(UIScreen), mainScreen];
|
||||
MonitorHandle::retained_new(uiscreen)
|
||||
}
|
||||
|
||||
// requires being run on main thread
|
||||
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
|
||||
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
|
||||
MonitorHandle::retained_new(uiscreen)
|
||||
}
|
||||
|
||||
// requires being run on main thread
|
||||
pub unsafe fn uiscreens() -> VecDeque<MonitorHandle> {
|
||||
let screens: id = msg_send![class!(UIScreen), screens];
|
||||
let count: NSUInteger = msg_send![screens, count];
|
||||
let mut result = VecDeque::with_capacity(count as _);
|
||||
let screens_enum: id = msg_send![screens, objectEnumerator];
|
||||
loop {
|
||||
let screen: id = msg_send![screens_enum, nextObject];
|
||||
if screen == nil {
|
||||
break result;
|
||||
}
|
||||
result.push_back(MonitorHandle::retained_new(screen));
|
||||
}
|
||||
}
|
||||
607
src/platform_impl/ios/view.rs
Normal file
607
src/platform_impl/ios/view.rs
Normal file
@@ -0,0 +1,607 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
runtime::{Class, Object, Sel, BOOL, NO, YES},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::PhysicalPosition,
|
||||
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
|
||||
platform::ios::MonitorHandleExtIOS,
|
||||
platform_impl::platform::{
|
||||
app_state::{self, OSCapabilities},
|
||||
event_loop::{self, EventProxy, EventWrapper},
|
||||
ffi::{
|
||||
id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask,
|
||||
UIRectEdge, UITouchPhase, UITouchType,
|
||||
},
|
||||
window::PlatformSpecificWindowBuilderAttributes,
|
||||
DeviceId,
|
||||
},
|
||||
window::{Fullscreen, WindowAttributes, WindowId as RootWindowId},
|
||||
};
|
||||
|
||||
macro_rules! add_property {
|
||||
(
|
||||
$decl:ident,
|
||||
$name:ident: $t:ty,
|
||||
$setter_name:ident: |$object:ident| $after_set:expr,
|
||||
$getter_name:ident,
|
||||
) => {
|
||||
add_property!(
|
||||
$decl,
|
||||
$name: $t,
|
||||
$setter_name: true, |_, _|{}; |$object| $after_set,
|
||||
$getter_name,
|
||||
)
|
||||
};
|
||||
(
|
||||
$decl:ident,
|
||||
$name:ident: $t:ty,
|
||||
$setter_name:ident: $capability:expr, $err:expr; |$object:ident| $after_set:expr,
|
||||
$getter_name:ident,
|
||||
) => {
|
||||
{
|
||||
const VAR_NAME: &'static str = concat!("_", stringify!($name));
|
||||
$decl.add_ivar::<$t>(VAR_NAME);
|
||||
let setter = if $capability {
|
||||
#[allow(non_snake_case)]
|
||||
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
|
||||
unsafe {
|
||||
$object.set_ivar::<$t>(VAR_NAME, value);
|
||||
}
|
||||
$after_set
|
||||
}
|
||||
$setter_name
|
||||
} else {
|
||||
#[allow(non_snake_case)]
|
||||
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
|
||||
unsafe {
|
||||
$object.set_ivar::<$t>(VAR_NAME, value);
|
||||
}
|
||||
$err(&app_state::os_capabilities(), "ignoring")
|
||||
}
|
||||
$setter_name
|
||||
};
|
||||
#[allow(non_snake_case)]
|
||||
extern "C" fn $getter_name($object: &Object, _: Sel) -> $t {
|
||||
unsafe { *$object.get_ivar::<$t>(VAR_NAME) }
|
||||
}
|
||||
$decl.add_method(
|
||||
sel!($setter_name:),
|
||||
setter as extern "C" fn(&mut Object, Sel, $t),
|
||||
);
|
||||
$decl.add_method(
|
||||
sel!($getter_name),
|
||||
$getter_name as extern "C" fn(&Object, Sel) -> $t,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
static mut CLASSES: Option<HashMap<*const Class, &'static Class>> = None;
|
||||
static mut ID: usize = 0;
|
||||
|
||||
if CLASSES.is_none() {
|
||||
CLASSES = Some(HashMap::default());
|
||||
}
|
||||
|
||||
let classes = CLASSES.as_mut().unwrap();
|
||||
|
||||
classes.entry(root_view_class).or_insert_with(move || {
|
||||
let uiview_class = class!(UIView);
|
||||
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass: uiview_class];
|
||||
assert_eq!(
|
||||
is_uiview, YES,
|
||||
"`root_view_class` must inherit from `UIView`"
|
||||
);
|
||||
|
||||
extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) {
|
||||
unsafe {
|
||||
let window: id = msg_send![object, window];
|
||||
assert!(!window.is_null());
|
||||
app_state::handle_nonuser_events(
|
||||
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
|
||||
RootWindowId(window.into()),
|
||||
)))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::RedrawEventsCleared,
|
||||
))),
|
||||
);
|
||||
let superclass: &'static Class = msg_send![object, superclass];
|
||||
let () = msg_send![super(object, superclass), drawRect: rect];
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn layout_subviews(object: &Object, _: Sel) {
|
||||
unsafe {
|
||||
let superclass: &'static Class = msg_send![object, superclass];
|
||||
let () = msg_send![super(object, superclass), layoutSubviews];
|
||||
|
||||
let window: id = msg_send![object, window];
|
||||
assert!(!window.is_null());
|
||||
let bounds: CGRect = msg_send![window, bounds];
|
||||
let screen: id = msg_send![window, screen];
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
let screen_frame: CGRect =
|
||||
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
|
||||
let scale_factor: CGFloat = msg_send![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.into());
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Resized(size),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn set_content_scale_factor(
|
||||
object: &mut Object,
|
||||
_: Sel,
|
||||
untrusted_scale_factor: CGFloat,
|
||||
) {
|
||||
unsafe {
|
||||
let superclass: &'static Class = msg_send![object, superclass];
|
||||
let () = msg_send![
|
||||
super(object, superclass),
|
||||
setContentScaleFactor: untrusted_scale_factor
|
||||
];
|
||||
|
||||
let window: id = msg_send![object, window];
|
||||
// `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
|
||||
if window.is_null() {
|
||||
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: CGFloat = msg_send![object, 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: f64 = scale_factor.into();
|
||||
let bounds: CGRect = msg_send![object, bounds];
|
||||
let screen: id = msg_send![window, screen];
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
let screen_frame: CGRect =
|
||||
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
|
||||
let size = crate::dpi::LogicalSize {
|
||||
width: screen_frame.size.width as _,
|
||||
height: screen_frame.size.height as _,
|
||||
};
|
||||
app_state::handle_nonuser_events(
|
||||
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
|
||||
window_id: window,
|
||||
scale_factor,
|
||||
suggested_size: size,
|
||||
}))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
||||
},
|
||||
))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) {
|
||||
unsafe {
|
||||
let window: id = msg_send![object, window];
|
||||
assert!(!window.is_null());
|
||||
let uiscreen: id = msg_send![window, screen];
|
||||
let touches_enum: id = msg_send![touches, objectEnumerator];
|
||||
let mut touch_events = Vec::new();
|
||||
let os_supports_force = app_state::os_capabilities().force_touch;
|
||||
loop {
|
||||
let touch: id = msg_send![touches_enum, nextObject];
|
||||
if touch == nil {
|
||||
break;
|
||||
}
|
||||
let logical_location: CGPoint = msg_send![touch, locationInView: nil];
|
||||
let touch_type: UITouchType = msg_send![touch, type];
|
||||
let force = if os_supports_force {
|
||||
let trait_collection: id = msg_send![object, traitCollection];
|
||||
let touch_capability: UIForceTouchCapability =
|
||||
msg_send![trait_collection, forceTouchCapability];
|
||||
// Both the OS _and_ the device need to be checked for force touch support.
|
||||
if touch_capability == UIForceTouchCapability::Available {
|
||||
let force: CGFloat = msg_send![touch, force];
|
||||
let max_possible_force: CGFloat =
|
||||
msg_send![touch, maximumPossibleForce];
|
||||
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
|
||||
let angle: CGFloat = msg_send![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 u64;
|
||||
let phase: UITouchPhase = msg_send![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: CGFloat = msg_send![object, contentScaleFactor];
|
||||
PhysicalPosition::from_logical::<(f64, f64), f64>(
|
||||
(logical_location.x as _, logical_location.y as _),
|
||||
scale_factor,
|
||||
)
|
||||
};
|
||||
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
device_id: RootDeviceId(DeviceId { uiscreen }),
|
||||
id: touch_id,
|
||||
location: physical_location,
|
||||
force,
|
||||
phase,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
app_state::handle_nonuser_events(touch_events);
|
||||
}
|
||||
}
|
||||
|
||||
let mut decl = ClassDecl::new(&format!("WinitUIView{}", ID), root_view_class)
|
||||
.expect("Failed to declare class `WinitUIView`");
|
||||
ID += 1;
|
||||
decl.add_method(
|
||||
sel!(drawRect:),
|
||||
draw_rect as extern "C" fn(&Object, Sel, CGRect),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(layoutSubviews),
|
||||
layout_subviews as extern "C" fn(&Object, Sel),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(setContentScaleFactor:),
|
||||
set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(touchesBegan:withEvent:),
|
||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(touchesMoved:withEvent:),
|
||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(touchesEnded:withEvent:),
|
||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(touchesCancelled:withEvent:),
|
||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
||||
);
|
||||
|
||||
decl.register()
|
||||
})
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn get_view_controller_class() -> &'static Class {
|
||||
static mut CLASS: Option<&'static Class> = None;
|
||||
if CLASS.is_none() {
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
|
||||
let uiviewcontroller_class = class!(UIViewController);
|
||||
|
||||
extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL {
|
||||
YES
|
||||
}
|
||||
|
||||
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
|
||||
.expect("Failed to declare class `WinitUIViewController`");
|
||||
decl.add_method(
|
||||
sel!(shouldAutorotate),
|
||||
should_autorotate as extern "C" fn(&Object, Sel) -> BOOL,
|
||||
);
|
||||
add_property! {
|
||||
decl,
|
||||
prefers_status_bar_hidden: BOOL,
|
||||
setPrefersStatusBarHidden: |object| {
|
||||
unsafe {
|
||||
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
|
||||
}
|
||||
},
|
||||
prefersStatusBarHidden,
|
||||
}
|
||||
add_property! {
|
||||
decl,
|
||||
prefers_home_indicator_auto_hidden: BOOL,
|
||||
setPrefersHomeIndicatorAutoHidden:
|
||||
os_capabilities.home_indicator_hidden,
|
||||
OSCapabilities::home_indicator_hidden_err_msg;
|
||||
|object| {
|
||||
unsafe {
|
||||
let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
|
||||
}
|
||||
},
|
||||
prefersHomeIndicatorAutoHidden,
|
||||
}
|
||||
add_property! {
|
||||
decl,
|
||||
supported_orientations: UIInterfaceOrientationMask,
|
||||
setSupportedInterfaceOrientations: |object| {
|
||||
unsafe {
|
||||
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
|
||||
}
|
||||
},
|
||||
supportedInterfaceOrientations,
|
||||
}
|
||||
add_property! {
|
||||
decl,
|
||||
preferred_screen_edges_deferring_system_gestures: UIRectEdge,
|
||||
setPreferredScreenEdgesDeferringSystemGestures:
|
||||
os_capabilities.defer_system_gestures,
|
||||
OSCapabilities::defer_system_gestures_err_msg;
|
||||
|object| {
|
||||
unsafe {
|
||||
let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
||||
}
|
||||
},
|
||||
preferredScreenEdgesDeferringSystemGestures,
|
||||
}
|
||||
CLASS = Some(decl.register());
|
||||
}
|
||||
CLASS.unwrap()
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn get_window_class() -> &'static Class {
|
||||
static mut CLASS: Option<&'static Class> = None;
|
||||
if CLASS.is_none() {
|
||||
let uiwindow_class = class!(UIWindow);
|
||||
|
||||
extern "C" fn become_key_window(object: &Object, _: Sel) {
|
||||
unsafe {
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(object.into()),
|
||||
event: WindowEvent::Focused(true),
|
||||
}));
|
||||
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn resign_key_window(object: &Object, _: Sel) {
|
||||
unsafe {
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(object.into()),
|
||||
event: WindowEvent::Focused(false),
|
||||
}));
|
||||
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
|
||||
}
|
||||
}
|
||||
|
||||
let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class)
|
||||
.expect("Failed to declare class `WinitUIWindow`");
|
||||
decl.add_method(
|
||||
sel!(becomeKeyWindow),
|
||||
become_key_window as extern "C" fn(&Object, Sel),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(resignKeyWindow),
|
||||
resign_key_window as extern "C" fn(&Object, Sel),
|
||||
);
|
||||
|
||||
CLASS = Some(decl.register());
|
||||
}
|
||||
CLASS.unwrap()
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn create_view(
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
) -> id {
|
||||
let class = get_view_class(platform_attributes.root_view_class);
|
||||
|
||||
let view: id = msg_send![class, alloc];
|
||||
assert!(!view.is_null(), "Failed to create `UIView` instance");
|
||||
let view: id = msg_send![view, initWithFrame: frame];
|
||||
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
|
||||
let () = msg_send![view, setMultipleTouchEnabled: YES];
|
||||
if let Some(scale_factor) = platform_attributes.scale_factor {
|
||||
let () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat];
|
||||
}
|
||||
|
||||
view
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn create_view_controller(
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
view: id,
|
||||
) -> id {
|
||||
let class = get_view_controller_class();
|
||||
|
||||
let view_controller: id = msg_send![class, alloc];
|
||||
assert!(
|
||||
!view_controller.is_null(),
|
||||
"Failed to create `UIViewController` instance"
|
||||
);
|
||||
let view_controller: id = msg_send![view_controller, init];
|
||||
assert!(
|
||||
!view_controller.is_null(),
|
||||
"Failed to initialize `UIViewController` instance"
|
||||
);
|
||||
let status_bar_hidden = if platform_attributes.prefers_status_bar_hidden {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
};
|
||||
let idiom = event_loop::get_idiom();
|
||||
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
|
||||
platform_attributes.valid_orientations,
|
||||
idiom,
|
||||
);
|
||||
let prefers_home_indicator_hidden = if platform_attributes.prefers_home_indicator_hidden {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
};
|
||||
let edges: UIRectEdge = platform_attributes
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.into();
|
||||
let () = msg_send![
|
||||
view_controller,
|
||||
setPrefersStatusBarHidden: status_bar_hidden
|
||||
];
|
||||
let () = msg_send![
|
||||
view_controller,
|
||||
setSupportedInterfaceOrientations: supported_orientations
|
||||
];
|
||||
let () = msg_send![
|
||||
view_controller,
|
||||
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
|
||||
];
|
||||
let () = msg_send![
|
||||
view_controller,
|
||||
setPreferredScreenEdgesDeferringSystemGestures: edges
|
||||
];
|
||||
let () = msg_send![view_controller, setView: view];
|
||||
view_controller
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn create_window(
|
||||
window_attributes: &WindowAttributes,
|
||||
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
view_controller: id,
|
||||
) -> id {
|
||||
let class = get_window_class();
|
||||
|
||||
let window: id = msg_send![class, alloc];
|
||||
assert!(!window.is_null(), "Failed to create `UIWindow` instance");
|
||||
let window: id = msg_send![window, initWithFrame: frame];
|
||||
assert!(
|
||||
!window.is_null(),
|
||||
"Failed to initialize `UIWindow` instance"
|
||||
);
|
||||
let () = msg_send![window, setRootViewController: view_controller];
|
||||
match window_attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
let uiscreen = video_mode.monitor().ui_screen() as id;
|
||||
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
|
||||
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
|
||||
}
|
||||
Some(Fullscreen::Borderless(ref monitor)) => {
|
||||
msg_send![window, setScreen:monitor.ui_screen()]
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
pub fn create_delegate_class() {
|
||||
extern "C" fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL {
|
||||
unsafe {
|
||||
app_state::did_finish_launching();
|
||||
}
|
||||
YES
|
||||
}
|
||||
|
||||
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
|
||||
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
|
||||
}
|
||||
|
||||
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
|
||||
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
|
||||
}
|
||||
|
||||
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
|
||||
extern "C" fn did_enter_background(_: &Object, _: Sel, _: id) {}
|
||||
|
||||
extern "C" fn will_terminate(_: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
||||
let windows: id = msg_send![app, windows];
|
||||
let windows_enum: id = msg_send![windows, objectEnumerator];
|
||||
let mut events = Vec::new();
|
||||
loop {
|
||||
let window: id = msg_send![windows_enum, nextObject];
|
||||
if window == nil {
|
||||
break;
|
||||
}
|
||||
let is_winit_window: BOOL = msg_send![window, isKindOfClass: class!(WinitUIWindow)];
|
||||
if is_winit_window == YES {
|
||||
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Destroyed,
|
||||
}));
|
||||
}
|
||||
}
|
||||
app_state::handle_nonuser_events(events);
|
||||
app_state::terminated();
|
||||
}
|
||||
}
|
||||
|
||||
let ui_responder = class!(UIResponder);
|
||||
let mut decl =
|
||||
ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
|
||||
|
||||
unsafe {
|
||||
decl.add_method(
|
||||
sel!(application:didFinishLaunchingWithOptions:),
|
||||
did_finish_launching as extern "C" fn(&mut Object, Sel, id, id) -> BOOL,
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(applicationDidBecomeActive:),
|
||||
did_become_active as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationWillResignActive:),
|
||||
will_resign_active as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationWillEnterForeground:),
|
||||
will_enter_foreground as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationDidEnterBackground:),
|
||||
did_enter_background as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(applicationWillTerminate:),
|
||||
will_terminate as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.register();
|
||||
}
|
||||
}
|
||||
638
src/platform_impl/ios/window.rs
Normal file
638
src/platform_impl/ios/window.rs
Normal file
@@ -0,0 +1,638 @@
|
||||
use raw_window_handle::{ios::IOSHandle, RawWindowHandle};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use objc::runtime::{Class, Object, BOOL, NO, YES};
|
||||
|
||||
use crate::{
|
||||
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::{Event, WindowEvent},
|
||||
icon::Icon,
|
||||
monitor::MonitorHandle as RootMonitorHandle,
|
||||
platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
|
||||
platform_impl::platform::{
|
||||
app_state,
|
||||
event_loop::{self, EventProxy, EventWrapper},
|
||||
ffi::{
|
||||
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
|
||||
UIRectEdge, UIScreenOverscanCompensation,
|
||||
},
|
||||
monitor, view, EventLoopWindowTarget, MonitorHandle,
|
||||
},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
|
||||
};
|
||||
|
||||
pub struct Inner {
|
||||
pub window: id,
|
||||
pub view_controller: id,
|
||||
pub view: id,
|
||||
gl_or_metal_backed: bool,
|
||||
}
|
||||
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let () = msg_send![self.view, release];
|
||||
let () = msg_send![self.view_controller, release];
|
||||
let () = msg_send![self.window, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
pub fn set_title(&self, _title: &str) {
|
||||
debug!("`Window::set_title` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
match visible {
|
||||
true => unsafe {
|
||||
let () = msg_send![self.window, setHidden: NO];
|
||||
},
|
||||
false => unsafe {
|
||||
let () = msg_send![self.window, setHidden: YES];
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
unsafe {
|
||||
if self.gl_or_metal_backed {
|
||||
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
|
||||
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
|
||||
// raw or gl/metal for drawing this work is completely avoided.
|
||||
//
|
||||
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
|
||||
// testing.
|
||||
//
|
||||
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
|
||||
app_state::queue_gl_or_metal_redraw(self.window);
|
||||
} else {
|
||||
let () = msg_send![self.view, setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
unsafe {
|
||||
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> {
|
||||
unsafe {
|
||||
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) {
|
||||
unsafe {
|
||||
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.from_screen_space(new_screen_frame);
|
||||
let () = msg_send![self.window, setBounds: bounds];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
unsafe {
|
||||
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> {
|
||||
unsafe {
|
||||
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 set_inner_size(&self, _size: Size) {
|
||||
unimplemented!("not clear what `Window::set_inner_size` means on iOS");
|
||||
}
|
||||
|
||||
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 set_resizable(&self, _resizable: bool) {
|
||||
warn!("`Window::set_resizable` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
unsafe {
|
||||
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
|
||||
hidpi as _
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
|
||||
debug!("`Window::set_cursor_icon` ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn set_cursor_grab(&self, _grab: bool) -> 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 set_minimized(&self, _minimized: bool) {
|
||||
warn!("`Window::set_minimized` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_maximized(&self, _maximized: bool) {
|
||||
warn!("`Window::set_maximized` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||
unsafe {
|
||||
let uiscreen = match monitor {
|
||||
Some(Fullscreen::Exclusive(video_mode)) => {
|
||||
let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
|
||||
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
|
||||
uiscreen
|
||||
}
|
||||
Some(Fullscreen::Borderless(monitor)) => monitor.ui_screen() as id,
|
||||
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: id = msg_send![self.window, screen];
|
||||
if uiscreen != current {
|
||||
let () = msg_send![self.window, setScreen: uiscreen];
|
||||
}
|
||||
|
||||
let bounds: CGRect = msg_send![uiscreen, bounds];
|
||||
let () = msg_send![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
|
||||
let () = msg_send![
|
||||
uiscreen,
|
||||
setOverscanCompensation: UIScreenOverscanCompensation::None
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
unsafe {
|
||||
let monitor = self.current_monitor();
|
||||
let uiscreen = monitor.inner.ui_screen();
|
||||
let screen_space_bounds = self.screen_frame();
|
||||
let screen_bounds: CGRect = msg_send![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(monitor))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_decorations(&self, _decorations: bool) {
|
||||
warn!("`Window::set_decorations` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||
warn!("`Window::set_always_on_top` 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_position(&self, _position: Position) {
|
||||
warn!("`Window::set_ime_position` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn current_monitor(&self) -> RootMonitorHandle {
|
||||
unsafe {
|
||||
let uiscreen: id = msg_send![self.window, screen];
|
||||
RootMonitorHandle {
|
||||
inner: MonitorHandle::retained_new(uiscreen),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
unsafe { monitor::uiscreens() }
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
unsafe { monitor::main_uiscreen() }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WindowId {
|
||||
self.window.into()
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let handle = IOSHandle {
|
||||
ui_window: self.window as _,
|
||||
ui_view: self.view as _,
|
||||
ui_view_controller: self.view_controller as _,
|
||||
..IOSHandle::empty()
|
||||
};
|
||||
RawWindowHandle::IOS(handle)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
pub inner: Inner,
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Window {}
|
||||
unsafe impl Sync for Window {}
|
||||
|
||||
impl Deref for Window {
|
||||
type Target = Inner;
|
||||
|
||||
fn deref(&self) -> &Inner {
|
||||
unsafe {
|
||||
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
|
||||
}
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Window {
|
||||
fn deref_mut(&mut self) -> &mut Inner {
|
||||
unsafe {
|
||||
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
|
||||
}
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new<T>(
|
||||
_event_loop: &EventLoopWindowTarget<T>,
|
||||
window_attributes: WindowAttributes,
|
||||
platform_attributes: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, RootOsError> {
|
||||
if let Some(_) = window_attributes.min_inner_size {
|
||||
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
|
||||
}
|
||||
if let Some(_) = window_attributes.max_inner_size {
|
||||
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
|
||||
}
|
||||
if window_attributes.always_on_top {
|
||||
warn!("`WindowAttributes::always_on_top` is unsupported on iOS");
|
||||
}
|
||||
// TODO: transparency, visible
|
||||
|
||||
unsafe {
|
||||
let screen = match window_attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
video_mode.video_mode.monitor.ui_screen() as id
|
||||
}
|
||||
Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id,
|
||||
None => monitor::main_uiscreen().ui_screen(),
|
||||
};
|
||||
|
||||
let screen_bounds: CGRect = msg_send![screen, bounds];
|
||||
|
||||
let frame = match window_attributes.inner_size {
|
||||
Some(dim) => {
|
||||
let scale_factor = msg_send![screen, scale];
|
||||
let size = dim.to_logical::<f64>(scale_factor);
|
||||
CGRect {
|
||||
origin: screen_bounds.origin,
|
||||
size: CGSize {
|
||||
width: size.width as _,
|
||||
height: size.height as _,
|
||||
},
|
||||
}
|
||||
}
|
||||
None => screen_bounds,
|
||||
};
|
||||
|
||||
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
|
||||
|
||||
let gl_or_metal_backed = {
|
||||
let view_class: id = msg_send![view, class];
|
||||
let layer_class: id = msg_send![view_class, layerClass];
|
||||
let is_metal: BOOL =
|
||||
msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
|
||||
let is_gl: BOOL = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
|
||||
is_metal == YES || is_gl == YES
|
||||
};
|
||||
|
||||
let view_controller =
|
||||
view::create_view_controller(&window_attributes, &platform_attributes, view);
|
||||
let window = view::create_window(
|
||||
&window_attributes,
|
||||
&platform_attributes,
|
||||
frame,
|
||||
view_controller,
|
||||
);
|
||||
|
||||
let result = Window {
|
||||
inner: Inner {
|
||||
window,
|
||||
view_controller,
|
||||
view,
|
||||
gl_or_metal_backed,
|
||||
},
|
||||
};
|
||||
app_state::set_key_window(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: CGFloat = msg_send![view, contentScaleFactor];
|
||||
let scale_factor: f64 = scale_factor.into();
|
||||
if scale_factor != 1.0 {
|
||||
let bounds: CGRect = msg_send![view, bounds];
|
||||
let screen: id = msg_send![window, screen];
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
let screen_frame: CGRect =
|
||||
msg_send![view, convertRect:bounds toCoordinateSpace:screen_space];
|
||||
let size = crate::dpi::LogicalSize {
|
||||
width: screen_frame.size.width as _,
|
||||
height: screen_frame.size.height as _,
|
||||
};
|
||||
app_state::handle_nonuser_events(
|
||||
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
|
||||
window_id: window,
|
||||
scale_factor,
|
||||
suggested_size: size,
|
||||
}))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
||||
},
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WindowExtIOS
|
||||
impl Inner {
|
||||
pub fn ui_window(&self) -> id {
|
||||
self.window
|
||||
}
|
||||
pub fn ui_view_controller(&self) -> id {
|
||||
self.view_controller
|
||||
}
|
||||
pub fn ui_view(&self) -> id {
|
||||
self.view
|
||||
}
|
||||
|
||||
pub fn set_scale_factor(&self, scale_factor: f64) {
|
||||
unsafe {
|
||||
assert!(
|
||||
dpi::validate_scale_factor(scale_factor),
|
||||
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
|
||||
);
|
||||
let scale_factor = scale_factor as CGFloat;
|
||||
let () = msg_send![self.view, setContentScaleFactor: scale_factor];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
||||
unsafe {
|
||||
let idiom = event_loop::get_idiom();
|
||||
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
|
||||
valid_orientations,
|
||||
idiom,
|
||||
);
|
||||
msg_send![
|
||||
self.view_controller,
|
||||
setSupportedInterfaceOrientations: supported_orientations
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||
unsafe {
|
||||
let prefers_home_indicator_hidden = if hidden { YES } else { NO };
|
||||
let () = msg_send![
|
||||
self.view_controller,
|
||||
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
||||
let edges: UIRectEdge = edges.into();
|
||||
unsafe {
|
||||
let () = msg_send![
|
||||
self.view_controller,
|
||||
setPreferredScreenEdgesDeferringSystemGestures: edges
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||
unsafe {
|
||||
let status_bar_hidden = if hidden { YES } else { NO };
|
||||
let () = msg_send![
|
||||
self.view_controller,
|
||||
setPrefersStatusBarHidden: status_bar_hidden
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
// requires main thread
|
||||
unsafe fn screen_frame(&self) -> CGRect {
|
||||
self.to_screen_space(msg_send![self.window, bounds])
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
let screen: id = msg_send![self.window, screen];
|
||||
if !screen.is_null() {
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
msg_send![self.window, convertRect:rect toCoordinateSpace:screen_space]
|
||||
} else {
|
||||
rect
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
let screen: id = msg_send![self.window, screen];
|
||||
if !screen.is_null() {
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
msg_send![self.window, convertRect:rect fromCoordinateSpace:screen_space]
|
||||
} else {
|
||||
rect
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn safe_area_screen_space(&self) -> CGRect {
|
||||
let bounds: CGRect = msg_send![self.window, bounds];
|
||||
if app_state::os_capabilities().safe_area {
|
||||
let safe_area: UIEdgeInsets = msg_send![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.to_screen_space(safe_bounds)
|
||||
} else {
|
||||
let screen_frame = self.to_screen_space(bounds);
|
||||
let status_bar_frame: CGRect = {
|
||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
||||
assert!(
|
||||
!app.is_null(),
|
||||
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS"
|
||||
);
|
||||
msg_send![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: id,
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId {
|
||||
window: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for WindowId {}
|
||||
unsafe impl Sync for WindowId {}
|
||||
|
||||
impl From<&Object> for WindowId {
|
||||
fn from(window: &Object) -> WindowId {
|
||||
WindowId {
|
||||
window: window as *const _ as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&mut Object> for WindowId {
|
||||
fn from(window: &mut Object) -> WindowId {
|
||||
WindowId {
|
||||
window: window as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<id> for WindowId {
|
||||
fn from(window: id) -> WindowId {
|
||||
WindowId { window }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub root_view_class: &'static Class,
|
||||
pub scale_factor: Option<f64>,
|
||||
pub valid_orientations: ValidOrientations,
|
||||
pub prefers_home_indicator_hidden: bool,
|
||||
pub prefers_status_bar_hidden: bool,
|
||||
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
fn default() -> PlatformSpecificWindowBuilderAttributes {
|
||||
PlatformSpecificWindowBuilderAttributes {
|
||||
root_view_class: class!(UIView),
|
||||
scale_factor: None,
|
||||
valid_orientations: Default::default(),
|
||||
prefers_home_indicator_hidden: false,
|
||||
prefers_status_bar_hidden: false,
|
||||
preferred_screen_edges_deferring_system_gestures: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
731
src/platform_impl/linux/mod.rs
Normal file
731
src/platform_impl/linux/mod.rs
Normal file
@@ -0,0 +1,731 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
|
||||
use std::{collections::VecDeque, env, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, sync::Arc};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::RawWindowHandle;
|
||||
use smithay_client_toolkit::reexports::client::ConnectError;
|
||||
|
||||
pub use self::x11::XNotSupported;
|
||||
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
icon::Icon,
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||
};
|
||||
|
||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||
|
||||
pub mod wayland;
|
||||
pub mod x11;
|
||||
|
||||
/// Environment variable specifying which backend should be used on unix platform.
|
||||
///
|
||||
/// Legal values are x11 and wayland. If this variable is set only the named backend
|
||||
/// will be tried by winit. If it is not set, winit will try to connect to a wayland connection,
|
||||
/// and if it fails will fallback on x11.
|
||||
///
|
||||
/// If this variable is set with any other value, winit will panic.
|
||||
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub visual_infos: Option<XVisualInfo>,
|
||||
pub screen_id: Option<i32>,
|
||||
pub resize_increments: Option<Size>,
|
||||
pub base_size: Option<Size>,
|
||||
pub class: Option<(String, String)>,
|
||||
pub override_redirect: bool,
|
||||
pub x11_window_types: Vec<XWindowType>,
|
||||
pub gtk_theme_variant: Option<String>,
|
||||
pub app_id: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
visual_infos: None,
|
||||
screen_id: None,
|
||||
resize_increments: None,
|
||||
base_size: None,
|
||||
class: None,
|
||||
override_redirect: false,
|
||||
x11_window_types: vec![XWindowType::Normal],
|
||||
gtk_theme_variant: None,
|
||||
app_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
|
||||
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new));
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OsError {
|
||||
XError(XError),
|
||||
XMisc(&'static str),
|
||||
}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
OsError::XError(e) => f.pad(&e.description),
|
||||
OsError::XMisc(e) => f.pad(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Window {
|
||||
X(x11::Window),
|
||||
Wayland(wayland::Window),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum WindowId {
|
||||
X(x11::WindowId),
|
||||
Wayland(wayland::WindowId),
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId::Wayland(wayland::WindowId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DeviceId {
|
||||
X(x11::DeviceId),
|
||||
Wayland(wayland::DeviceId),
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId::Wayland(wayland::DeviceId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum MonitorHandle {
|
||||
X(x11::MonitorHandle),
|
||||
Wayland(wayland::MonitorHandle),
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.name(),
|
||||
&MonitorHandle::Wayland(ref m) => m.name(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_identifier(&self) -> u32 {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.native_identifier(),
|
||||
&MonitorHandle::Wayland(ref m) => m.native_identifier(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.size(),
|
||||
&MonitorHandle::Wayland(ref m) => m.size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.position(),
|
||||
&MonitorHandle::Wayland(ref m) => m.position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.scale_factor(),
|
||||
&MonitorHandle::Wayland(ref m) => m.scale_factor() as f64,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
|
||||
match self {
|
||||
MonitorHandle::X(m) => Box::new(m.video_modes()),
|
||||
MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum VideoMode {
|
||||
X(x11::VideoMode),
|
||||
Wayland(wayland::VideoMode),
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
match self {
|
||||
&VideoMode::X(ref m) => m.size(),
|
||||
&VideoMode::Wayland(ref m) => m.size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> u16 {
|
||||
match self {
|
||||
&VideoMode::X(ref m) => m.bit_depth(),
|
||||
&VideoMode::Wayland(ref m) => m.bit_depth(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
match self {
|
||||
&VideoMode::X(ref m) => m.refresh_rate(),
|
||||
&VideoMode::Wayland(ref m) => m.refresh_rate(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn monitor(&self) -> RootMonitorHandle {
|
||||
match self {
|
||||
&VideoMode::X(ref m) => m.monitor(),
|
||||
&VideoMode::Wayland(ref m) => m.monitor(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
#[inline]
|
||||
pub fn new<T>(
|
||||
window_target: &EventLoopWindowTarget<T>,
|
||||
attribs: WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Self, RootOsError> {
|
||||
match *window_target {
|
||||
EventLoopWindowTarget::Wayland(ref window_target) => {
|
||||
wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland)
|
||||
}
|
||||
EventLoopWindowTarget::X(ref window_target) => {
|
||||
x11::Window::new(window_target, attribs, pl_attribs).map(Window::X)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
match self {
|
||||
&Window::X(ref w) => WindowId::X(w.id()),
|
||||
&Window::Wayland(ref w) => WindowId::Wayland(w.id()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, title: &str) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_title(title),
|
||||
&Window::Wayland(ref w) => w.set_title(title),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_visible(visible),
|
||||
&Window::Wayland(ref w) => w.set_visible(visible),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.outer_position(),
|
||||
&Window::Wayland(ref w) => w.outer_position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
match self {
|
||||
&Window::X(ref m) => m.inner_position(),
|
||||
&Window::Wayland(ref m) => m.inner_position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_outer_position(&self, position: Position) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_outer_position(position),
|
||||
&Window::Wayland(ref w) => w.set_outer_position(position),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.inner_size(),
|
||||
&Window::Wayland(ref w) => w.inner_size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.outer_size(),
|
||||
&Window::Wayland(ref w) => w.outer_size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_inner_size(size),
|
||||
&Window::Wayland(ref w) => w.set_inner_size(size),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_min_inner_size(dimensions),
|
||||
&Window::Wayland(ref w) => w.set_min_inner_size(dimensions),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_max_inner_size(dimensions),
|
||||
&Window::Wayland(ref w) => w.set_max_inner_size(dimensions),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_resizable(resizable),
|
||||
&Window::Wayland(ref w) => w.set_resizable(resizable),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_cursor_icon(cursor),
|
||||
&Window::Wayland(ref w) => w.set_cursor_icon(cursor),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
||||
match self {
|
||||
&Window::X(ref window) => window.set_cursor_grab(grab),
|
||||
&Window::Wayland(ref window) => window.set_cursor_grab(grab),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
match self {
|
||||
&Window::X(ref window) => window.set_cursor_visible(visible),
|
||||
&Window::Wayland(ref window) => window.set_cursor_visible(visible),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
match self {
|
||||
&Window::X(ref w) => w.scale_factor(),
|
||||
&Window::Wayland(ref w) => w.scale_factor() as f64,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_cursor_position(position),
|
||||
&Window::Wayland(ref w) => w.set_cursor_position(position),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_maximized(maximized),
|
||||
&Window::Wayland(ref w) => w.set_maximized(maximized),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_minimized(minimized),
|
||||
&Window::Wayland(ref w) => w.set_minimized(minimized),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.fullscreen(),
|
||||
&Window::Wayland(ref w) => w.fullscreen(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_fullscreen(monitor),
|
||||
&Window::Wayland(ref w) => w.set_fullscreen(monitor),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_decorations(decorations),
|
||||
&Window::Wayland(ref w) => w.set_decorations(decorations),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, always_on_top: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_always_on_top(always_on_top),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_window_icon(window_icon),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, position: Position) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_ime_position(position),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_redraw(&self) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.request_redraw(),
|
||||
&Window::Wayland(ref w) => w.request_redraw(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> RootMonitorHandle {
|
||||
match self {
|
||||
&Window::X(ref window) => RootMonitorHandle {
|
||||
inner: MonitorHandle::X(window.current_monitor()),
|
||||
},
|
||||
&Window::Wayland(ref window) => RootMonitorHandle {
|
||||
inner: MonitorHandle::Wayland(window.current_monitor()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
match self {
|
||||
&Window::X(ref window) => window
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::X)
|
||||
.collect(),
|
||||
&Window::Wayland(ref window) => window
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::Wayland)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
match self {
|
||||
&Window::X(ref window) => MonitorHandle::X(window.primary_monitor()),
|
||||
&Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
match self {
|
||||
&Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()),
|
||||
&Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn x_error_callback(
|
||||
display: *mut x11::ffi::Display,
|
||||
event: *mut x11::ffi::XErrorEvent,
|
||||
) -> c_int {
|
||||
let xconn_lock = X11_BACKEND.lock();
|
||||
if let Ok(ref xconn) = *xconn_lock {
|
||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||
// which do not require initialization.
|
||||
let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
|
||||
(xconn.xlib.XGetErrorText)(
|
||||
display,
|
||||
(*event).error_code as c_int,
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
buf.len() as c_int,
|
||||
);
|
||||
let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy();
|
||||
|
||||
let error = XError {
|
||||
description: description.into_owned(),
|
||||
error_code: (*event).error_code,
|
||||
request_code: (*event).request_code,
|
||||
minor_code: (*event).minor_code,
|
||||
};
|
||||
|
||||
error!("X11 error: {:#?}", error);
|
||||
|
||||
*xconn.latest_error.lock() = Some(error);
|
||||
}
|
||||
// Fun fact: this return value is completely ignored.
|
||||
0
|
||||
}
|
||||
|
||||
pub enum EventLoop<T: 'static> {
|
||||
Wayland(wayland::EventLoop<T>),
|
||||
X(x11::EventLoop<T>),
|
||||
}
|
||||
|
||||
pub enum EventLoopProxy<T: 'static> {
|
||||
X(x11::EventLoopProxy<T>),
|
||||
Wayland(wayland::EventLoopProxy<T>),
|
||||
}
|
||||
|
||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
EventLoopProxy::X(proxy) => EventLoopProxy::X(proxy.clone()),
|
||||
EventLoopProxy::Wayland(proxy) => EventLoopProxy::Wayland(proxy.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> EventLoop<T> {
|
||||
assert_is_main_thread("new_any_thread");
|
||||
|
||||
EventLoop::new_any_thread()
|
||||
}
|
||||
|
||||
pub fn new_any_thread() -> EventLoop<T> {
|
||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
||||
match env_var.as_str() {
|
||||
"x11" => {
|
||||
// TODO: propagate
|
||||
return EventLoop::new_x11_any_thread()
|
||||
.expect("Failed to initialize X11 backend");
|
||||
}
|
||||
"wayland" => {
|
||||
return EventLoop::new_wayland_any_thread()
|
||||
.expect("Failed to initialize Wayland backend");
|
||||
}
|
||||
_ => panic!(
|
||||
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
|
||||
BACKEND_PREFERENCE_ENV_VAR,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
let wayland_err = match EventLoop::new_wayland_any_thread() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
let x11_err = match EventLoop::new_x11_any_thread() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
let err_string = format!(
|
||||
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
|
||||
wayland_err, x11_err,
|
||||
);
|
||||
panic!(err_string);
|
||||
}
|
||||
|
||||
pub fn new_wayland() -> Result<EventLoop<T>, ConnectError> {
|
||||
assert_is_main_thread("new_wayland_any_thread");
|
||||
|
||||
EventLoop::new_wayland_any_thread()
|
||||
}
|
||||
|
||||
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, ConnectError> {
|
||||
wayland::EventLoop::new().map(EventLoop::Wayland)
|
||||
}
|
||||
|
||||
pub fn new_x11() -> Result<EventLoop<T>, XNotSupported> {
|
||||
assert_is_main_thread("new_x11_any_thread");
|
||||
|
||||
EventLoop::new_x11_any_thread()
|
||||
}
|
||||
|
||||
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
|
||||
let xconn = match X11_BACKEND.lock().as_ref() {
|
||||
Ok(xconn) => xconn.clone(),
|
||||
Err(err) => return Err(err.clone()),
|
||||
};
|
||||
|
||||
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evlp) => evlp
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::Wayland)
|
||||
.collect(),
|
||||
EventLoop::X(ref evlp) => evlp
|
||||
.x_connection()
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::X)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()),
|
||||
EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evlp) => EventLoopProxy::Wayland(evlp.create_proxy()),
|
||||
EventLoop::X(ref evlp) => EventLoopProxy::X(evlp.create_proxy()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, callback: F)
|
||||
where
|
||||
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
match *self {
|
||||
EventLoop::Wayland(ref mut evlp) => evlp.run_return(callback),
|
||||
EventLoop::X(ref mut evlp) => evlp.run_return(callback),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run<F>(self, callback: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
match self {
|
||||
EventLoop::Wayland(evlp) => evlp.run(callback),
|
||||
EventLoop::X(evlp) => evlp.run(callback),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evl) => evl.window_target(),
|
||||
EventLoop::X(ref evl) => evl.window_target(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopProxy<T> {
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
match *self {
|
||||
EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event),
|
||||
EventLoopProxy::X(ref proxy) => proxy.send_event(event),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum EventLoopWindowTarget<T> {
|
||||
Wayland(wayland::EventLoopWindowTarget<T>),
|
||||
X(x11::EventLoopWindowTarget<T>),
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
#[inline]
|
||||
pub fn is_wayland(&self) -> bool {
|
||||
match *self {
|
||||
EventLoopWindowTarget::Wayland(_) => true,
|
||||
EventLoopWindowTarget::X(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sticky_exit_callback<T, F>(
|
||||
evt: Event<'_, T>,
|
||||
target: &RootELW<T>,
|
||||
control_flow: &mut ControlFlow,
|
||||
callback: &mut F,
|
||||
) where
|
||||
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
// make ControlFlow::Exit sticky by providing a dummy
|
||||
// control flow reference if it is already Exit.
|
||||
let mut dummy = ControlFlow::Exit;
|
||||
let cf = if *control_flow == ControlFlow::Exit {
|
||||
&mut dummy
|
||||
} else {
|
||||
control_flow
|
||||
};
|
||||
// user callback
|
||||
callback(evt, target, cf)
|
||||
}
|
||||
|
||||
fn assert_is_main_thread(suggested_method: &str) {
|
||||
if !is_main_thread() {
|
||||
panic!(
|
||||
"Initializing the event loop outside of the main thread is a significant \
|
||||
cross-platform compatibility hazard. If you really, absolutely need to create an \
|
||||
EventLoop on a different thread, please use the `EventLoopExtUnix::{}` function.",
|
||||
suggested_method
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn is_main_thread() -> bool {
|
||||
use libc::{c_long, getpid, syscall, SYS_gettid};
|
||||
|
||||
unsafe { syscall(SYS_gettid) == getpid() as c_long }
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
fn is_main_thread() -> bool {
|
||||
use libc::pthread_main_np;
|
||||
|
||||
unsafe { pthread_main_np() == 1 }
|
||||
}
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
fn is_main_thread() -> bool {
|
||||
use libc::_lwp_self;
|
||||
|
||||
unsafe { _lwp_self() == 1 }
|
||||
}
|
||||
1138
src/platform_impl/linux/wayland/event_loop.rs
Normal file
1138
src/platform_impl/linux/wayland/event_loop.rs
Normal file
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