mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
648 Commits
v0.20.0-al
...
v0.27.x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbdcb79bbd | ||
|
|
b8ac784ffc | ||
|
|
e7037c6ad0 | ||
|
|
51986bce26 | ||
|
|
429cc58f98 | ||
|
|
81303d81a8 | ||
|
|
adfa5bd275 | ||
|
|
66319c571c | ||
|
|
f1470d1ab7 | ||
|
|
79dc6bf4ab | ||
|
|
ad0520e935 | ||
|
|
156fa375ea | ||
|
|
2a2733b2d1 | ||
|
|
7af1163ee7 | ||
|
|
229dbffa17 | ||
|
|
a5457b24c2 | ||
|
|
5645bb1459 | ||
|
|
77cd021c02 | ||
|
|
2d732069a9 | ||
|
|
2e4338bb8d | ||
|
|
ec2888b8b7 | ||
|
|
fa83bace12 | ||
|
|
ee7dc48e3b | ||
|
|
11d4a301e4 | ||
|
|
ad41eaf151 | ||
|
|
9b71df9f97 | ||
|
|
b1c9e4a6fa | ||
|
|
cdbaf4816a | ||
|
|
6b7ceedc91 | ||
|
|
c53a574bff | ||
|
|
95246d81c1 | ||
|
|
bf537009d9 | ||
|
|
50035643f7 | ||
|
|
64c22f9075 | ||
|
|
4895a29e92 | ||
|
|
6cdb3179c8 | ||
|
|
4fd52af682 | ||
|
|
5a0bad130d | ||
|
|
08d025968e | ||
|
|
1cd0e94c26 | ||
|
|
1ec976f95e | ||
|
|
f10ef5f331 | ||
|
|
653bc59813 | ||
|
|
3e991e13dc | ||
|
|
5397b53e04 | ||
|
|
f09259f6de | ||
|
|
430a49ebc2 | ||
|
|
1091a8ba1a | ||
|
|
9116b6c8cd | ||
|
|
990e34a129 | ||
|
|
472d7b9376 | ||
|
|
50dd7881b1 | ||
|
|
aa8f8db305 | ||
|
|
cdd9b1e1eb | ||
|
|
2d2ce70edc | ||
|
|
78f1d1df38 | ||
|
|
a06bb3f992 | ||
|
|
e289f30e5d | ||
|
|
4b10993970 | ||
|
|
d78a870e66 | ||
|
|
cb41c58f21 | ||
|
|
c55d97183d | ||
|
|
8646cbc9f5 | ||
|
|
64c1d4c5bb | ||
|
|
76b949c196 | ||
|
|
c93ef47b9b | ||
|
|
2b414cd825 | ||
|
|
ac42447459 | ||
|
|
401d20fa1f | ||
|
|
6b5b570b45 | ||
|
|
9e6f666616 | ||
|
|
8ef9fe44c7 | ||
|
|
3e0a544eb8 | ||
|
|
6474891f1e | ||
|
|
40abb526cc | ||
|
|
c532d910c0 | ||
|
|
44288f6280 | ||
|
|
eec84ade86 | ||
|
|
10419ff441 | ||
|
|
57981b533d | ||
|
|
c5eaa0ab69 | ||
|
|
2c01e9e747 | ||
|
|
4c39b3188c | ||
|
|
224872ce03 | ||
|
|
f10a984ba3 | ||
|
|
c7f7181388 | ||
|
|
92530299eb | ||
|
|
58cd23d1ac | ||
|
|
5d85c10a2c | ||
|
|
f11270dac0 | ||
|
|
bcd76d4718 | ||
|
|
4dd2b66aaa | ||
|
|
829a140d9b | ||
|
|
f04fa5d54f | ||
|
|
b4175c1454 | ||
|
|
0728105b2b | ||
|
|
ea09d1d10e | ||
|
|
e7f88588bf | ||
|
|
ce890c3455 | ||
|
|
cbba00d360 | ||
|
|
bf366cb99d | ||
|
|
142d55ff24 | ||
|
|
a58400a82c | ||
|
|
aac28d24ac | ||
|
|
d624a3e648 | ||
|
|
c57294b41a | ||
|
|
485e82dcb1 | ||
|
|
e8d910ffd3 | ||
|
|
ab1f636960 | ||
|
|
52c4670237 | ||
|
|
2ae12fb0a0 | ||
|
|
6c1d3c4fd8 | ||
|
|
08de2b3fc4 | ||
|
|
945a9e3122 | ||
|
|
6e28ba8927 | ||
|
|
7369551c02 | ||
|
|
e22c76b3ac | ||
|
|
a438091266 | ||
|
|
85baf79d17 | ||
|
|
1c68be0631 | ||
|
|
b222dde835 | ||
|
|
78e5a395da | ||
|
|
7846e6a31e | ||
|
|
b7e7755edd | ||
|
|
40f48cbeb4 | ||
|
|
fb8313aa97 | ||
|
|
f9643917d3 | ||
|
|
ac1c9b1218 | ||
|
|
daf0d6b9a7 | ||
|
|
fa14863284 | ||
|
|
cd9ec0afc7 | ||
|
|
f3f6f1008a | ||
|
|
0e52672f4a | ||
|
|
bc1dc1fd63 | ||
|
|
f93f2c158b | ||
|
|
9229e2d88b | ||
|
|
51bb6b751e | ||
|
|
2cc87cab65 | ||
|
|
7cd273ae58 | ||
|
|
001fb7ef60 | ||
|
|
cf4660841a | ||
|
|
a52f755ce8 | ||
|
|
2a2abc4843 | ||
|
|
8af222c1e3 | ||
|
|
d3e6949007 | ||
|
|
a033b25ecb | ||
|
|
39dd30c239 | ||
|
|
c5c99d2357 | ||
|
|
25ff30ee8c | ||
|
|
6b250a74f8 | ||
|
|
5331397c6c | ||
|
|
0b39024133 | ||
|
|
438d286fd5 | ||
|
|
18a61f1058 | ||
|
|
20d012ae3f | ||
|
|
efc54ab8ba | ||
|
|
ea1c031b54 | ||
|
|
11a44081df | ||
|
|
5eb9c9504b | ||
|
|
29a078f65c | ||
|
|
be61ca13fe | ||
|
|
e9d5b2007a | ||
|
|
f2de8475fc | ||
|
|
3ecbea3c39 | ||
|
|
c4df7ad7a5 | ||
|
|
387567a917 | ||
|
|
cfbe8462cc | ||
|
|
5f4df54895 | ||
|
|
ed698f2462 | ||
|
|
b4774861db | ||
|
|
9768f73bb7 | ||
|
|
805249e27e | ||
|
|
5f24c40d05 | ||
|
|
1b3b82a3c1 | ||
|
|
9e72396709 | ||
|
|
125ee0b446 | ||
|
|
3bfb580d7a | ||
|
|
b54d47796d | ||
|
|
b5d0d6ff3e | ||
|
|
c9520deef8 | ||
|
|
ceab0f8c40 | ||
|
|
b87757c552 | ||
|
|
1972eb952d | ||
|
|
f92803d80e | ||
|
|
8afeb910bd | ||
|
|
f16ed98af4 | ||
|
|
2a9916103b | ||
|
|
63ad47a7bf | ||
|
|
27e6548343 | ||
|
|
8c91986dd3 | ||
|
|
5a65347c4e | ||
|
|
635180c8be | ||
|
|
019ce9862f | ||
|
|
c7f46876a7 | ||
|
|
c916eb6137 | ||
|
|
67cca71524 | ||
|
|
1eff7ae004 | ||
|
|
982ad46c83 | ||
|
|
657b4fd59e | ||
|
|
b371b406d5 | ||
|
|
91591c4e94 | ||
|
|
078b9719cc | ||
|
|
41d9826ee9 | ||
|
|
0152508a39 | ||
|
|
cdeb1c3828 | ||
|
|
0986fae066 | ||
|
|
277515636d | ||
|
|
45aacd8407 | ||
|
|
e8cdf8b092 | ||
|
|
1c4d6e7613 | ||
|
|
04b4e48265 | ||
|
|
dabcb1834d | ||
|
|
629cd86c7c | ||
|
|
ba704c4eb4 | ||
|
|
0487876826 | ||
|
|
ca9c05368e | ||
|
|
0d634a0061 | ||
|
|
86748fbc68 | ||
|
|
599477d754 | ||
|
|
889258f538 | ||
|
|
ffe2143d14 | ||
|
|
98470393d1 | ||
|
|
4192d04a53 | ||
|
|
3571dcd68c | ||
|
|
952edcb804 | ||
|
|
10a94c0794 | ||
|
|
dd32ace9ab | ||
|
|
7e0c6ee097 | ||
|
|
b1be34c6a0 | ||
|
|
b9307a9967 | ||
|
|
b1d353180b | ||
|
|
bd99eb1347 | ||
|
|
f79c01b0cf | ||
|
|
3f1e09ec0e | ||
|
|
05125029c6 | ||
|
|
05fe983757 | ||
|
|
d1a7749df5 | ||
|
|
9d63fc7ca0 | ||
|
|
38fccebe1f | ||
|
|
c05952b813 | ||
|
|
932cbe40bf | ||
|
|
39573d65d0 | ||
|
|
6db308f1e9 | ||
|
|
6f70fd90b9 | ||
|
|
db038d943c | ||
|
|
c5620efc9c | ||
|
|
8fb7aa5cef | ||
|
|
6ddee9a8ac | ||
|
|
5700359a61 | ||
|
|
0861a353d6 | ||
|
|
f79efec7ef | ||
|
|
77d5d20391 | ||
|
|
165e51d850 | ||
|
|
1c38f113b3 | ||
|
|
66859607a3 | ||
|
|
edf396b1a4 | ||
|
|
cbeb51b436 | ||
|
|
45e4fd6ec1 | ||
|
|
3a077ff211 | ||
|
|
be850e483a | ||
|
|
33fb62bb25 | ||
|
|
66c117e599 | ||
|
|
8aa1be8336 | ||
|
|
037d4121a1 | ||
|
|
fbd3918d3a | ||
|
|
7c543a43a9 | ||
|
|
ee3996cac6 | ||
|
|
96809ac659 | ||
|
|
6343059bc0 | ||
|
|
5a78fe33e8 | ||
|
|
676fb947f2 | ||
|
|
d18afb4a50 | ||
|
|
fc336a76bf | ||
|
|
b9f3d333e4 | ||
|
|
3d85af04be | ||
|
|
471b1e003a | ||
|
|
be2e17d605 | ||
|
|
9d6b9797c0 | ||
|
|
3cd6a18048 | ||
|
|
c9558c5f0e | ||
|
|
71e3d25422 | ||
|
|
644dc13e00 | ||
|
|
47e7aa4209 | ||
|
|
1c97a310b1 | ||
|
|
d612a1b5a1 | ||
|
|
386ead15a3 | ||
|
|
83c95e774d | ||
|
|
e4754999b7 | ||
|
|
c66489dbb1 | ||
|
|
21f9aefc7e | ||
|
|
d103dc2631 | ||
|
|
cac627ed05 | ||
|
|
e2cf2a5754 | ||
|
|
658a9a4ea8 | ||
|
|
a2db4c0a32 | ||
|
|
02a34a167a | ||
|
|
bea60930b6 | ||
|
|
0f7c82d38f | ||
|
|
6ba583d198 | ||
|
|
89d4c06dec | ||
|
|
9c72cc2a98 | ||
|
|
412bd94ea4 | ||
|
|
514ab043f2 | ||
|
|
68100102be | ||
|
|
05fdcb5b27 | ||
|
|
7a49c88200 | ||
|
|
40232d48ba | ||
|
|
55dff53a98 | ||
|
|
6919c2fb2d | ||
|
|
3d5d05eac7 | ||
|
|
dd866a74a6 | ||
|
|
b1e22aa559 | ||
|
|
2191e9ecd5 | ||
|
|
bf62103417 | ||
|
|
4b1b314ce2 | ||
|
|
c1ea0dde92 | ||
|
|
5a6cfc314e | ||
|
|
a4121a2c2e | ||
|
|
03335cef85 | ||
|
|
ff66bdda7c | ||
|
|
6cfddfea21 | ||
|
|
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 | ||
|
|
026b331ba5 | ||
|
|
93c36ccf78 | ||
|
|
76645f3b5a | ||
|
|
5cc84f32db | ||
|
|
e89674d337 | ||
|
|
de120280e3 | ||
|
|
7f2ba0ee3e | ||
|
|
ea73dac753 | ||
|
|
2a35646520 | ||
|
|
bb285984da | ||
|
|
1596cc5d9e | ||
|
|
8f66d96915 | ||
|
|
8ad078b964 | ||
|
|
d5368d7979 | ||
|
|
9c5657b86c | ||
|
|
b79089ea57 | ||
|
|
c5703eb00a | ||
|
|
eea9530f38 | ||
|
|
a0f280e71f | ||
|
|
cf28751ae3 | ||
|
|
b571362bf1 | ||
|
|
5d31f73302 | ||
|
|
7de1261555 | ||
|
|
91a511ba8c | ||
|
|
94f6294c0a | ||
|
|
77cd3adb01 | ||
|
|
7dabad4d71 | ||
|
|
54b4074369 | ||
|
|
e4d8e22846 | ||
|
|
182beb4f8b | ||
|
|
2690306f4a | ||
|
|
b59e3c670b | ||
|
|
f2b6ef2edd | ||
|
|
1409f83fb9 | ||
|
|
37dadab745 | ||
|
|
70c7382a09 | ||
|
|
9f801cf79e | ||
|
|
fe5e300062 | ||
|
|
9e25561edf | ||
|
|
7c6bdcc459 | ||
|
|
b09629f1d4 | ||
|
|
85446d81f3 | ||
|
|
96786bbb87 | ||
|
|
a5166baba2 | ||
|
|
d1deba8620 | ||
|
|
aaee72422a | ||
|
|
3dd0e31cc4 | ||
|
|
283a8dec37 | ||
|
|
37d354cf7f | ||
|
|
f698d451df | ||
|
|
c088f8bd03 | ||
|
|
f44e98ddc9 | ||
|
|
fd4db4000c |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[alias]
|
||||
run-wasm = ["run", "--release", "--package", "run-wasm", "--"]
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -20,5 +20,3 @@
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
/CHANGELOG.md merge=union
|
||||
|
||||
35
.github/CODEOWNERS
vendored
Normal file
35
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# Core maintainers:
|
||||
# - @msiglreith
|
||||
# - @kchibisov
|
||||
# - @madsmtm
|
||||
# - @maroider
|
||||
|
||||
# Android
|
||||
/src/platform/android.rs @msiglreith
|
||||
/src/platform_impl/android @msiglreith
|
||||
|
||||
# iOS
|
||||
/src/platform/ios.rs @francesca64
|
||||
/src/platform_impl/ios @francesca64
|
||||
|
||||
# Unix in general
|
||||
/src/platform/unix.rs @kchibisov
|
||||
/src/platform_impl/linux/mod.rs @kchibisov
|
||||
|
||||
# Wayland
|
||||
/src/platform_impl/linux/wayland @kchibisov
|
||||
|
||||
# X11
|
||||
/src/platform_impl/linux/x11 @kchibisov
|
||||
|
||||
# macOS
|
||||
/src/platform/macos.rs @madsmtm
|
||||
/src/platform_impl/macos @madsmtm
|
||||
|
||||
# Web (no maintainer)
|
||||
/src/platform/web.rs
|
||||
/src/platform_impl/web
|
||||
|
||||
# Windows
|
||||
/src/platform/windows.rs @msiglreith
|
||||
/src/platform_impl/windows @msiglreith
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,5 +1,4 @@
|
||||
- [ ] Tested on all platforms changed
|
||||
- [ ] `cargo fmt` has been run on this branch
|
||||
- [ ] 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
|
||||
|
||||
110
.github/workflows/ci.yml
vendored
Normal file
110
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
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:
|
||||
name: Tests
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust_version: [1.57.0, stable, nightly]
|
||||
platform:
|
||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||
- { target: i686-pc-windows-msvc, os: windows-latest, }
|
||||
- { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
|
||||
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
|
||||
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
|
||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
|
||||
- { target: aarch64-linux-android, os: ubuntu-latest, 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, }
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_INCREMENTAL: 0
|
||||
PKG_CONFIG_ALLOW_CROSS: 1
|
||||
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
|
||||
OPTIONS: ${{ matrix.platform.options }}
|
||||
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
|
||||
CMD: ${{ matrix.platform.cmd }}
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@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 }}
|
||||
components: clippy
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: (matrix.platform.os == 'ubuntu-latest')
|
||||
run: sudo apt-get update && sudo apt-get install pkg-config cmake libfreetype6-dev libfontconfig1-dev
|
||||
|
||||
- name: Install GCC Multilib
|
||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install g++-multilib gcc-multilib libfreetype6-dev:i386 libfontconfig1-dev:i386
|
||||
- name: Install cargo-apk
|
||||
if: contains(matrix.platform.target, 'android')
|
||||
run: cargo install cargo-apk
|
||||
|
||||
- name: Check documentation
|
||||
shell: bash
|
||||
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
|
||||
|
||||
- name: Build tests
|
||||
shell: bash
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
if: (
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32'))
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
||||
|
||||
- name: Lint with clippy
|
||||
shell: bash
|
||||
if: (matrix.rust_version == '1.57.0') && !contains(matrix.platform.options, '--no-default-features')
|
||||
run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings
|
||||
|
||||
- name: Build tests with serde enabled
|
||||
shell: bash
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
|
||||
- name: Run tests with serde enabled
|
||||
shell: bash
|
||||
if: (
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32'))
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,4 +3,8 @@ target/
|
||||
rls/
|
||||
.vscode/
|
||||
*~
|
||||
*.wasm
|
||||
*.ts
|
||||
*.js
|
||||
#*#
|
||||
.DS_Store
|
||||
62
.travis.yml
62
.travis.yml
@@ -1,62 +0,0 @@
|
||||
language: rust
|
||||
|
||||
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
|
||||
- rustup component add rustfmt
|
||||
|
||||
script:
|
||||
- cargo fmt --all -- --check
|
||||
- cargo build --target $TARGET --verbose
|
||||
- cargo build --target $TARGET --features serde --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
|
||||
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features serde --verbose; fi
|
||||
|
||||
after_success:
|
||||
- |
|
||||
[ $TRAVIS_BRANCH = master ] &&
|
||||
[ $TRAVIS_PULL_REQUEST = false ] &&
|
||||
cargo publish --token ${CRATESIO_TOKEN}
|
||||
475
CHANGELOG.md
475
CHANGELOG.md
@@ -1,6 +1,475 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
Please keep one empty line before and after all headers. (This is required for `git` to produce a conflict when a release is made while a PR is open and the PR's changelog entry would go into the wrong section).
|
||||
|
||||
And please only add new entries to the top of this list, right below the `# Unreleased` header.
|
||||
|
||||
# Unreleased
|
||||
|
||||
# 0.20.0 Alpha 2 (2019-06-28)
|
||||
# 0.27.5
|
||||
|
||||
- On Wayland, fix byte offset in `Ime::Preedit` pointing to invalid bytes.
|
||||
|
||||
# 0.27.4
|
||||
|
||||
- On Windows, emit `ReceivedCharacter` events on system keybindings.
|
||||
- On Windows, fixed focus event emission on minimize.
|
||||
- On X11, fixed IME crashing during reload.
|
||||
|
||||
# 0.27.3 (2022-9-10)
|
||||
|
||||
- On Windows, added `WindowExtWindows::set_undecorated_shadow` and `WindowBuilderExtWindows::with_undecorated_shadow` to draw the drop shadow behind a borderless window.
|
||||
- On Windows, fixed default window features (ie snap, animations, shake, etc.) when decorations are disabled.
|
||||
- On Windows, fixed ALT+Space shortcut to open window menu.
|
||||
- On Wayland, fixed `Ime::Preedit` not being sent on IME reset.
|
||||
- Fixed unbound version specified for `raw-window-handle` leading to compilation failures.
|
||||
- Empty `Ime::Preedit` event will be sent before `Ime::Commit` to help clearing preedit.
|
||||
- On X11, fixed IME context picking by querying for supported styles beforehand.
|
||||
|
||||
# 0.27.2 (2022-8-12)
|
||||
|
||||
- On macOS, fixed touch phase reporting when scrolling.
|
||||
- On X11, fix min, max and resize increment hints not persisting for resizable windows (e.g. on DPI change).
|
||||
- On Windows, respect min/max inner sizes when creating the window.
|
||||
- For backwards compatibility, `Window` now (additionally) implements the old version (`0.4`) of the `HasRawWindowHandle` trait
|
||||
- On Windows, added support for `EventLoopWindowTarget::set_device_event_filter`.
|
||||
- On Wayland, fix user requested `WindowEvent::RedrawRequested` being delayed by a frame.
|
||||
|
||||
# 0.27.1 (2022-07-30)
|
||||
|
||||
- The minimum supported Rust version was lowered to `1.57.0` and now explicitly tested.
|
||||
- On X11, fix crash on start due to inability to create an IME context without any preedit.
|
||||
|
||||
# 0.27.0 (2022-07-26)
|
||||
|
||||
- On Windows, fix hiding a maximized window.
|
||||
- On Android, `ndk-glue`'s `NativeWindow` lock is now held between `Event::Resumed` and `Event::Suspended`.
|
||||
- On Web, added `EventLoopExtWebSys` with a `spawn` method to start the event loop without throwing an exception.
|
||||
- Added `WindowEvent::Occluded(bool)`, currently implemented on macOS and X11.
|
||||
- On X11, fix events for caps lock key not being sent
|
||||
- Build docs on `docs.rs` for iOS and Android as well.
|
||||
- **Breaking:** Removed the `WindowAttributes` struct, since all its functionality is accessible from `WindowBuilder`.
|
||||
- Added `WindowBuilder::transparent` getter to check if the user set `transparent` attribute.
|
||||
- On macOS, Fix emitting `Event::LoopDestroyed` on CMD+Q.
|
||||
- On macOS, fixed an issue where having multiple windows would prevent run_return from ever returning.
|
||||
- On Wayland, fix bug where the cursor wouldn't hide in GNOME.
|
||||
- On macOS, Windows, and Wayland, add `set_cursor_hittest` to let the window ignore mouse events.
|
||||
- On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`.
|
||||
- On Windows, added `EventLoopBuilderExtWindows::with_msg_hook`.
|
||||
- On Windows, remove internally unique DC per window.
|
||||
- On macOS, remove the need to call `set_ime_position` after moving the window.
|
||||
- Added `Window::is_visible`.
|
||||
- Added `Window::is_resizable`.
|
||||
- Added `Window::is_decorated`.
|
||||
- On X11, fix for repeated event loop iteration when `ControlFlow` was `Wait`
|
||||
- On X11, fix scale factor calculation when the only monitor is reconnected
|
||||
- On Wayland, report unaccelerated mouse deltas in `DeviceEvent::MouseMotion`.
|
||||
- On Web, a focused event is manually generated when a click occurs to emulate behaviour of other backends.
|
||||
- **Breaking:** Bump `ndk` version to 0.6, ndk-sys to `v0.3`, `ndk-glue` to `0.6`.
|
||||
- Remove no longer needed `WINIT_LINK_COLORSYNC` environment variable.
|
||||
- **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs.
|
||||
- Add `EventLoopBuilder`, which allows you to create and tweak the settings of an event loop before creating it.
|
||||
- Deprecated `EventLoop::with_user_event`; use `EventLoopBuilder::with_user_event` instead.
|
||||
- **Breaking:** Replaced `EventLoopExtMacOS` with `EventLoopBuilderExtMacOS` (which also has renamed methods).
|
||||
- **Breaking:** Replaced `EventLoopExtWindows` with `EventLoopBuilderExtWindows` (which also has renamed methods).
|
||||
- **Breaking:** Replaced `EventLoopExtUnix` with `EventLoopBuilderExtUnix` (which also has renamed methods).
|
||||
- **Breaking:** The platform specific extensions for Windows `winit::platform::windows` have changed. All `HANDLE`-like types e.g. `HWND` and `HMENU` were converted from winapi types or `*mut c_void` to `isize`. This was done to be consistent with the type definitions in windows-sys and to not expose internal dependencies.
|
||||
- The internal bindings to the [Windows API](https://docs.microsoft.com/en-us/windows/) were changed from the unofficial [winapi](https://github.com/retep998/winapi-rs) bindings to the official Microsoft [windows-sys](https://github.com/microsoft/windows-rs) bindings.
|
||||
- On Wayland, fix polling during consecutive `EventLoop::run_return` invocations.
|
||||
- On Windows, fix race issue creating fullscreen windows with `WindowBuilder::with_fullscreen`
|
||||
- On Android, `virtual_keycode` for `KeyboardInput` events is now filled in where a suitable match is found.
|
||||
- Added helper methods on `ControlFlow` to set its value.
|
||||
- On Wayland, fix `TouchPhase::Ended` always reporting the location of the first touch down, unless the compositor
|
||||
sent a cancel or frame event.
|
||||
- On iOS, send `RedrawEventsCleared` even if there are no redraw events, consistent with other platforms.
|
||||
- **Breaking:** Replaced `Window::with_app_id` and `Window::with_class` with `Window::with_name` on `WindowBuilderExtUnix`.
|
||||
- On Wayland, fallback CSD was replaced with proper one:
|
||||
- `WindowBuilderExtUnix::with_wayland_csd_theme` to set color theme in builder.
|
||||
- `WindowExtUnix::wayland_set_csd_theme` to set color theme when creating a window.
|
||||
- `WINIT_WAYLAND_CSD_THEME` env variable was added, it can be used to set "dark"/"light" theme in apps that don't expose theme setting.
|
||||
- `wayland-csd-adwaita` feature that enables proper CSD with title rendering using FreeType system library.
|
||||
- `wayland-csd-adwaita-notitle` feature that enables CSD but without title rendering.
|
||||
- On Wayland and X11, fix window not resizing with `Window::set_inner_size` after calling `Window:set_resizable(false)`.
|
||||
- On Windows, fix wrong fullscreen monitors being recognized when handling WM_WINDOWPOSCHANGING messages
|
||||
- **Breaking:** Added new `WindowEvent::Ime` supported on desktop platforms.
|
||||
- Added `Window::set_ime_allowed` supported on desktop platforms.
|
||||
- **Breaking:** IME input on desktop platforms won't be received unless it's explicitly allowed via `Window::set_ime_allowed` and new `WindowEvent::Ime` events are handled.
|
||||
- On macOS, `WindowEvent::Resized` is now emitted in `frameDidChange` instead of `windowDidResize`.
|
||||
- **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level.
|
||||
- Implemented `Default` on `EventLoop<()>`.
|
||||
- Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`.
|
||||
- **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior.
|
||||
- On Wayland, add support for `Window::set_cursor_position`.
|
||||
- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value.
|
||||
- `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once.
|
||||
- Added `From<u64>` for `WindowId` and `From<WindowId>` for `u64`.
|
||||
- Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate.
|
||||
- **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision.
|
||||
- On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated.
|
||||
- On Windows, fix focus events being sent to inactive windows.
|
||||
- **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`.
|
||||
- On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors comming from Xlib.
|
||||
- On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`.
|
||||
- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability.
|
||||
- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down.
|
||||
|
||||
# 0.26.1 (2022-01-05)
|
||||
|
||||
- Fix linking to the `ColorSync` framework on macOS 10.7, and in newer Rust versions.
|
||||
- On Web, implement cursor grabbing through the pointer lock API.
|
||||
- On X11, add mappings for numpad comma, numpad enter, numlock and pause.
|
||||
- On macOS, fix Pinyin IME input by reverting a change that intended to improve IME.
|
||||
- On Windows, fix a crash with transparent windows on Windows 11.
|
||||
|
||||
# 0.26.0 (2021-12-01)
|
||||
|
||||
- Update `raw-window-handle` to `v0.4`. This is _not_ a breaking change, we still implement `HasRawWindowHandle` from `v0.3`, see [rust-windowing/raw-window-handle#74](https://github.com/rust-windowing/raw-window-handle/pull/74). Note that you might have to run `cargo update -p raw-window-handle` after upgrading.
|
||||
- On X11, bump `mio` to 0.8.
|
||||
- On Android, fixed `WindowExtAndroid::config` initially returning an empty `Configuration`.
|
||||
- On Android, fixed `Window::scale_factor` and `MonitorHandle::scale_factor` initially always returning 1.0.
|
||||
- On X11, select an appropriate visual for transparency if is requested
|
||||
- On Wayland and X11, fix diagonal window resize cursor orientation.
|
||||
- On macOS, drop the event callback before exiting.
|
||||
- On Android, implement `Window::request_redraw`
|
||||
- **Breaking:** On Web, remove the `stdweb` backend.
|
||||
- Added `Window::focus_window`to bring the window to the front and set input focus.
|
||||
- On Wayland and X11, implement `is_maximized` method on `Window`.
|
||||
- On Windows, prevent ghost window from showing up in the taskbar after either several hours of use or restarting `explorer.exe`.
|
||||
- On macOS, fix issue where `ReceivedCharacter` was not being emitted during some key repeat events.
|
||||
- On Wayland, load cursor icons `hand2` and `hand1` for `CursorIcon::Hand`.
|
||||
- **Breaking:** On Wayland, Theme trait and its support types are dropped.
|
||||
- On Wayland, bump `smithay-client-toolkit` to 0.15.1.
|
||||
- On Wayland, implement `request_user_attention` with `xdg_activation_v1`.
|
||||
- On X11, emit missing `WindowEvent::ScaleFactorChanged` when the only monitor gets reconnected.
|
||||
- On X11, if RANDR based scale factor is higher than 20 reset it to 1
|
||||
- On Wayland, add an enabled-by-default feature called `wayland-dlopen` so users can opt out of using `dlopen` to load system libraries.
|
||||
- **Breaking:** On Android, bump `ndk` and `ndk-glue` to 0.5.
|
||||
- On Windows, increase wait timer resolution for more accurate timing when using `WaitUntil`.
|
||||
- On macOS, fix native file dialogs hanging the event loop.
|
||||
- On Wayland, implement a workaround for wrong configure size when using `xdg_decoration` in `kwin_wayland`
|
||||
- On macOS, fix an issue that prevented the menu bar from showing in borderless fullscreen mode.
|
||||
- On X11, EINTR while polling for events no longer causes a panic. Instead it will be treated as a spurious wakeup.
|
||||
|
||||
# 0.25.0 (2021-05-15)
|
||||
|
||||
- **Breaking:** On macOS, replace `WindowBuilderExtMacOS::with_activation_policy` with `EventLoopExtMacOS::set_activation_policy`
|
||||
- On macOS, wait with activating the application until the application has initialized.
|
||||
- On macOS, fix creating new windows when the application has a main menu.
|
||||
- On Windows, fix fractional deltas for mouse wheel device events.
|
||||
- On macOS, fix segmentation fault after dropping the main window.
|
||||
- On Android, `InputEvent::KeyEvent` is partially implemented providing the key scancode.
|
||||
- Added `is_maximized` method to `Window`.
|
||||
- On Windows, fix bug where clicking the decoration bar would make the cursor blink.
|
||||
- On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor.
|
||||
- On macOS, wake up the event loop immediately when a redraw is requested.
|
||||
- On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600).
|
||||
- On Windows, fix bug causing mouse capture to not be released.
|
||||
- On Windows, fix fullscreen not preserving minimized/maximized state.
|
||||
- On Android, unimplemented events are marked as unhandled on the native event loop.
|
||||
- On Windows, added `WindowBuilderExtWindows::with_menu` to set a custom menu at window creation time.
|
||||
- On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`.
|
||||
- On macOS, fix objects captured by the event loop closure not being dropped on panic.
|
||||
- On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme.
|
||||
- On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements.
|
||||
- Added `WindowBuilder::with_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11.
|
||||
- Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland.
|
||||
- On X11, bump `mio` to 0.7.
|
||||
- On Windows, added `WindowBuilderExtWindows::with_owner_window` to allow creating popup windows.
|
||||
- On Windows, added `WindowExtWindows::set_enable` to allow creating modal popup windows.
|
||||
- On macOS, emit `RedrawRequested` events immediately while the window is being resized.
|
||||
- Implement `Default`, `Hash`, and `Eq` for `LogicalPosition`, `PhysicalPosition`, `LogicalSize`, and `PhysicalSize`.
|
||||
- On macOS, initialize the Menu Bar with minimal defaults. (Can be prevented using `enable_default_menu_creation`)
|
||||
- On macOS, change the default behavior for first click when the window was unfocused. Now the window becomes focused and then emits a `MouseInput` event on a "first mouse click".
|
||||
- Implement mint (math interoperability standard types) conversions (under feature flag `mint`).
|
||||
|
||||
# 0.24.0 (2020-12-09)
|
||||
|
||||
- On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory.
|
||||
- On Windows, implement `Window::set_ime_position`.
|
||||
- **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`.
|
||||
- **Breaking:** On Windows, renamed `WindowBuilderExtWindows::is_dark_mode` to `theme`.
|
||||
- On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme.
|
||||
- On Windows, fix bug causing message boxes to appear delayed.
|
||||
- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false.
|
||||
- On Windows, fix Alt-Tab behaviour by removing borderless fullscreen "always on top" flag.
|
||||
- On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions.
|
||||
- **Breaking:** On Windows, include prefix byte in scancodes.
|
||||
- On Wayland, fix window not being resizeable when using `WindowBuilder::with_min_inner_size`.
|
||||
- On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland.
|
||||
- On Windows, fix use-after-free crash during window destruction.
|
||||
- On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input.
|
||||
- On macOS, fix compilation when targeting aarch64.
|
||||
- On X11, fix `Window::request_redraw` not waking the event loop.
|
||||
- On Wayland, the keypad arrow keys are now recognized.
|
||||
- **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`.
|
||||
- Added `request_user_attention` method to `Window`.
|
||||
- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`.
|
||||
- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`.
|
||||
- On Wayland, default font size in CSD increased from 11 to 17.
|
||||
- On Windows, fix bug causing message boxes to appear delayed.
|
||||
- On Android, support multi-touch.
|
||||
- On Wayland, extra mouse buttons are not dropped anymore.
|
||||
- **Breaking**: `MouseButton::Other` now uses `u16`.
|
||||
|
||||
# 0.23.0 (2020-10-02)
|
||||
|
||||
- On iOS, fixed support for the "Debug View Heirarchy" feature in Xcode.
|
||||
- On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop.
|
||||
- On Unix, X11 and Wayland are now optional features (enabled by default)
|
||||
- On X11, fix deadlock when calling `set_fullscreen_inner`.
|
||||
- On Web, prevent the webpage from scrolling when the user is focused on a winit canvas
|
||||
- On Web, calling `window.set_cursor_icon` no longer breaks HiDPI scaling
|
||||
- On Windows, drag and drop is now optional (enabled by default) and can be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`.
|
||||
- On Wayland, fix deadlock when calling to `set_inner_size` from a callback.
|
||||
- On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`.
|
||||
- On android added support for `run_return`.
|
||||
- On MacOS, Fixed fullscreen and dialog support for `run_return`.
|
||||
- On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops.
|
||||
- On Web, use mouse events if pointer events aren't supported. This affects Safari.
|
||||
- On Windows, `set_ime_position` is now a no-op instead of a runtime crash.
|
||||
- On Android, `set_fullscreen` is now a no-op instead of a runtime crash.
|
||||
- On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash.
|
||||
- On Android, fix `ControlFlow::Poll` not polling the Android event queue.
|
||||
- On macOS, add `NSWindow.hasShadow` support.
|
||||
- On Web, fix vertical mouse wheel scrolling being inverted.
|
||||
- On Web, implement mouse capturing for click-dragging out of the canvas.
|
||||
- On Web, fix `ControlFlow::Exit` not properly handled.
|
||||
- On Web (web-sys only), send `WindowEvent::ScaleFactorChanged` event when `window.devicePixelRatio` is changed.
|
||||
- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error.
|
||||
- **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`.
|
||||
- On NetBSD, fixed crash due to incorrect detection of the main thread.
|
||||
- **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract`.
|
||||
- On macOS, fix inverted horizontal scroll.
|
||||
- **Breaking:** `current_monitor` now returns `Option<MonitorHandle>`.
|
||||
- **Breaking:** `primary_monitor` now returns `Option<MonitorHandle>`.
|
||||
- On macOS, updated core-* dependencies and cocoa.
|
||||
- Bump `parking_lot` to 0.11
|
||||
- On Android, bump `ndk`, `ndk-sys` and `ndk-glue` to 0.2. Checkout the new ndk-glue main proc attribute.
|
||||
- On iOS, fixed starting the app in landscape where the view still had portrait dimensions.
|
||||
- Deprecate the stdweb backend, to be removed in a future release
|
||||
- **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`.
|
||||
- Added `Asterisk` and `Plus` virtual key codes.
|
||||
- On Web (web-sys only), the `Event::LoopDestroyed` event is correctly emitted when leaving the page.
|
||||
- On Web, the `WindowEvent::Destroyed` event now gets emitted when a `Window` is dropped.
|
||||
- On Web (web-sys only), the event listeners are now removed when a `Window` is dropped or when the event loop is destroyed.
|
||||
- On Web, the event handler closure passed to `EventLoop::run` now gets dropped after the event loop is destroyed.
|
||||
- **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped.
|
||||
- On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called.
|
||||
- **Breaking:** `Fullscreen` enum now uses `Borderless(Option<MonitorHandle>)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor.
|
||||
- On MacOS, fix `WindowEvent::Moved` ignoring the scale factor.
|
||||
- On Wayland, add missing virtual keycodes.
|
||||
- On Wayland, implement proper `set_cursor_grab`.
|
||||
- On Wayland, the cursor will use similar icons if the requested one isn't available.
|
||||
- On Wayland, right clicking on client side decorations will request application menu.
|
||||
- On Wayland, fix tracking of window size after state changes.
|
||||
- On Wayland, fix client side decorations not being hidden properly in fullscreen.
|
||||
- On Wayland, fix incorrect size event when entering fullscreen with client side decorations.
|
||||
- On Wayland, fix `resizable` attribute not being applied properly on startup.
|
||||
- On Wayland, fix disabled repeat rate not being handled.
|
||||
- On Wayland, fix decoration buttons not working after tty switch.
|
||||
- On Wayland, fix scaling not being applied on output re-enable.
|
||||
- On Wayland, fix crash when `XCURSOR_SIZE` is `0`.
|
||||
- On Wayland, fix pointer getting created in some cases without pointer capability.
|
||||
- On Wayland, on kwin, fix space between window and decorations on startup.
|
||||
- **Breaking:** On Wayland, `Theme` trait was reworked.
|
||||
- On Wayland, disable maximize button for non-resizable window.
|
||||
- On Wayland, added support for `set_ime_position`.
|
||||
- On Wayland, fix crash on startup since GNOME 3.37.90.
|
||||
- On X11, fix incorrect modifiers state on startup.
|
||||
|
||||
# 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`
|
||||
@@ -179,7 +648,7 @@ and `WindowEvent::HoveredFile`.
|
||||
# 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.
|
||||
- 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)
|
||||
@@ -329,7 +798,7 @@ and `WindowEvent::HoveredFile`.
|
||||
|
||||
# Version 0.10.1 (2018-02-05)
|
||||
|
||||
*Yanked*
|
||||
_Yanked_
|
||||
|
||||
# Version 0.10.0 (2017-12-27)
|
||||
|
||||
|
||||
@@ -20,12 +20,16 @@ your description of the issue as detailed as possible:
|
||||
|
||||
When making a code contribution to winit, before opening your pull request, please make sure that:
|
||||
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.57.0.
|
||||
- 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
|
||||
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users.
|
||||
|
||||
You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will
|
||||
handle those for you when merging (see below).
|
||||
- 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.
|
||||
@@ -34,9 +38,25 @@ Once your PR is open, you can ask for review by a maintainer of your platform. W
|
||||
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).
|
||||
|
||||
Once your PR is deemed ready, the merging maintainer will take care of resolving conflicts in
|
||||
`CHANGELOG.md` (but you must resolve other conflicts yourself). Doing this requires that you check the
|
||||
"give contributors write access to the branch" checkbox when creating the PR.
|
||||
|
||||
## 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.
|
||||
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
|
||||
|
||||
If you are interested in contributing or testing on a platform, please add yourself to that table!
|
||||
If you are interested in being pinged when testing is needed for a certain platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
|
||||
|
||||
## Making a new release
|
||||
|
||||
If you believe a new release is warranted, you can make a pull-request with:
|
||||
- An updated version number (remember to change the version everywhere it is used).
|
||||
- A new section in the changelog (below the `# Unreleased` section).
|
||||
|
||||
This gives contributors an opportunity to squeeze in an extra PR or two that they feel is valuable
|
||||
enough to warrant blocking the release a little.
|
||||
|
||||
Once the PR is merged, a maintainer will create a new tag matching the version name (e.g. `v0.26.1`),
|
||||
and a CI job will automatically release the new version. Remember that the release date in the
|
||||
changelog must be kept in check with the actual release date.
|
||||
|
||||
169
Cargo.toml
169
Cargo.toml
@@ -1,81 +1,150 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.20.0-alpha2"
|
||||
version = "0.27.5"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
keywords = ["windowing"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
rust-version = "1.57.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["serde"]
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
# These are all tested in CI
|
||||
targets = [
|
||||
# Windows
|
||||
"i686-pc-windows-msvc",
|
||||
"x86_64-pc-windows-msvc",
|
||||
# macOS
|
||||
"x86_64-apple-darwin",
|
||||
# Unix (X11 & Wayland)
|
||||
"i686-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
# iOS
|
||||
"x86_64-apple-ios",
|
||||
# Android
|
||||
"aarch64-linux-android",
|
||||
# WebAssembly
|
||||
"wasm32-unknown-unknown",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
x11 = ["x11-dl", "mio", "percent-encoding", "parking_lot"]
|
||||
wayland = ["wayland-client", "wayland-protocols", "sctk"]
|
||||
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
|
||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/title"]
|
||||
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1"
|
||||
libc = "0.2"
|
||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
||||
once_cell = "1.12"
|
||||
log = "0.4"
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
derivative = "1.0.2"
|
||||
raw_window_handle = { package = "raw-window-handle", version = "0.5" }
|
||||
raw_window_handle_04 = { package = "raw-window-handle", version = "0.4.3" }
|
||||
bitflags = "1"
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
image = "0.21"
|
||||
env_logger = "0.5"
|
||||
image = { version = "0.24.0", default-features = false, features = ["png"] }
|
||||
simple_logger = { version = "2.1.0", default_features = false }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies.android_glue]
|
||||
version = "0.2"
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
|
||||
ndk = "0.7.0"
|
||||
ndk-glue = "0.7.0"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
objc = "0.2.3"
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
objc = "0.2.7"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa = "0.18.4"
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.17.3"
|
||||
dispatch = "0.1.4"
|
||||
objc = "0.2.3"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
|
||||
version = "0.1.3"
|
||||
default_features = false
|
||||
features = ["display_link"]
|
||||
cocoa = "0.24"
|
||||
core-foundation = "0.9"
|
||||
core-graphics = "0.22"
|
||||
dispatch = "0.2.0"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
bitflags = "1"
|
||||
parking_lot = "0.12"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
||||
version = "0.3.6"
|
||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||
version = "0.36"
|
||||
features = [
|
||||
"combaseapi",
|
||||
"commctrl",
|
||||
"dwmapi",
|
||||
"errhandlingapi",
|
||||
"hidusage",
|
||||
"libloaderapi",
|
||||
"objbase",
|
||||
"ole2",
|
||||
"processthreadsapi",
|
||||
"shellapi",
|
||||
"shellscalingapi",
|
||||
"shobjidl_core",
|
||||
"unknwnbase",
|
||||
"winbase",
|
||||
"windowsx",
|
||||
"winerror",
|
||||
"wingdi",
|
||||
"winnt",
|
||||
"winuser",
|
||||
"Win32_Devices_HumanInterfaceDevice",
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Media",
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Ole",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_WindowsProgramming",
|
||||
"Win32_UI_Accessibility",
|
||||
"Win32_UI_Controls",
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_Input_Ime",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Input_Pointer",
|
||||
"Win32_UI_Input_Touch",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_TextServices",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
]
|
||||
|
||||
[target.'cfg(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"] }
|
||||
calloop = "0.4.2"
|
||||
smithay-client-toolkit = "0.6"
|
||||
x11-dl = "2.18.3"
|
||||
percent-encoding = "1.0"
|
||||
wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true }
|
||||
wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true }
|
||||
sctk-adwaita = { version = "0.4.1", optional = true }
|
||||
mio = { version = "0.8", features = ["os-ext"], optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
parking_lot = { version = "0.12.0", optional = true }
|
||||
libc = "0.2.64"
|
||||
|
||||
[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.8"
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
|
||||
package = "web-sys"
|
||||
version = "0.3.22"
|
||||
features = [
|
||||
'console',
|
||||
"AddEventListenerOptions",
|
||||
'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"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
console_log = "0.2"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"run-wasm",
|
||||
]
|
||||
|
||||
110
FEATURES.md
110
FEATURES.md
@@ -12,7 +12,6 @@ be used to create both games and applications. It supports the main graphical pl
|
||||
- iOS
|
||||
- Android
|
||||
- Web
|
||||
- via Emscripten
|
||||
- via WASM
|
||||
|
||||
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
|
||||
@@ -81,9 +80,13 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **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
|
||||
@@ -97,22 +100,27 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
### 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 locking**: Locking the cursor inside the window so it cannot move.
|
||||
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
|
||||
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
|
||||
- **Cursor hittest**: Handle or ignore mouse events for a window.
|
||||
- **Touch events**: Single-touch events.
|
||||
- **Touch pressure**: Touch events contain information about the amount of force being applied.
|
||||
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
|
||||
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
|
||||
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
|
||||
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
|
||||
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
|
||||
- **Gamepad/Joystick events**: Capturing input from gampads and joysticks.
|
||||
- **Device movement events:**: Capturing input from the device gyroscope and accelerometer.
|
||||
- **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
|
||||
* Setting a menu bar
|
||||
* `WS_EX_NOREDIRECTIONBITMAP` support
|
||||
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
|
||||
|
||||
### macOS
|
||||
* Window activation policy
|
||||
@@ -129,6 +137,24 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
* 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)
|
||||
|
||||
@@ -143,55 +169,61 @@ Legend:
|
||||
- ❓: Unknown status
|
||||
|
||||
### Windowing
|
||||
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Emscripten|
|
||||
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |
|
||||
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |❓ |
|
||||
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |
|
||||
|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 |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|
||||
|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** |
|
||||
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
||||
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
||||
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|
||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|
||||
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |
|
||||
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|
|
||||
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|
|
||||
|Window decorations |✔️ |✔️ |✔️ |✔️ |**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]|✔️ |✔️ |
|
||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|
||||
|
||||
### System information
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|
||||
|---------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
||||
|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❌ |
|
||||
|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 |Emscripten|
|
||||
|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] |❌[#306] |**N/A**|**N/A**|✔️ |
|
||||
|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ |
|
||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |
|
||||
|Multitouch |❓ |❌ |✔️ |✔️ |❓ |❌ |❌ |
|
||||
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|
||||
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|
||||
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|
|
||||
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |
|
||||
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
||||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|
||||
|Cursor hittest |✔️ |✔️ |❌ |✔️ |**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 |❓ |❓ |❓ |❓ |❌ |❌ |❌ |
|
||||
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|
||||
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|
||||
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|
||||
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
||||
|
||||
### Pending API Reworks
|
||||
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|
||||
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|
||||
|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]) |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|
||||
|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 |Emscripten|
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|
||||
[#165]: https://github.com/rust-windowing/winit/issues/165
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
# 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
|
||||
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
|
||||
* [@tomaka]: For creating the winit project and guiding it through its early
|
||||
years of existence.
|
||||
* [@vberger]: For diligently creating the Wayland backend, and being its
|
||||
extremely helpful and benevolent maintainer for years.
|
||||
* [@francesca64]: For taking over the responsibility of maintaining almost every
|
||||
Winit backend, and standardizing HiDPI support across all of them
|
||||
winit backend, and standardizing HiDPI support across all of them.
|
||||
* [@Osspial]: For heroically landing EventLoop 2.0, and valiantly ushering in a
|
||||
vastly more sustainable era of winit.
|
||||
* [@goddessfreya]: For selflessly taking over maintainership of glutin, and her
|
||||
stellar dedication to improving both winit and glutin.
|
||||
* [@ArturKovacs]: For consistently maintaining the macOS backend, and his
|
||||
immense involvement in designing and implementing the new keyboard API.
|
||||
|
||||
[@tomaka]: https://github.com/tomaka
|
||||
[@vberger]: https://github.com/vberger
|
||||
[@francesca64]: https://github.com/francesca64
|
||||
[@Osspial]: https://github.com/Osspial
|
||||
[@goddessfreya]: https://github.com/goddessfreya
|
||||
[@ArturKovacs]: https://github.com/ArturKovacs
|
||||
|
||||
95
README.md
95
README.md
@@ -2,12 +2,11 @@
|
||||
|
||||
[](https://crates.io/crates/winit)
|
||||
[](https://docs.rs/winit)
|
||||
[](https://travis-ci.org/rust-windowing/winit)
|
||||
[](https://ci.appveyor.com/project/Osspial/winit/branch/master)
|
||||
[](https://github.com/rust-windowing/winit/actions)
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.20.0-alpha2"
|
||||
winit = "0.27.5"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -20,9 +19,8 @@ For features _outside_ the scope of winit, see [Missing features provided by oth
|
||||
|
||||
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)
|
||||
[](https://matrix.to/#/#rust-windowing:matrix.org)
|
||||
[](https://web.libera.chat/#winit)
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -46,12 +44,14 @@ fn main() {
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -63,15 +63,82 @@ Winit is only officially supported on the latest stable version of the Rust comp
|
||||
|
||||
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).
|
||||
* `x11` (enabled by default): On Unix platform, compiles with the X11 backend
|
||||
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
|
||||
* `mint`: Enables mint (math interoperability standard types) conversions.
|
||||
|
||||
### Platform-specific usage
|
||||
|
||||
#### Emscripten and WebAssembly
|
||||
#### Wayland
|
||||
|
||||
Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to:
|
||||
Note that windows don't appear on Wayland until you draw/present to them.
|
||||
|
||||
- Put a `<canvas id="my_id"></canvas>` element somewhere. A canvas corresponds to a winit "window".
|
||||
- Write a Javascript code that creates a global variable named `Module`. Set `Module.canvas` to
|
||||
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.
|
||||
`winit` doesn't do drawing, try the examples in [`glutin`] instead.
|
||||
|
||||
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||
|
||||
#### WebAssembly
|
||||
|
||||
To run the web example: `cargo run-wasm --example web`
|
||||
|
||||
Winit supports compiling to the `wasm32-unknown-unknown` target with `web-sys`.
|
||||
|
||||
On the web platform, a Winit window is backed by a `<canvas>` element. You can
|
||||
either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
|
||||
create a `<canvas>` element which you can then retrieve][web canvas getter] and
|
||||
insert it into the DOM yourself.
|
||||
|
||||
For example code using Winit with WebAssembly, check out the [web example]. For
|
||||
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
|
||||
book].
|
||||
|
||||
[web with_canvas]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowBuilderExtWebSys.html#tymethod.with_canvas
|
||||
[web canvas getter]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowExtWebSys.html#tymethod.canvas
|
||||
[web example]: ./examples/web.rs
|
||||
[Rust and WebAssembly book]: https://rustwasm.github.io/book/
|
||||
|
||||
#### 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.
|
||||
|
||||
The `ndk-glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk-glue`'s internal `NativeActivity` static is not the same due to version mismatch.
|
||||
|
||||
`winit` compatibility table with `ndk-glue`:
|
||||
|
||||
| winit | ndk-glue |
|
||||
| :---: | :------------------: |
|
||||
| 0.24 | `ndk-glue = "0.2.0"` |
|
||||
| 0.25 | `ndk-glue = "0.3.0"` |
|
||||
| 0.26 | `ndk-glue = "0.5.0"` |
|
||||
| 0.27 | `ndk-glue = "0.7.0"` |
|
||||
|
||||
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_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
|
||||
fn main() {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
And run the application with `cargo apk run --example request_redraw_threaded`
|
||||
|
||||
#### MacOS
|
||||
|
||||
A lot of functionality expects the application to be ready before you start
|
||||
doing anything; this includes creating windows, fetching monitors, drawing,
|
||||
and so on, see issues [#2238], [#2051] and [#2087].
|
||||
|
||||
If you encounter problems, you should try doing your initialization inside
|
||||
`Event::NewEvents(StartCause::Init)`.
|
||||
|
||||
[#2238]: https://github.com/rust-windowing/winit/issues/2238
|
||||
[#2051]: https://github.com/rust-windowing/winit/issues/2051
|
||||
[#2087]: https://github.com/rust-windowing/winit/issues/2087
|
||||
|
||||
23
appveyor.yml
23
appveyor.yml
@@ -1,23 +0,0 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: stable
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
install:
|
||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init -yv --default-toolchain %CHANNEL% --default-host %TARGET%
|
||||
- SET PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
- SET PATH=%PATH%;C:\MinGW\bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo test --verbose
|
||||
- cargo test --features serde --verbose
|
||||
114
examples/control_flow.rs
Normal file
114
examples/control_flow.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use std::{thread, time};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, KeyboardInput, WindowEvent},
|
||||
event_loop::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() {
|
||||
SimpleLogger::new().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.set_exit();
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_window_id) => {}
|
||||
Event::RedrawEventsCleared => {
|
||||
match mode {
|
||||
Mode::Wait => control_flow.set_wait(),
|
||||
Mode::WaitUntil => {
|
||||
if !wait_cancelled {
|
||||
control_flow.set_wait_until(instant::Instant::now() + WAIT_TIME);
|
||||
}
|
||||
}
|
||||
Mode::Poll => {
|
||||
thread::sleep(POLL_SLEEP_TIME);
|
||||
control_flow.set_poll();
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::{CursorIcon, WindowBuilder},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
@@ -12,35 +16,38 @@ fn main() {
|
||||
|
||||
let mut cursor_idx = 0;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
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_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
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,
|
||||
..
|
||||
} => {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::{CursorGrabMode, WindowBuilder},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
@@ -12,29 +16,44 @@ fn main() {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut modifiers = ModifiersState::default();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(key),
|
||||
modifiers,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
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),
|
||||
_ => (),
|
||||
let result = match key {
|
||||
Escape => {
|
||||
control_flow.set_exit();
|
||||
Ok(())
|
||||
}
|
||||
G => window.set_cursor_grab(CursorGrabMode::Confined),
|
||||
L => window.set_cursor_grab(CursorGrabMode::Locked),
|
||||
A => window.set_cursor_grab(CursorGrabMode::None),
|
||||
H => {
|
||||
window.set_cursor_visible(modifiers.shift());
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
println!("error: {}", err);
|
||||
}
|
||||
}
|
||||
WindowEvent::ModifiersChanged(m) => modifiers = m,
|
||||
_ => (),
|
||||
},
|
||||
Event::DeviceEvent { event, .. } => match event {
|
||||
|
||||
55
examples/custom_events.rs
Normal file
55
examples/custom_events.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoopBuilder,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum CustomEvent {
|
||||
Timer,
|
||||
}
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
|
||||
|
||||
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.set_wait();
|
||||
|
||||
match event {
|
||||
Event::UserEvent(event) => println!("user event: {:?}", event),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => control_flow.set_exit(),
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
panic!("This example is not supported on web.");
|
||||
}
|
||||
75
examples/drag_window.rs
Normal file
75
examples/drag_window.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{
|
||||
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
|
||||
},
|
||||
event_loop::EventLoop,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
let mut switched = false;
|
||||
let mut entered_id = window_2.id();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
Event::NewEvents(StartCause::Init) => {
|
||||
eprintln!("Switch which window is to be dragged by pressing \"x\".")
|
||||
}
|
||||
Event::WindowEvent { event, window_id } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Pressed,
|
||||
button: MouseButton::Left,
|
||||
..
|
||||
} => {
|
||||
let window = if (window_id == window_1.id() && switched)
|
||||
|| (window_id == window_2.id() && !switched)
|
||||
{
|
||||
&window_2
|
||||
} else {
|
||||
&window_1
|
||||
};
|
||||
|
||||
window.drag_window().unwrap()
|
||||
}
|
||||
WindowEvent::CursorEntered { .. } => {
|
||||
entered_id = window_id;
|
||||
name_windows(entered_id, switched, &window_1, &window_2)
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(VirtualKeyCode::X),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
switched = !switched;
|
||||
name_windows(entered_id, switched, &window_1, &window_2);
|
||||
println!("Switched!")
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
|
||||
fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) {
|
||||
let (drag_target, other) =
|
||||
if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) {
|
||||
(&window_2, &window_1)
|
||||
} else {
|
||||
(&window_1, &window_2)
|
||||
};
|
||||
drag_target.set_title("drag target");
|
||||
other.set_title("winit window");
|
||||
}
|
||||
@@ -1,110 +1,108 @@
|
||||
use std::io::{self, Write};
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
monitor::MonitorHandle,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::window::{Fullscreen, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut macos_use_simple_fullscreen = false;
|
||||
|
||||
let monitor = {
|
||||
// On macOS there are two fullscreen modes "native" and "simple"
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
print!("Please choose the fullscreen mode: (1) native, (2) simple: ");
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
let mut num = String::new();
|
||||
io::stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
match num {
|
||||
2 => macos_use_simple_fullscreen = true,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Prompt for monitor when using native fullscreen
|
||||
if !macos_use_simple_fullscreen {
|
||||
Some(prompt_for_monitor(&event_loop))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
Some(prompt_for_monitor(&event_loop))
|
||||
};
|
||||
|
||||
let mut is_fullscreen = monitor.is_some();
|
||||
let mut is_maximized = false;
|
||||
let mut decorations = true;
|
||||
let mut minimized = false;
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello world!")
|
||||
.with_fullscreen(monitor)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
*control_flow = ControlFlow::Wait;
|
||||
let mut monitor_index = 0;
|
||||
let mut monitor = event_loop
|
||||
.available_monitors()
|
||||
.next()
|
||||
.expect("no monitor found!");
|
||||
println!("Monitor: {:?}", monitor.name());
|
||||
|
||||
let mut mode_index = 0;
|
||||
let mut mode = monitor.video_modes().next().expect("no mode found");
|
||||
println!("Mode: {}", mode);
|
||||
|
||||
println!("Keys:");
|
||||
println!("- Esc\tExit");
|
||||
println!("- F\tToggle exclusive fullscreen mode");
|
||||
println!("- B\tToggle borderless mode");
|
||||
println!("- S\tNext screen");
|
||||
println!("- M\tNext mode for this screen");
|
||||
println!("- D\tToggle window decorations");
|
||||
println!("- X\tMaximize window");
|
||||
println!("- Z\tMinimize window");
|
||||
|
||||
event_loop.run(move |event, elwt, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match (virtual_code, state) {
|
||||
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
||||
(VirtualKeyCode::F, ElementState::Pressed) => {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if macos_use_simple_fullscreen {
|
||||
use winit::platform::macos::WindowExtMacOS;
|
||||
if WindowExtMacOS::set_simple_fullscreen(&window, !is_fullscreen) {
|
||||
is_fullscreen = !is_fullscreen;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
is_fullscreen = !is_fullscreen;
|
||||
if !is_fullscreen {
|
||||
window.set_fullscreen(None);
|
||||
} => match virtual_code {
|
||||
VirtualKeyCode::Escape => control_flow.set_exit(),
|
||||
VirtualKeyCode::F | VirtualKeyCode::B if window.fullscreen().is_some() => {
|
||||
window.set_fullscreen(None);
|
||||
}
|
||||
VirtualKeyCode::F => {
|
||||
let fullscreen = Some(Fullscreen::Exclusive(mode.clone()));
|
||||
println!("Setting mode: {:?}", fullscreen);
|
||||
window.set_fullscreen(fullscreen);
|
||||
}
|
||||
VirtualKeyCode::B => {
|
||||
let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone())));
|
||||
println!("Setting mode: {:?}", fullscreen);
|
||||
window.set_fullscreen(fullscreen);
|
||||
}
|
||||
VirtualKeyCode::S => {
|
||||
monitor_index += 1;
|
||||
if let Some(mon) = elwt.available_monitors().nth(monitor_index) {
|
||||
monitor = mon;
|
||||
} else {
|
||||
window.set_fullscreen(Some(window.current_monitor()));
|
||||
monitor_index = 0;
|
||||
monitor = elwt.available_monitors().next().expect("no monitor found!");
|
||||
}
|
||||
}
|
||||
(VirtualKeyCode::S, ElementState::Pressed) => {
|
||||
println!("window.fullscreen {:?}", window.fullscreen());
|
||||
println!("Monitor: {:?}", monitor.name());
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use winit::platform::macos::WindowExtMacOS;
|
||||
println!(
|
||||
"window.simple_fullscreen {:?}",
|
||||
WindowExtMacOS::simple_fullscreen(&window)
|
||||
);
|
||||
mode_index = 0;
|
||||
mode = monitor.video_modes().next().expect("no mode found");
|
||||
println!("Mode: {}", mode);
|
||||
}
|
||||
VirtualKeyCode::M => {
|
||||
mode_index += 1;
|
||||
if let Some(m) = monitor.video_modes().nth(mode_index) {
|
||||
mode = m;
|
||||
} else {
|
||||
mode_index = 0;
|
||||
mode = monitor.video_modes().next().expect("no mode found");
|
||||
}
|
||||
println!("Mode: {}", mode);
|
||||
}
|
||||
(VirtualKeyCode::M, ElementState::Pressed) => {
|
||||
is_maximized = !is_maximized;
|
||||
window.set_maximized(is_maximized);
|
||||
}
|
||||
(VirtualKeyCode::D, ElementState::Pressed) => {
|
||||
VirtualKeyCode::D => {
|
||||
decorations = !decorations;
|
||||
window.set_decorations(decorations);
|
||||
}
|
||||
VirtualKeyCode::X => {
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
}
|
||||
VirtualKeyCode::Z => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
@@ -113,25 +111,3 @@ fn main() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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: ");
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
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 = event_loop
|
||||
.available_monitors()
|
||||
.nth(num)
|
||||
.expect("Please enter a valid ID");
|
||||
|
||||
println!("Using {:?}", monitor.name());
|
||||
|
||||
monitor
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
@@ -19,7 +23,7 @@ fn main() {
|
||||
ElementState::Released,
|
||||
VirtualKeyCode::{N, Y},
|
||||
};
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => {
|
||||
@@ -61,7 +65,7 @@ fn main() {
|
||||
// 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;
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
N => {
|
||||
|
||||
99
examples/ime.rs
Normal file
99
examples/ime.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use log::LevelFilter;
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::PhysicalPosition,
|
||||
event::{ElementState, Event, Ime, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new()
|
||||
.with_level(LevelFilter::Trace)
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
println!("IME position will system default");
|
||||
println!("Click to set IME position to cursor's");
|
||||
println!("Press F2 to toggle IME. See the documentation of `set_ime_allowed` for more info");
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut ime_allowed = true;
|
||||
window.set_ime_allowed(ime_allowed);
|
||||
|
||||
let mut may_show_ime = false;
|
||||
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
|
||||
let mut ime_pos = PhysicalPosition::new(0.0, 0.0);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CursorMoved { position, .. },
|
||||
..
|
||||
} => {
|
||||
cursor_position = position;
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
println!(
|
||||
"Setting ime position to {}, {}",
|
||||
cursor_position.x, cursor_position.y
|
||||
);
|
||||
ime_pos = cursor_position;
|
||||
if may_show_ime {
|
||||
window.set_ime_position(ime_pos);
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::Ime(event),
|
||||
..
|
||||
} => {
|
||||
println!("{:?}", event);
|
||||
may_show_ime = event != Ime::Disabled;
|
||||
if may_show_ime {
|
||||
window.set_ime_position(ime_pos);
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::ReceivedCharacter(ch),
|
||||
..
|
||||
} => {
|
||||
println!("ch: {:?}", ch);
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::KeyboardInput { input, .. },
|
||||
..
|
||||
} => {
|
||||
println!("key: {:?}", input);
|
||||
|
||||
if input.state == ElementState::Pressed
|
||||
&& input.virtual_keycode == Some(VirtualKeyCode::F2)
|
||||
{
|
||||
ime_allowed = !ime_allowed;
|
||||
window.set_ime_allowed(ime_allowed);
|
||||
println!("\nIME: {}\n", ime_allowed);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0)));
|
||||
window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0)));
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
dbg!(window.available_monitors());
|
||||
dbg!(window.available_monitors().collect::<Vec<_>>());
|
||||
dbg!(window.primary_monitor());
|
||||
}
|
||||
|
||||
62
examples/mouse_wheel.rs
Normal file
62
examples/mouse_wheel.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Mouse Wheel events")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
println!(
|
||||
r"
|
||||
When using so called 'natural scrolling' (scrolling that acts like on a touch screen), this is what to expect:
|
||||
|
||||
Moving your finger downwards on a scroll wheel should make the window move down, and you should see a positive Y scroll value.
|
||||
|
||||
When moving fingers on a trackpad down and to the right, you should see positive X and Y deltas, and the window should move down and to the right.
|
||||
|
||||
With reverse scrolling, you should see the inverse behavior.
|
||||
|
||||
In both cases the example window should move like the content of a scroll area in any other application.
|
||||
|
||||
In other words, the deltas indicate the direction in which to move the content (in this case the window)."
|
||||
);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::MouseWheel { delta, .. } => match delta {
|
||||
winit::event::MouseScrollDelta::LineDelta(x, y) => {
|
||||
println!("mouse wheel Line Delta: ({},{})", x, y);
|
||||
let pixels_per_line = 120.0;
|
||||
let mut pos = window.outer_position().unwrap();
|
||||
pos.x += (x * pixels_per_line) as i32;
|
||||
pos.y += (y * pixels_per_line) as i32;
|
||||
window.set_outer_position(pos)
|
||||
}
|
||||
winit::event::MouseScrollDelta::PixelDelta(p) => {
|
||||
println!("mouse wheel Pixel Delta: ({},{})", p.x, p.y);
|
||||
let mut pos = window.outer_position().unwrap();
|
||||
pos.x += p.x as i32;
|
||||
pos.y += p.y as i32;
|
||||
window.set_outer_position(pos)
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,29 +1,57 @@
|
||||
extern crate env_logger;
|
||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{CursorIcon, WindowBuilder},
|
||||
};
|
||||
|
||||
const WINDOW_COUNT: usize = 3;
|
||||
const WINDOW_SIZE: (u32, u32) = (600, 400);
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
const WINDOW_COUNT: usize = 3;
|
||||
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
|
||||
|
||||
SimpleLogger::new().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.into())
|
||||
.with_inner_size(WINDOW_SIZE)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut video_modes: Vec<_> = window.current_monitor().unwrap().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.get(video_mode_id).cloned();
|
||||
video_modes = window.current_monitor().unwrap().video_modes().collect();
|
||||
video_mode_id = video_mode_id.min(video_modes.len());
|
||||
let video_mode = video_modes.get(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.get(video_mode_id).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
@@ -35,8 +63,8 @@ fn main() {
|
||||
..
|
||||
} => {
|
||||
window.set_title(&format!("{:?}", key));
|
||||
let state = !modifiers.shift;
|
||||
use self::VirtualKeyCode::*;
|
||||
let state = !modifiers.shift();
|
||||
use VirtualKeyCode::*;
|
||||
match key {
|
||||
A => window.set_always_on_top(state),
|
||||
C => window.set_cursor_icon(match state {
|
||||
@@ -44,11 +72,37 @@ fn main() {
|
||||
false => CursorIcon::Default,
|
||||
}),
|
||||
D => window.set_decorations(!state),
|
||||
F => window.set_fullscreen(match state {
|
||||
true => Some(window.current_monitor()),
|
||||
false => None,
|
||||
// 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[video_mode_id]);
|
||||
}
|
||||
F => window.set_fullscreen(match (state, modifiers.alt()) {
|
||||
(true, false) => Some(Fullscreen::Borderless(None)),
|
||||
(true, true) => {
|
||||
Some(Fullscreen::Exclusive(video_modes[video_mode_id].clone()))
|
||||
}
|
||||
(false, _) => None,
|
||||
}),
|
||||
G => window.set_cursor_grab(state).unwrap(),
|
||||
L if state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) {
|
||||
println!("error: {}", err);
|
||||
}
|
||||
}
|
||||
G if state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Confined) {
|
||||
println!("error: {}", err);
|
||||
}
|
||||
}
|
||||
G | L if !state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
|
||||
println!("error: {}", err);
|
||||
}
|
||||
}
|
||||
H => window.set_cursor_visible(!state),
|
||||
I => {
|
||||
println!("Info:");
|
||||
@@ -56,33 +110,41 @@ fn main() {
|
||||
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.into()),
|
||||
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.0 } else { -1.0 };
|
||||
position.x += 10.0 * sign;
|
||||
position.y += 10.0 * sign;
|
||||
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 => (WINDOW_SIZE.0 + 100, WINDOW_SIZE.1 + 100),
|
||||
false => WINDOW_SIZE,
|
||||
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()
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
W => window
|
||||
.set_cursor_position(
|
||||
(WINDOW_SIZE.0 as i32 / 2, WINDOW_SIZE.1 as i32 / 2).into(),
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
Z => {
|
||||
window.set_visible(false);
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
@@ -97,9 +159,9 @@ fn main() {
|
||||
});
|
||||
}
|
||||
event_loop.run(move |event, _event_loop, control_flow| {
|
||||
*control_flow = match !window_senders.is_empty() {
|
||||
true => ControlFlow::Wait,
|
||||
false => ControlFlow::Exit,
|
||||
match !window_senders.is_empty() {
|
||||
true => control_flow.set_wait(),
|
||||
false => control_flow.set_exit(),
|
||||
};
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => match event {
|
||||
@@ -108,6 +170,7 @@ fn main() {
|
||||
| WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
..
|
||||
},
|
||||
@@ -117,11 +180,18 @@ fn main() {
|
||||
}
|
||||
_ => {
|
||||
if let Some(tx) = window_senders.get(&window_id) {
|
||||
tx.send(event).unwrap();
|
||||
if let Some(event) = event.to_static() {
|
||||
tx.send(event).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
panic!("Example not supported on Wasm");
|
||||
}
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
for _ in 0..3 {
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
println!("Opened a new window: {:?}", window.id());
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
|
||||
println!("Press N to open a new window.");
|
||||
|
||||
event_loop.run(move |event, event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => {
|
||||
match event {
|
||||
@@ -26,18 +35,21 @@ fn main() {
|
||||
windows.remove(&window_id);
|
||||
|
||||
if windows.is_empty() {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
virtual_keycode: Some(VirtualKeyCode::N),
|
||||
..
|
||||
},
|
||||
is_synthetic: false,
|
||||
..
|
||||
} => {
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
let window = Window::new(event_loop).unwrap();
|
||||
println!("Opened a new window: {:?}", window.id());
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let event_loop: EventLoop<i32> = EventLoop::new_user_event();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let proxy = event_loop.create_proxy();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut counter = 0;
|
||||
// Wake up the `event_loop` once every second.
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
proxy.send_event(counter).unwrap();
|
||||
counter += 1;
|
||||
}
|
||||
});
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
use std::time::{Duration, Instant};
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event::{ElementState, Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
@@ -14,21 +16,26 @@ fn main() {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::EventsCleared => {
|
||||
window.request_redraw();
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0))
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Released,
|
||||
..
|
||||
} => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
println!("\nredrawing!\n");
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
println!("{:?}", event);
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
|
||||
48
examples/request_redraw_threaded.rs
Normal file
48
examples/request_redraw_threaded.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
use std::{thread, time};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
SimpleLogger::new().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.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
println!("\nredrawing!\n");
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
unimplemented!() // `Window` can't be sent between threads
|
||||
}
|
||||
@@ -1,26 +1,34 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().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((400, 200).into())
|
||||
.with_inner_size(LogicalSize::new(600.0, 300.0))
|
||||
.with_min_inner_size(LogicalSize::new(400.0, 200.0))
|
||||
.with_max_inner_size(LogicalSize::new(800.0, 400.0))
|
||||
.with_resizable(resizable)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
use std::time::{Duration, Instant};
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use instant::Instant;
|
||||
use std::time::Duration;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
@@ -20,16 +26,16 @@ fn main() {
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => {
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length)
|
||||
control_flow.set_wait_until(Instant::now() + timer_length);
|
||||
}
|
||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length);
|
||||
control_flow.set_wait_until(Instant::now() + timer_length);
|
||||
println!("\nTimer\n");
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
} => control_flow.set_exit(),
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
@@ -16,14 +20,15 @@ fn main() {
|
||||
window.set_title("A fantastic window!");
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
} => control_flow.set_exit(),
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::event_loop::EventLoop;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let monitor = event_loop.primary_monitor();
|
||||
let monitor = match event_loop.primary_monitor() {
|
||||
Some(monitor) => monitor,
|
||||
None => {
|
||||
println!("No primary monitor detected.");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
println!("Listing available video modes:");
|
||||
|
||||
for mode in monitor.video_modes() {
|
||||
println!("{:?}", mode);
|
||||
println!("{}", mode);
|
||||
}
|
||||
}
|
||||
|
||||
90
examples/web.rs
Normal file
90
examples/web.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::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(target_arch = "wasm32")]
|
||||
let log_list = wasm::create_log_list(&window);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm::log_event(&log_list, &event);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use winit::{event::Event, window::Window};
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn run() {
|
||||
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
|
||||
|
||||
#[allow(clippy::main_recursion)]
|
||||
super::main();
|
||||
}
|
||||
|
||||
pub fn create_log_list(window: &Window) -> web_sys::Element {
|
||||
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();
|
||||
|
||||
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
|
||||
canvas.style().set_css_text("background-color: crimson;");
|
||||
body.append_child(&canvas).unwrap();
|
||||
|
||||
let log_header = document.create_element("h2").unwrap();
|
||||
log_header.set_text_content(Some("Event Log"));
|
||||
body.append_child(&log_header).unwrap();
|
||||
|
||||
let log_list = document.create_element("ul").unwrap();
|
||||
body.append_child(&log_list).unwrap();
|
||||
log_list
|
||||
}
|
||||
|
||||
pub fn log_event(log_list: &web_sys::Element, event: &Event<()>) {
|
||||
log::debug!("{:?}", event);
|
||||
|
||||
// Getting access to browser logs requires a lot of setup on mobile devices.
|
||||
// So we implement this basic logging system into the page to give developers an easy alternative.
|
||||
// As a bonus its also kind of handy on desktop.
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let log = document.create_element("li").unwrap();
|
||||
log.set_text_content(Some(&format!("{:?}", event)));
|
||||
log_list
|
||||
.insert_before(&log, log_list.first_child().as_ref())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,35 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
131
examples/window_debug.rs
Normal file
131
examples/window_debug.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
// This example is used by developers to test various window functions.
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::{LogicalSize, PhysicalSize},
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{DeviceEventFilter, EventLoop},
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().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!(" (P) Toggle borderless fullscreen on system's preffered monitor");
|
||||
eprintln!(" (M) Toggle minimized");
|
||||
eprintln!(" (Q) Quit event loop");
|
||||
eprintln!(" (V) Toggle visibility");
|
||||
eprintln!(" (X) Toggle maximized");
|
||||
|
||||
let mut minimized = false;
|
||||
let mut visible = true;
|
||||
|
||||
event_loop.set_device_event_filter(DeviceEventFilter::Never);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_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:
|
||||
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().unwrap();
|
||||
if let Some(mode) = monitor
|
||||
.video_modes()
|
||||
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
|
||||
{
|
||||
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
|
||||
} else {
|
||||
eprintln!("no video modes available");
|
||||
}
|
||||
}
|
||||
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::P => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
|
||||
}
|
||||
}
|
||||
VirtualKeyCode::M => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
VirtualKeyCode::Q => {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
VirtualKeyCode::V => {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
}
|
||||
VirtualKeyCode::X => {
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
extern crate image;
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::{Icon, WindowBuilder},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().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
|
||||
@@ -26,11 +31,12 @@ fn main() {
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
use winit::event::WindowEvent::*;
|
||||
match event {
|
||||
CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
CloseRequested => control_flow.set_exit(),
|
||||
DroppedFile(path) => {
|
||||
window.set_window_icon(Some(load_icon(&path)));
|
||||
}
|
||||
@@ -42,13 +48,11 @@ fn main() {
|
||||
|
||||
fn load_icon(path: &Path) -> Icon {
|
||||
let (icon_rgba, icon_width, icon_height) = {
|
||||
let image = image::open(path).expect("Failed to open icon path");
|
||||
use image::{GenericImageView, Pixel};
|
||||
let image = image::open(path)
|
||||
.expect("Failed to open icon path")
|
||||
.into_rgba8();
|
||||
let (width, height) = image.dimensions();
|
||||
let mut rgba = Vec::with_capacity((width * height) as usize * 4);
|
||||
for (_, _, pixel) in image.pixels() {
|
||||
rgba.extend_from_slice(&pixel.to_rgba().data);
|
||||
}
|
||||
let rgba = image.into_raw();
|
||||
(rgba, width, height)
|
||||
};
|
||||
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
|
||||
|
||||
@@ -1,41 +1,66 @@
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
platform::desktop::EventLoopExtDesktop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
// 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",
|
||||
target_os = "android",
|
||||
))]
|
||||
fn main() {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
platform::run_return::EventLoopExtRunReturn,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
let mut event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
println!("Close the window to continue.");
|
||||
event_loop.run_return(|event, _, control_flow| match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
});
|
||||
drop(window);
|
||||
let mut quit = false;
|
||||
|
||||
let _window_2 = WindowBuilder::new()
|
||||
.with_title("A second, fantasticer window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
while !quit {
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
println!("Wa ha ha! You thought that closing the window would finish this?!");
|
||||
event_loop.run_return(|event, _, control_flow| match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
});
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
// Print only Window events to reduce noise
|
||||
println!("{:?}", event);
|
||||
}
|
||||
|
||||
println!("Okay we're done now for real.");
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
quit = true;
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
// Sleep for 1/60 second to simulate rendering
|
||||
println!("rendering");
|
||||
sleep(Duration::from_millis(16));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios", target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
println!("This platform doesn't support run_return.");
|
||||
}
|
||||
|
||||
9
run-wasm/Cargo.toml
Normal file
9
run-wasm/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "run-wasm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cargo-run-wasm = "0.1.0"
|
||||
3
run-wasm/src/main.rs
Normal file
3
run-wasm/src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
cargo_run_wasm::run_wasm();
|
||||
}
|
||||
667
src/dpi.rs
667
src/dpi.rs
@@ -1,330 +1,593 @@
|
||||
//! DPI is important, so read the docs for this module if you don't want to be confused.
|
||||
//! UI scaling is important, so read the docs for this module if you don't want to be confused.
|
||||
//!
|
||||
//! Originally, `winit` dealt entirely in physical pixels (excluding unintentional inconsistencies), but now all
|
||||
//! window-related functions both produce and consume logical pixels. Monitor-related functions still use physical
|
||||
//! pixels, as do any context-related functions in `glutin`.
|
||||
//! ## Why should I care about UI scaling?
|
||||
//!
|
||||
//! If you've never heard of these terms before, then you're not alone, and this documentation will explain the
|
||||
//! concepts.
|
||||
//! 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.
|
||||
//!
|
||||
//! Modern screens have a defined physical resolution, most commonly 1920x1080. Indepedent of that is the amount of
|
||||
//! space the screen occupies, which is to say, the height and width in millimeters. The relationship between these two
|
||||
//! measurements is the *pixel density*. Mobile screens require a high pixel density, as they're held close to the
|
||||
//! eyes. Larger displays also require a higher pixel density, hence the growing presence of 1440p and 4K displays.
|
||||
//! 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.
|
||||
//!
|
||||
//! So, this presents a problem. Let's say we want to render a square 100px button. It will occupy 100x100 of the
|
||||
//! screen's pixels, which in many cases, seems perfectly fine. However, because this size doesn't account for the
|
||||
//! screen's dimensions or pixel density, the button's size can vary quite a bit. On a 4K display, it would be unusably
|
||||
//! small.
|
||||
//! 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.
|
||||
//!
|
||||
//! That's a description of what happens when the button is 100x100 *physical* pixels. Instead, let's try using 100x100
|
||||
//! *logical* pixels. To map logical pixels to physical pixels, we simply multiply by the DPI (dots per inch) factor.
|
||||
//! On a "typical" desktop display, the DPI factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical
|
||||
//! pixels. However, a 1440p display may have a DPI factor of 1.25, so the button is rendered as 125x125 physical pixels.
|
||||
//! Ideally, the button now has approximately the same perceived size across varying displays.
|
||||
//! ## How should I handle it?
|
||||
//!
|
||||
//! Failure to account for the DPI factor can create a badly 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 entering an existential panic. Once users enter that state, they will no longer be focused on your application.
|
||||
//! 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`.
|
||||
//!
|
||||
//! There are two ways to get the DPI factor:
|
||||
//! - You can track the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) event of your
|
||||
//! windows. This event is sent any time the DPI factor changes, either because the window moved to another monitor,
|
||||
//! or because the user changed the configuration of their screen.
|
||||
//! - You can also retrieve the DPI factor of a monitor by calling
|
||||
//! [`MonitorHandle::hidpi_factor`](../monitor/struct.MonitorHandle.html#method.hidpi_factor), or the
|
||||
//! current DPI factor applied to a window by calling
|
||||
//! [`Window::hidpi_factor`](../window/struct.Window.html#method.hidpi_factor), which is roughly equivalent
|
||||
//! to `window.current_monitor().hidpi_factor()`.
|
||||
//! 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.
|
||||
//!
|
||||
//! Depending on the platform, the window's actual DPI factor may only be known after
|
||||
//! the event loop has started and your window has been drawn once. To properly handle these cases,
|
||||
//! the most robust way is to monitor the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged)
|
||||
//! event and dynamically adapt your drawing logic to follow the DPI factor.
|
||||
//! ### Position and Size types
|
||||
//!
|
||||
//! Here's an overview of what sort of DPI factors you can expect, and where they come from:
|
||||
//! - **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" DPI factors, i.e.
|
||||
//! 1.0, 1.25, 1.5... on Windows 7, the DPI factor is global and changing it requires logging out.
|
||||
//! - **macOS:** The buzzword is "retina displays", which have a DPI factor of 2.0. Otherwise, the DPI factor is 1.0.
|
||||
//! Intermediate DPI factors are never used, thus 1440p displays/etc. aren't properly supported. It's possible for any
|
||||
//! display to use that 2.0 DPI factor, given the use of the command line.
|
||||
//! - **X11:** On X11, we calcuate the DPI factor based on the millimeter dimensions provided by XRandR. This can
|
||||
//! result in a wide range of possible values, including some interesting ones like 1.0833333333333333. This can be
|
||||
//! overridden using the `WINIT_HIDPI_FACTOR` environment variable, though that's not recommended.
|
||||
//! - **Wayland:** On Wayland, DPI factors are set per-screen by the server, and are always integers (most often 1 or 2).
|
||||
//! - **iOS:** DPI factors are both constant and device-specific on iOS.
|
||||
//! - **Android:** This feature isn't yet implemented on Android, so the DPI factor will always be returned as 1.0.
|
||||
//! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the
|
||||
//! device, and the [`LogicalPosition`] / [`LogicalSize`] 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.
|
||||
//!
|
||||
//! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This
|
||||
//! may be surprising on X11, but is quite standard elsewhere. Physical size changes always produce a
|
||||
//! [`Resized`](../event/enum.WindowEvent.html#variant.Resized) event, even on platforms where no resize actually occurs,
|
||||
//! such as macOS and Wayland. As a result, it's not necessary to separately handle
|
||||
//! [`HiDpiFactorChanged`](../event/enum.WindowEvent.html#variant.HiDpiFactorChanged) if you're only listening for size.
|
||||
//! 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.
|
||||
//!
|
||||
//! Your GPU has no awareness of the concept of logical pixels, and unless you like wasting pixel density, your
|
||||
//! framebuffer's size should be in physical pixels.
|
||||
//! ### Events
|
||||
//!
|
||||
//! `winit` will send [`Resized`](../enum.WindowEvent.html#variant.Resized) events whenever a window's logical size
|
||||
//! changes, and [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events
|
||||
//! whenever the DPI factor changes. Receiving either of these events means that the physical size of your window has
|
||||
//! changed, and you should recompute it using the latest values you received for each. If the logical size and the
|
||||
//! DPI factor change simultaneously, `winit` will send both events together; thus, it's recommended to buffer
|
||||
//! these events and process them at the end of the queue.
|
||||
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
|
||||
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
|
||||
//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your
|
||||
//! application's UI elements and adjust how the platform changes the window's size to reflect the new
|
||||
//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor
|
||||
//! can be found by calling [`window.scale_factor()`].
|
||||
//!
|
||||
//! If you never received any [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events,
|
||||
//! then your window's DPI factor is 1.
|
||||
//! ## 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:** Recent versions of macOS allow the user to change the scaling factor for certain
|
||||
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
|
||||
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
|
||||
//! the specific value varies across devices.
|
||||
//! - **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, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
|
||||
//!
|
||||
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
|
||||
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
|
||||
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
|
||||
//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always
|
||||
//! integers (most often 1 or 2).
|
||||
//! - **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.
|
||||
//! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by
|
||||
//! both the screen scaling and the browser zoom level and can go below `1.0`.
|
||||
//!
|
||||
//!
|
||||
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
||||
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
||||
//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged
|
||||
//! [`window.scale_factor()`]: crate::window::Window::scale_factor
|
||||
//! [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
|
||||
//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
|
||||
|
||||
/// Checks that the DPI factor is a normal positive `f64`.
|
||||
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 DPI factor assert that this will return `true`. If you're sourcing DPI factors from
|
||||
/// 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_hidpi_factor(dpi_factor: f64) -> bool {
|
||||
dpi_factor.is_sign_positive() && dpi_factor.is_normal()
|
||||
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)]
|
||||
/// 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, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LogicalPosition {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub struct LogicalPosition<P> {
|
||||
pub x: P,
|
||||
pub y: P,
|
||||
}
|
||||
|
||||
impl LogicalPosition {
|
||||
impl<P> LogicalPosition<P> {
|
||||
#[inline]
|
||||
pub fn new(x: f64, y: f64) -> Self {
|
||||
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>>(physical: T, dpi_factor: f64) -> Self {
|
||||
physical.into().to_logical(dpi_factor)
|
||||
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(&self, dpi_factor: f64) -> PhysicalPosition {
|
||||
assert!(validate_hidpi_factor(dpi_factor));
|
||||
let x = self.x * dpi_factor;
|
||||
let y = self.y * dpi_factor;
|
||||
PhysicalPosition::new(x, y)
|
||||
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 From<(f64, f64)> for LogicalPosition {
|
||||
#[inline]
|
||||
fn from((x, y): (f64, f64)) -> Self {
|
||||
Self::new(x, y)
|
||||
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 From<(i32, i32)> for LogicalPosition {
|
||||
#[inline]
|
||||
fn from((x, y): (i32, i32)) -> Self {
|
||||
Self::new(x as f64, y as f64)
|
||||
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) {
|
||||
fn from(p: LogicalPosition<P>) -> (X, X) {
|
||||
(p.x.cast(), p.y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(f64, f64)> for LogicalPosition {
|
||||
#[inline]
|
||||
fn into(self) -> (f64, f64) {
|
||||
(self.x, self.y)
|
||||
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 Into<(i32, i32)> for LogicalPosition {
|
||||
/// Note that this rounds instead of truncating.
|
||||
#[inline]
|
||||
fn into(self) -> (i32, i32) {
|
||||
(self.x.round() as _, self.y.round() as _)
|
||||
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] {
|
||||
fn from(p: LogicalPosition<P>) -> [X; 2] {
|
||||
[p.x.cast(), p.y.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
|
||||
fn from(p: mint::Point2<P>) -> Self {
|
||||
Self::new(p.x, p.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
|
||||
fn from(p: LogicalPosition<P>) -> Self {
|
||||
mint::Point2 { x: p.x, y: p.y }
|
||||
}
|
||||
}
|
||||
|
||||
/// A position represented in physical pixels.
|
||||
///
|
||||
/// 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)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PhysicalPosition {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub struct PhysicalPosition<P> {
|
||||
pub x: P,
|
||||
pub y: P,
|
||||
}
|
||||
|
||||
impl PhysicalPosition {
|
||||
impl<P> PhysicalPosition<P> {
|
||||
#[inline]
|
||||
pub fn new(x: f64, y: f64) -> Self {
|
||||
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>>(logical: T, dpi_factor: f64) -> Self {
|
||||
logical.into().to_physical(dpi_factor)
|
||||
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(&self, dpi_factor: f64) -> LogicalPosition {
|
||||
assert!(validate_hidpi_factor(dpi_factor));
|
||||
let x = self.x / dpi_factor;
|
||||
let y = self.y / dpi_factor;
|
||||
LogicalPosition::new(x, y)
|
||||
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 From<(f64, f64)> for PhysicalPosition {
|
||||
#[inline]
|
||||
fn from((x, y): (f64, f64)) -> Self {
|
||||
Self::new(x, y)
|
||||
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 From<(i32, i32)> for PhysicalPosition {
|
||||
#[inline]
|
||||
fn from((x, y): (i32, i32)) -> Self {
|
||||
Self::new(x as f64, y as f64)
|
||||
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) {
|
||||
fn from(p: PhysicalPosition<P>) -> (X, X) {
|
||||
(p.x.cast(), p.y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(f64, f64)> for PhysicalPosition {
|
||||
#[inline]
|
||||
fn into(self) -> (f64, f64) {
|
||||
(self.x, self.y)
|
||||
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 Into<(i32, i32)> for PhysicalPosition {
|
||||
/// Note that this rounds instead of truncating.
|
||||
#[inline]
|
||||
fn into(self) -> (i32, i32) {
|
||||
(self.x.round() as _, self.y.round() as _)
|
||||
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] {
|
||||
fn from(p: PhysicalPosition<P>) -> [X; 2] {
|
||||
[p.x.cast(), p.y.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
|
||||
fn from(p: mint::Point2<P>) -> Self {
|
||||
Self::new(p.x, p.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
|
||||
fn from(p: PhysicalPosition<P>) -> Self {
|
||||
mint::Point2 { x: p.x, y: p.y }
|
||||
}
|
||||
}
|
||||
|
||||
/// A size represented in logical pixels.
|
||||
///
|
||||
/// The size 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<(u32, u32)>` implementation is provided which
|
||||
/// does the rounding for you.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LogicalSize {
|
||||
pub width: f64,
|
||||
pub height: f64,
|
||||
pub struct LogicalSize<P> {
|
||||
pub width: P,
|
||||
pub height: P,
|
||||
}
|
||||
|
||||
impl LogicalSize {
|
||||
impl<P> LogicalSize<P> {
|
||||
#[inline]
|
||||
pub fn new(width: f64, height: f64) -> Self {
|
||||
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>>(physical: T, dpi_factor: f64) -> Self {
|
||||
physical.into().to_logical(dpi_factor)
|
||||
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(&self, dpi_factor: f64) -> PhysicalSize {
|
||||
assert!(validate_hidpi_factor(dpi_factor));
|
||||
let width = self.width * dpi_factor;
|
||||
let height = self.height * dpi_factor;
|
||||
PhysicalSize::new(width, height)
|
||||
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 From<(f64, f64)> for LogicalSize {
|
||||
#[inline]
|
||||
fn from((width, height): (f64, f64)) -> Self {
|
||||
Self::new(width, height)
|
||||
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 From<(u32, u32)> for LogicalSize {
|
||||
#[inline]
|
||||
fn from((width, height): (u32, u32)) -> Self {
|
||||
Self::new(width as f64, height as f64)
|
||||
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for (X, X) {
|
||||
fn from(s: LogicalSize<P>) -> (X, X) {
|
||||
(s.width.cast(), s.height.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(f64, f64)> for LogicalSize {
|
||||
#[inline]
|
||||
fn into(self) -> (f64, f64) {
|
||||
(self.width, self.height)
|
||||
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 Into<(u32, u32)> for LogicalSize {
|
||||
/// Note that this rounds instead of truncating.
|
||||
#[inline]
|
||||
fn into(self) -> (u32, u32) {
|
||||
(self.width.round() as _, self.height.round() as _)
|
||||
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for [X; 2] {
|
||||
fn from(s: LogicalSize<P>) -> [X; 2] {
|
||||
[s.width.cast(), s.height.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
|
||||
fn from(v: mint::Vector2<P>) -> Self {
|
||||
Self::new(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
|
||||
fn from(s: LogicalSize<P>) -> Self {
|
||||
mint::Vector2 {
|
||||
x: s.width,
|
||||
y: s.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A size represented in physical pixels.
|
||||
///
|
||||
/// The size 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<(u32, u32)>` implementation is provided which
|
||||
/// does the rounding for you.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PhysicalSize {
|
||||
pub width: f64,
|
||||
pub height: f64,
|
||||
pub struct PhysicalSize<P> {
|
||||
pub width: P,
|
||||
pub height: P,
|
||||
}
|
||||
|
||||
impl PhysicalSize {
|
||||
impl<P> PhysicalSize<P> {
|
||||
#[inline]
|
||||
pub fn new(width: f64, height: f64) -> Self {
|
||||
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>>(logical: T, dpi_factor: f64) -> Self {
|
||||
logical.into().to_physical(dpi_factor)
|
||||
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(&self, dpi_factor: f64) -> LogicalSize {
|
||||
assert!(validate_hidpi_factor(dpi_factor));
|
||||
let width = self.width / dpi_factor;
|
||||
let height = self.height / dpi_factor;
|
||||
LogicalSize::new(width, height)
|
||||
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 From<(f64, f64)> for PhysicalSize {
|
||||
#[inline]
|
||||
fn from((width, height): (f64, f64)) -> Self {
|
||||
Self::new(width, height)
|
||||
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 From<(u32, u32)> for PhysicalSize {
|
||||
#[inline]
|
||||
fn from((width, height): (u32, u32)) -> Self {
|
||||
Self::new(width as f64, height as f64)
|
||||
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for (X, X) {
|
||||
fn from(s: PhysicalSize<P>) -> (X, X) {
|
||||
(s.width.cast(), s.height.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(f64, f64)> for PhysicalSize {
|
||||
#[inline]
|
||||
fn into(self) -> (f64, f64) {
|
||||
(self.width, self.height)
|
||||
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 Into<(u32, u32)> for PhysicalSize {
|
||||
/// Note that this rounds instead of truncating.
|
||||
#[inline]
|
||||
fn into(self) -> (u32, u32) {
|
||||
(self.width.round() as _, self.height.round() as _)
|
||||
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for [X; 2] {
|
||||
fn from(s: PhysicalSize<P>) -> [X; 2] {
|
||||
[s.width.cast(), s.height.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
|
||||
fn from(v: mint::Vector2<P>) -> Self {
|
||||
Self::new(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
|
||||
fn from(s: PhysicalSize<P>) -> Self {
|
||||
mint::Vector2 {
|
||||
x: s.width,
|
||||
y: s.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A size that's either physical or logical.
|
||||
#[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),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp<S: Into<Size>>(input: S, min: S, max: S, scale_factor: f64) -> Size {
|
||||
let (input, min, max) = (
|
||||
input.into().to_physical::<f64>(scale_factor),
|
||||
min.into().to_physical::<f64>(scale_factor),
|
||||
max.into().to_physical::<f64>(scale_factor),
|
||||
);
|
||||
|
||||
let clamp = |input: f64, min: f64, max: f64| {
|
||||
if input < min {
|
||||
min
|
||||
} else if input > max {
|
||||
max
|
||||
} else {
|
||||
input
|
||||
}
|
||||
};
|
||||
|
||||
let width = clamp(input.width, min.width, max.width);
|
||||
let height = clamp(input.height, min.height, max.height);
|
||||
|
||||
PhysicalSize::new(width, height).into()
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
876
src/event.rs
876
src/event.rs
File diff suppressed because it is too large
Load Diff
@@ -1,48 +1,117 @@
|
||||
//! The `EventLoop` struct and assorted supporting types, including `ControlFlow`.
|
||||
//! 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.
|
||||
//! If you want to send custom events to the event loop, use
|
||||
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
|
||||
//! [`send_event`](`EventLoopProxy::send_event`) method.
|
||||
//!
|
||||
//! See the root-level documentation for information on how to create and use an event loop to
|
||||
//! handle events.
|
||||
//!
|
||||
//! [create_proxy]: ./struct.EventLoop.html#method.create_proxy
|
||||
//! [event_loop_proxy]: ./struct.EventLoopProxy.html
|
||||
//! [send_event]: ./struct.EventLoopProxy.html#method.send_event
|
||||
use std::{error, fmt, ops::Deref, time::Instant};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::{error, fmt};
|
||||
|
||||
use crate::{
|
||||
event::Event,
|
||||
monitor::{AvailableMonitorsIter, MonitorHandle},
|
||||
platform_impl,
|
||||
};
|
||||
use instant::Instant;
|
||||
use once_cell::sync::OnceCell;
|
||||
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
|
||||
|
||||
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()`
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// Note that this cannot be shared across threads (due to platform-dependant logic
|
||||
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access, the
|
||||
/// [`Window`] created from this _can_ be sent to an other thread, and the
|
||||
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
pub struct EventLoop<T: 'static> {
|
||||
pub(crate) event_loop: platform_impl::EventLoop<T>,
|
||||
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
|
||||
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
|
||||
}
|
||||
|
||||
/// Target that associates windows with an `EventLoop`.
|
||||
/// 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, so functions that take this as a parameter can also
|
||||
/// take `&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
|
||||
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
|
||||
}
|
||||
|
||||
/// Object that allows building the event loop.
|
||||
///
|
||||
/// This is used to make specifying options that affect the whole application
|
||||
/// easier. But note that constructing multiple event loops is not supported.
|
||||
#[derive(Default)]
|
||||
pub struct EventLoopBuilder<T: 'static> {
|
||||
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
|
||||
_p: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl EventLoopBuilder<()> {
|
||||
/// Start building a new event loop.
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self::with_user_event()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopBuilder<T> {
|
||||
/// Start building a new event loop, with the given type as the user event
|
||||
/// type.
|
||||
#[inline]
|
||||
pub fn with_user_event() -> Self {
|
||||
Self {
|
||||
platform_specific: Default::default(),
|
||||
_p: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new event loop.
|
||||
///
|
||||
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
|
||||
/// and only once per application.***
|
||||
///
|
||||
/// Attempting to create the event loop on a different thread, or multiple event loops in
|
||||
/// the same application, will panic. This restriction isn't
|
||||
/// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
|
||||
/// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed
|
||||
/// in the relevant [`platform`] module if the target platform supports creating an event loop on
|
||||
/// any thread.
|
||||
///
|
||||
/// Calling this function will result in display backend initialisation.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux:** Backend type can be controlled 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 that fails,
|
||||
/// will fall back on X11. If this variable is set with any other value, winit will panic.
|
||||
///
|
||||
/// [`platform`]: crate::platform
|
||||
#[inline]
|
||||
pub fn build(&mut self) -> EventLoop<T> {
|
||||
static EVENT_LOOP_CREATED: OnceCell<()> = OnceCell::new();
|
||||
if EVENT_LOOP_CREATED.set(()).is_err() {
|
||||
panic!("Creating EventLoop multiple times is not supported.");
|
||||
}
|
||||
// Certain platforms accept a mutable reference in their API.
|
||||
#[allow(clippy::unnecessary_mut_passed)]
|
||||
EventLoop {
|
||||
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for EventLoop<T> {
|
||||
@@ -57,68 +126,133 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set by the user callback given to the `EventLoop::run` method.
|
||||
/// Set by the user callback given to the [`EventLoop::run`] method.
|
||||
///
|
||||
/// Indicates the desired behavior of the event loop after [`Event::EventsCleared`][events_cleared]
|
||||
/// is emitted. Defaults to `Poll`.
|
||||
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] 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]: ../event/enum.Event.html#variant.EventsCleared
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
/// Almost every change is persistent between multiple calls to the event loop closure within a
|
||||
/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset.
|
||||
/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will
|
||||
/// reset the control flow to [`Poll`].
|
||||
///
|
||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
||||
/// [`Poll`]: Self::Poll
|
||||
#[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.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes
|
||||
/// the events in the queue may be sent before the next `requestAnimationFrame` callback, for
|
||||
/// example when the scaling of the page has changed. This should be treated as an implementation
|
||||
/// detail which should not be relied on.
|
||||
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.
|
||||
///
|
||||
/// Useful for implementing efficient timers. Applications which want to render at the display's
|
||||
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API
|
||||
/// to reduce odds of missed frames.
|
||||
///
|
||||
/// [`Poll`]: Self::Poll
|
||||
WaitUntil(Instant),
|
||||
/// 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,
|
||||
/// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set,
|
||||
/// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
|
||||
/// result in the `control_flow` parameter being reset to `ExitWithCode`.
|
||||
///
|
||||
/// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this
|
||||
/// with exit code 0.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS / WASM:** The supplied exit code is unused.
|
||||
/// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used,
|
||||
/// which can cause surprises with negative exit values (`-42` would end up as `214`). See
|
||||
/// [`std::process::exit`].
|
||||
///
|
||||
/// [`LoopDestroyed`]: Event::LoopDestroyed
|
||||
/// [`Exit`]: ControlFlow::Exit
|
||||
ExitWithCode(i32),
|
||||
}
|
||||
|
||||
impl ControlFlow {
|
||||
/// Alias for [`ExitWithCode`]`(0)`.
|
||||
///
|
||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const Exit: Self = Self::ExitWithCode(0);
|
||||
|
||||
/// Sets this to [`Poll`].
|
||||
///
|
||||
/// [`Poll`]: Self::Poll
|
||||
pub fn set_poll(&mut self) {
|
||||
*self = Self::Poll;
|
||||
}
|
||||
|
||||
/// Sets this to [`Wait`].
|
||||
///
|
||||
/// [`Wait`]: Self::Wait
|
||||
pub fn set_wait(&mut self) {
|
||||
*self = Self::Wait;
|
||||
}
|
||||
|
||||
/// Sets this to [`WaitUntil`]`(instant)`.
|
||||
///
|
||||
/// [`WaitUntil`]: Self::WaitUntil
|
||||
pub fn set_wait_until(&mut self, instant: Instant) {
|
||||
*self = Self::WaitUntil(instant);
|
||||
}
|
||||
|
||||
/// Sets this to [`ExitWithCode`]`(code)`.
|
||||
///
|
||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
||||
pub fn set_exit_with_code(&mut self, code: i32) {
|
||||
*self = Self::ExitWithCode(code);
|
||||
}
|
||||
|
||||
/// Sets this to [`Exit`].
|
||||
///
|
||||
/// [`Exit`]: Self::Exit
|
||||
pub fn set_exit(&mut self) {
|
||||
*self = Self::Exit;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ControlFlow {
|
||||
#[inline(always)]
|
||||
fn default() -> ControlFlow {
|
||||
ControlFlow::Poll
|
||||
fn default() -> Self {
|
||||
Self::Poll
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop<()> {
|
||||
/// Builds a new event loop with a `()` as the user event type.
|
||||
/// Alias for [`EventLoopBuilder::new().build()`].
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
|
||||
#[inline]
|
||||
pub fn new() -> EventLoop<()> {
|
||||
EventLoop::<()>::new_user_event()
|
||||
EventLoopBuilder::new().build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EventLoop<()> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoop<T> {
|
||||
/// Builds a new event 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.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
pub fn new_user_event() -> EventLoop<T> {
|
||||
EventLoop {
|
||||
event_loop: platform_impl::EventLoop::new(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
|
||||
pub fn with_user_event() -> EventLoop<T> {
|
||||
EventLoopBuilder::<T>::with_user_event().build()
|
||||
}
|
||||
|
||||
/// Hijacks the calling thread and initializes the winit event loop with the provided
|
||||
@@ -130,38 +264,26 @@ impl<T> EventLoop<T> {
|
||||
///
|
||||
/// Any values not passed to this function will *not* be dropped.
|
||||
///
|
||||
/// [`ControlFlow`]: ./enum.ControlFlow.html
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11 / Wayland:** The program terminates with exit code 1 if the display server
|
||||
/// disconnects.
|
||||
///
|
||||
/// [`ControlFlow`]: crate::event_loop::ControlFlow
|
||||
#[inline]
|
||||
pub fn run<F>(self, event_handler: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(Event<T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
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.
|
||||
/// 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> {
|
||||
let data = self.event_loop.available_monitors();
|
||||
AvailableMonitorsIter {
|
||||
data: data.into_iter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
@@ -171,19 +293,81 @@ impl<T> Deref for EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to send custom events to `EventLoop`.
|
||||
#[derive(Clone)]
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
/// Returns the list of all the monitors available on the system.
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
||||
self.p
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(|inner| MonitorHandle { inner })
|
||||
}
|
||||
|
||||
/// Returns the primary monitor of the system.
|
||||
///
|
||||
/// Returns `None` if it can't identify any monitor as a primary one.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **Wayland:** Always returns `None`.
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
self.p.primary_monitor()
|
||||
}
|
||||
|
||||
/// Change [`DeviceEvent`] filter mode.
|
||||
///
|
||||
/// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit
|
||||
/// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
|
||||
/// this filter at runtime to explicitly capture them again.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Wayland / macOS / iOS / Android / Web:** Unsupported.
|
||||
///
|
||||
/// [`DeviceEvent`]: crate::event::DeviceEvent
|
||||
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "windows"
|
||||
))]
|
||||
self.p.set_device_event_filter(_filter);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> HasRawDisplayHandle for EventLoopWindowTarget<T> {
|
||||
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
|
||||
fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
self.p.raw_display_handle()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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> {
|
||||
/// Returns an `Err` if the associated [`EventLoop`] no longer exists.
|
||||
///
|
||||
/// [`UserEvent(event)`]: Event::UserEvent
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
self.event_loop_proxy.send_event(event)
|
||||
}
|
||||
}
|
||||
@@ -194,19 +378,34 @@ impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that
|
||||
/// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that
|
||||
/// no longer exists.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct EventLoopClosed;
|
||||
///
|
||||
/// Contains the original event given to [`EventLoopProxy::send_event`].
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct EventLoopClosed<T>(pub T);
|
||||
|
||||
impl fmt::Display for EventLoopClosed {
|
||||
impl<T> fmt::Display for EventLoopClosed<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", error::Error::description(self))
|
||||
f.write_str("Tried to wake up a closed `EventLoop`")
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for EventLoopClosed {
|
||||
fn description(&self) -> &str {
|
||||
"Tried to wake up a closed `EventLoop`"
|
||||
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
|
||||
|
||||
/// Filter controlling the propagation of device events.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum DeviceEventFilter {
|
||||
/// Always filter out device events.
|
||||
Always,
|
||||
/// Filter out device events while the window is not focused.
|
||||
Unfocused,
|
||||
/// Report all device events regardless of window focus.
|
||||
Never,
|
||||
}
|
||||
|
||||
impl Default for DeviceEventFilter {
|
||||
fn default() -> Self {
|
||||
Self::Unfocused
|
||||
}
|
||||
}
|
||||
|
||||
109
src/icon.rs
109
src/icon.rs
@@ -1,4 +1,5 @@
|
||||
use std::{error::Error, fmt, mem};
|
||||
use crate::platform_impl::PlatformIcon;
|
||||
use std::{error::Error, fmt, io, mem};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
@@ -11,8 +12,8 @@ pub(crate) struct Pixel {
|
||||
|
||||
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// An error produced when using `Icon::from_rgba` with invalid arguments.
|
||||
#[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.
|
||||
@@ -25,72 +26,106 @@ pub enum BadIcon {
|
||||
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 {
|
||||
let msg = match self {
|
||||
&BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!(
|
||||
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 {
|
||||
BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height,
|
||||
pixel_count,
|
||||
} => format!(
|
||||
} => 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,
|
||||
),
|
||||
};
|
||||
write!(f, "{}", msg)
|
||||
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadIcon {
|
||||
fn description(&self) -> &str {
|
||||
"A valid icon cannot be created from these arguments"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// An icon used for the window titlebar, taskbar, etc.
|
||||
pub struct Icon {
|
||||
pub(crate) struct RgbaIcon {
|
||||
pub(crate) rgba: Vec<u8>,
|
||||
pub(crate) width: u32,
|
||||
pub(crate) height: u32,
|
||||
}
|
||||
|
||||
/// For platforms which don't have window icons (e.g. web)
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct NoIcon;
|
||||
|
||||
#[allow(dead_code)] // These are not used on every platform
|
||||
mod constructors {
|
||||
use super::*;
|
||||
|
||||
impl RgbaIcon {
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
if rgba.len() % PIXEL_SIZE != 0 {
|
||||
return Err(BadIcon::ByteCountNotDivisibleBy4 {
|
||||
byte_count: rgba.len(),
|
||||
});
|
||||
}
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
if pixel_count != (width * height) as usize {
|
||||
Err(BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height: (width * height) as usize,
|
||||
pixel_count,
|
||||
})
|
||||
} else {
|
||||
Ok(RgbaIcon {
|
||||
rgba,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NoIcon {
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
// Create the rgba icon anyway to validate the input
|
||||
let _ = RgbaIcon::from_rgba(rgba, width, height)?;
|
||||
Ok(NoIcon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An icon used for the window titlebar, taskbar, etc.
|
||||
#[derive(Clone)]
|
||||
pub struct Icon {
|
||||
pub(crate) inner: PlatformIcon,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Icon {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
fmt::Debug::fmt(&self.inner, formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
/// Creates an `Icon` from 32bpp RGBA data.
|
||||
/// 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(Icon {
|
||||
rgba,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
Ok(Icon {
|
||||
inner: PlatformIcon::from_rgba(rgba, width, height)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
159
src/lib.rs
159
src/lib.rs
@@ -1,6 +1,6 @@
|
||||
//! 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 [`EventLoop`]. This is done with the
|
||||
//! [`EventLoop::new()`] function.
|
||||
@@ -15,31 +15,36 @@
|
||||
//! - 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 simplest way and will give you default values for everything.
|
||||
//!
|
||||
//! 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`].
|
||||
//! 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`].
|
||||
//!
|
||||
//! # Event handling
|
||||
//!
|
||||
//! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can
|
||||
//! generate a [`WindowEvent`] when certain things happen, like whenever the user moves their mouse
|
||||
//! or presses a key inside the [`Window`]. Devices can generate a [`DeviceEvent`] directly as well,
|
||||
//! which contains 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.
|
||||
//! 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.
|
||||
//!
|
||||
//! Events can be retreived by using an [`EventLoop`]. A [`Window`] will send its events to the
|
||||
//! [`EventLoop`] object it was created with.
|
||||
//! You can retrieve 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`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which
|
||||
//! point [`Event`]`::`[`LoopDestroyed`] is emitted and the entire program terminates.
|
||||
//!
|
||||
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
||||
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
|
||||
//! most other platforms. However, this model can be re-implemented to an extent with
|
||||
//! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why
|
||||
//! it's discouraged, beyond compatibility reasons.
|
||||
//!
|
||||
//! You do this by calling [`event_loop.run(...)`][event_loop_run]. This function will run forever
|
||||
//! unless `control_flow` is set to [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`]
|
||||
//! is emitted and the entire program terminates.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use winit::{
|
||||
//! event::{Event, WindowEvent},
|
||||
//! event_loop::{ControlFlow, EventLoop},
|
||||
//! event_loop::EventLoop,
|
||||
//! window::WindowBuilder,
|
||||
//! };
|
||||
//!
|
||||
@@ -47,84 +52,98 @@
|
||||
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
//!
|
||||
//! event_loop.run(move |event, _, control_flow| {
|
||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
||||
//! // dispatched any events. This is ideal for games and similar applications.
|
||||
//! control_flow.set_poll();
|
||||
//!
|
||||
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
|
||||
//! // This is ideal for non-game applications that only update in response to user
|
||||
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
||||
//! control_flow.set_wait();
|
||||
//!
|
||||
//! match event {
|
||||
//! Event::EventsCleared => {
|
||||
//! // Application update code.
|
||||
//!
|
||||
//! // Queue a RedrawRequested event.
|
||||
//! window.request_redraw();
|
||||
//! },
|
||||
//! Event::WindowEvent {
|
||||
//! event: WindowEvent::RedrawRequested,
|
||||
//! ..
|
||||
//! } => {
|
||||
//! // Redraw the application.
|
||||
//! //
|
||||
//! // It's preferrable to render in this event rather than in EventsCleared, since
|
||||
//! // rendering in here allows the program to gracefully handle redraws requested
|
||||
//! // by the OS.
|
||||
//! },
|
||||
//! Event::WindowEvent {
|
||||
//! event: WindowEvent::CloseRequested,
|
||||
//! ..
|
||||
//! } => {
|
||||
//! println!("The close button was pressed; stopping");
|
||||
//! *control_flow = ControlFlow::Exit
|
||||
//! control_flow.set_exit();
|
||||
//! },
|
||||
//! // 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,
|
||||
//! // 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,
|
||||
//! 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 [`Window`]s, [`Event`]`::`[`WindowEvent`] has a member named `window_id`. You can
|
||||
//! compare it with the value returned by the [`id()`][window_id_fn] 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
|
||||
//! retrieve the raw handle of the window (see the [`platform`] module), 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 and display (see the [`platform`] module and/or the
|
||||
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
|
||||
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
||||
//!
|
||||
//! [`EventLoop`]: ./event_loop/struct.EventLoop.html
|
||||
//! [`EventLoop::new()`]: ./event_loop/struct.EventLoop.html#method.new
|
||||
//! [event_loop_run]: ./event_loop/struct.EventLoop.html#method.run
|
||||
//! [`ControlFlow`]: ./event_loop/enum.ControlFlow.html
|
||||
//! [`Exit`]: ./event_loop/enum.ControlFlow.html#variant.Exit
|
||||
//! [`Window`]: ./window/struct.Window.html
|
||||
//! [`WindowBuilder`]: ./window/struct.WindowBuilder.html
|
||||
//! [window_new]: ./window/struct.Window.html#method.new
|
||||
//! [window_builder_new]: ./window/struct.WindowBuilder.html#method.new
|
||||
//! [window_builder_build]: ./window/struct.WindowBuilder.html#method.build
|
||||
//! [window_id_fn]: ./window/struct.Window.html#method.id
|
||||
//! [`Event`]: ./event/enum.Event.html
|
||||
//! [`WindowEvent`]: ./event/enum.WindowEvent.html
|
||||
//! [`DeviceEvent`]: ./event/enum.DeviceEvent.html
|
||||
//! [`UserEvent`]: ./event/enum.Event.html#variant.UserEvent
|
||||
//! [`LoopDestroyed`]: ./event/enum.Event.html#variant.LoopDestroyed
|
||||
//! [`platform`]: ./platform/index.html
|
||||
//! 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
|
||||
//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.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
|
||||
//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode
|
||||
//! [`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
|
||||
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
|
||||
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
#![deny(clippy::all)]
|
||||
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "serde")]
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate derivative;
|
||||
#[macro_use]
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate bitflags;
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
#[macro_use]
|
||||
|
||||
180
src/monitor.rs
180
src/monitor.rs
@@ -1,68 +1,58 @@
|
||||
//! 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_id]
|
||||
//! type. This is retreived from an [`AvailableMonitorsIter`][monitor_iter], which can be acquired
|
||||
//! with:
|
||||
//! - [`EventLoop::available_monitors`][loop_get]
|
||||
//! - [`Window::available_monitors`][window_get].
|
||||
//!
|
||||
//! [monitor_id]: ./struct.MonitorHandle.html
|
||||
//! [monitor_iter]: ./struct.AvailableMonitorsIter.html
|
||||
//! [loop_get]: ../event_loop/struct.EventLoop.html#method.available_monitors
|
||||
//! [window_get]: ../window/struct.Window.html#method.available_monitors
|
||||
use std::collections::vec_deque::IntoIter as VecDequeIter;
|
||||
|
||||
//! If you want to get basic information about a monitor, you can use the
|
||||
//! [`MonitorHandle`] type. This is retrieved from one of the following
|
||||
//! methods, which return an iterator of [`MonitorHandle`]:
|
||||
//! - [`EventLoopWindowTarget::available_monitors`](crate::event_loop::EventLoopWindowTarget::available_monitors).
|
||||
//! - [`Window::available_monitors`](crate::window::Window::available_monitors).
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
platform_impl,
|
||||
};
|
||||
|
||||
/// An iterator over all available monitors.
|
||||
///
|
||||
/// Can be acquired with:
|
||||
/// - [`EventLoop::available_monitors`][loop_get]
|
||||
/// - [`Window::available_monitors`][window_get].
|
||||
///
|
||||
/// [loop_get]: ../event_loop/struct.EventLoop.html#method.available_monitors
|
||||
/// [window_get]: ../window/struct.Window.html#method.available_monitors
|
||||
// Implementation note: we retrieve the list once, then serve each element by one by one.
|
||||
// This may change in the future.
|
||||
#[derive(Debug)]
|
||||
pub struct AvailableMonitorsIter {
|
||||
pub(crate) data: VecDequeIter<platform_impl::MonitorHandle>,
|
||||
}
|
||||
|
||||
impl Iterator for AvailableMonitorsIter {
|
||||
type Item = MonitorHandle;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<MonitorHandle> {
|
||||
self.data.next().map(|id| MonitorHandle { inner: id })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.data.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a fullscreen video mode of a monitor.
|
||||
///
|
||||
/// Can be acquired with:
|
||||
/// - [`MonitorHandle::video_modes`][monitor_get].
|
||||
///
|
||||
/// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
/// Can be acquired with [`MonitorHandle::video_modes`].
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: (u32, u32),
|
||||
pub(crate) bit_depth: u16,
|
||||
pub(crate) refresh_rate: u16,
|
||||
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_millihertz()
|
||||
.cmp(&other.refresh_rate_millihertz())
|
||||
.then(self.bit_depth().cmp(&other.bit_depth())),
|
||||
)
|
||||
.reverse(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
/// Returns the resolution of this video mode.
|
||||
pub fn size(&self) -> PhysicalSize {
|
||||
self.size.into()
|
||||
#[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
|
||||
@@ -73,15 +63,35 @@ impl VideoMode {
|
||||
///
|
||||
/// - **Wayland:** Always returns 32.
|
||||
/// - **iOS:** Always returns 32.
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> u16 {
|
||||
self.bit_depth
|
||||
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.
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.refresh_rate
|
||||
/// Returns the refresh rate of this video mode in mHz.
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
||||
self.video_mode.refresh_rate_millihertz()
|
||||
}
|
||||
|
||||
/// Returns the monitor that this video mode is valid for. Each monitor has
|
||||
/// a separate set of valid video modes.
|
||||
#[inline]
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.video_mode.monitor()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VideoMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}x{} @ {} mHz ({} bpp)",
|
||||
self.size().width,
|
||||
self.size().height,
|
||||
self.refresh_rate_millihertz(),
|
||||
self.bit_depth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +99,8 @@ impl VideoMode {
|
||||
///
|
||||
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
|
||||
///
|
||||
/// [`Window`]: ../window/struct.Window.html
|
||||
#[derive(Debug, Clone)]
|
||||
/// [`Window`]: crate::window::Window
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct MonitorHandle {
|
||||
pub(crate) inner: platform_impl::MonitorHandle,
|
||||
}
|
||||
@@ -99,38 +109,64 @@ 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 {
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.inner.size()
|
||||
}
|
||||
|
||||
/// Returns the top-left corner position of the monitor relative to the larger full
|
||||
/// screen area.
|
||||
#[inline]
|
||||
pub fn position(&self) -> PhysicalPosition {
|
||||
self.inner.position()
|
||||
}
|
||||
|
||||
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
///
|
||||
/// See the [`dpi`](../dpi/index.html) module for more information.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
|
||||
/// - **Android:** Always returns 1.0.
|
||||
/// - **Web:** Always returns (0,0)
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f64 {
|
||||
self.inner.hidpi_factor()
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
self.inner.position()
|
||||
}
|
||||
|
||||
/// The monitor refresh rate used by the system.
|
||||
///
|
||||
/// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to
|
||||
/// enter fullscreen should be used instead.
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
self.inner.refresh_rate_millihertz()
|
||||
}
|
||||
|
||||
/// Returns the scale factor 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,33 +1,40 @@
|
||||
#![cfg(any(target_os = "android"))]
|
||||
|
||||
use crate::{EventLoop, Window, WindowBuilder};
|
||||
use std::os::raw::c_void;
|
||||
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 {
|
||||
/// Makes it possible for glutin to register a callback when a suspend event happens on Android
|
||||
fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>);
|
||||
}
|
||||
/// Additional methods on [`EventLoop`] that are specific to Android.
|
||||
pub trait EventLoopExtAndroid {}
|
||||
|
||||
impl EventLoopExtAndroid for EventLoop {
|
||||
fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>) {
|
||||
self.event_loop.set_suspend_callback(cb);
|
||||
}
|
||||
}
|
||||
impl<T> EventLoopExtAndroid for EventLoop<T> {}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Android.
|
||||
/// 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 native_window(&self) -> *const c_void;
|
||||
fn content_rect(&self) -> Rect;
|
||||
|
||||
fn config(&self) -> Configuration;
|
||||
}
|
||||
|
||||
impl WindowExtAndroid for Window {
|
||||
#[inline]
|
||||
fn native_window(&self) -> *const c_void {
|
||||
self.window.native_window()
|
||||
fn content_rect(&self) -> Rect {
|
||||
self.window.content_rect()
|
||||
}
|
||||
|
||||
fn config(&self) -> Configuration {
|
||||
self.window.config()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Android.
|
||||
impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
|
||||
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to Android.
|
||||
pub trait WindowBuilderExtAndroid {}
|
||||
|
||||
impl WindowBuilderExtAndroid for WindowBuilder {}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#![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<T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
self.event_loop.run_return(event_handler)
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,13 @@ use std::os::raw::c_void;
|
||||
|
||||
use crate::{
|
||||
event_loop::EventLoop,
|
||||
monitor::MonitorHandle,
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to iOS.
|
||||
/// Additional methods on [`EventLoop`] that are specific to iOS.
|
||||
pub trait EventLoopExtIOS {
|
||||
/// Returns the idiom (phone/tablet/tv/etc) for the current device.
|
||||
/// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
|
||||
fn idiom(&self) -> Idiom;
|
||||
}
|
||||
|
||||
@@ -20,32 +20,80 @@ impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to iOS.
|
||||
/// Additional methods on [`Window`] that are specific to iOS.
|
||||
pub trait WindowExtIOS {
|
||||
/// Returns a pointer to the `UIWindow` that is used by this window.
|
||||
/// Returns a pointer to the [`UIWindow`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
/// 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.
|
||||
/// Returns a pointer to the [`UIViewController`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
/// 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.
|
||||
/// Returns a pointer to the [`UIView`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
/// 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 HiDpi factor used by this window.
|
||||
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
|
||||
///
|
||||
/// This translates to `-[UIWindow setContentScaleFactor:hidpi_factor]`.
|
||||
fn set_hidpi_factor(&self, hidpi_factor: f64);
|
||||
/// 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 screens showing this `Window`.
|
||||
/// Sets the valid orientations for the [`Window`].
|
||||
///
|
||||
/// On iPhones and iPods upside down portrait is never enabled.
|
||||
/// 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 {
|
||||
@@ -65,31 +113,87 @@ impl WindowExtIOS for Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_hidpi_factor(&self, hidpi_factor: f64) {
|
||||
self.window.set_hidpi_factor(hidpi_factor)
|
||||
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.
|
||||
/// 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.
|
||||
/// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided.
|
||||
///
|
||||
/// The class will be initialized by calling `[root_view initWithFrame:CGRect]`
|
||||
/// 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 `hidpi_factor`.
|
||||
/// 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::hidpi_factor()`.
|
||||
fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder;
|
||||
/// 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`.
|
||||
/// 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 {
|
||||
@@ -100,8 +204,8 @@ impl WindowBuilderExtIOS for WindowBuilder {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_hidpi_factor(mut self, hidpi_factor: f64) -> WindowBuilder {
|
||||
self.platform_specific.hidpi_factor = Some(hidpi_factor);
|
||||
fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder {
|
||||
self.platform_specific.scale_factor = Some(scale_factor);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -110,12 +214,41 @@ impl WindowBuilderExtIOS for 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.
|
||||
/// Additional methods on [`MonitorHandle`] that are specific to iOS.
|
||||
pub trait MonitorHandleExtIOS {
|
||||
/// Returns a pointer to the `UIScreen` that is used by this monitor.
|
||||
/// 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 {
|
||||
@@ -123,9 +256,14 @@ impl MonitorHandleExtIOS for MonitorHandle {
|
||||
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`.
|
||||
/// Valid orientations for a particular [`Window`].
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ValidOrientations {
|
||||
/// Excludes `PortraitUpsideDown` on iphone
|
||||
@@ -161,3 +299,19 @@ pub enum Idiom {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,46 +4,23 @@ use std::os::raw::c_void;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event_loop::{EventLoopBuilder, 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.
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// 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;
|
||||
|
||||
@@ -55,6 +32,12 @@ pub trait WindowExtMacOS {
|
||||
/// And allows the user to have a fullscreen window without using another
|
||||
/// space or taking control over the entire monitor.
|
||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
|
||||
|
||||
/// Returns whether or not the window has shadow.
|
||||
fn has_shadow(&self) -> bool;
|
||||
|
||||
/// Sets whether or not the window has shadow.
|
||||
fn set_has_shadow(&self, has_shadow: bool);
|
||||
}
|
||||
|
||||
impl WindowExtMacOS for Window {
|
||||
@@ -68,11 +51,6 @@ impl WindowExtMacOS for Window {
|
||||
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()
|
||||
@@ -82,10 +60,20 @@ impl WindowExtMacOS for Window {
|
||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
||||
self.window.set_simple_fullscreen(fullscreen)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn has_shadow(&self) -> bool {
|
||||
self.window.has_shadow()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_has_shadow(&self, has_shadow: bool) {
|
||||
self.window.set_has_shadow(has_shadow)
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ActivationPolicy {
|
||||
/// Corresponds to `NSApplicationActivationPolicyRegular`.
|
||||
Regular,
|
||||
@@ -101,19 +89,15 @@ impl Default for ActivationPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to MacOS.
|
||||
/// 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`
|
||||
/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method:
|
||||
/// - `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;
|
||||
@@ -128,16 +112,12 @@ pub trait WindowBuilderExtMacOS {
|
||||
/// 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) -> WindowBuilder;
|
||||
fn with_resize_increments(self, increments: LogicalSize<f64>) -> WindowBuilder;
|
||||
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
|
||||
fn with_has_shadow(self, has_shadow: 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,
|
||||
@@ -178,13 +158,85 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
|
||||
self.platform_specific.resize_increments = Some(increments.into());
|
||||
fn with_resize_increments(mut self, increments: LogicalSize<f64>) -> WindowBuilder {
|
||||
self.platform_specific.resize_increments = Some(increments);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
|
||||
self.platform_specific.disallow_hidpi = disallow_hidpi;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder {
|
||||
self.platform_specific.has_shadow = has_shadow;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorHandle` that are specific to MacOS.
|
||||
pub trait EventLoopBuilderExtMacOS {
|
||||
/// Sets the activation policy for the application.
|
||||
///
|
||||
/// It is set to [`ActivationPolicy::Regular`] by default.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Set the activation policy to "accessory".
|
||||
///
|
||||
/// ```
|
||||
/// use winit::event_loop::EventLoopBuilder;
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// use winit::platform::macos::{EventLoopBuilderExtMacOS, ActivationPolicy};
|
||||
///
|
||||
/// let mut builder = EventLoopBuilder::new();
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// builder.with_activation_policy(ActivationPolicy::Accessory);
|
||||
/// # if false { // We can't test this part
|
||||
/// let event_loop = builder.build();
|
||||
/// # }
|
||||
/// ```
|
||||
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self;
|
||||
|
||||
/// Used to control whether a default menubar menu is created.
|
||||
///
|
||||
/// Menu creation is enabled by default.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Disable creating a default menubar.
|
||||
///
|
||||
/// ```
|
||||
/// use winit::event_loop::EventLoopBuilder;
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// use winit::platform::macos::EventLoopBuilderExtMacOS;
|
||||
///
|
||||
/// let mut builder = EventLoopBuilder::new();
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// builder.with_default_menu(false);
|
||||
/// # if false { // We can't test this part
|
||||
/// let event_loop = builder.build();
|
||||
/// # }
|
||||
/// ```
|
||||
fn with_default_menu(&mut self, enable: bool) -> &mut Self;
|
||||
}
|
||||
|
||||
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
|
||||
#[inline]
|
||||
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
|
||||
self.platform_specific.activation_policy = activation_policy;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_default_menu(&mut self, enable: bool) -> &mut Self {
|
||||
self.platform_specific.default_menu = enable;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
@@ -202,3 +254,21 @@ impl MonitorHandleExtMacOS for MonitorHandle {
|
||||
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);
|
||||
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
|
||||
fn hide_other_applications(&self);
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
||||
fn hide_application(&self) {
|
||||
self.p.hide_application()
|
||||
}
|
||||
|
||||
fn hide_other_applications(&self) {
|
||||
self.p.hide_other_applications()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
//! - `macos`
|
||||
//! - `unix`
|
||||
//! - `windows`
|
||||
//! - `web`
|
||||
//!
|
||||
//! And the following platform-specific module:
|
||||
//!
|
||||
//! - `desktop` (available on `windows`, `unix`, and `macos`)
|
||||
//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`)
|
||||
//!
|
||||
//! However only the module corresponding to the platform you're compiling to will be available.
|
||||
|
||||
@@ -18,6 +19,7 @@ pub mod android;
|
||||
pub mod ios;
|
||||
pub mod macos;
|
||||
pub mod unix;
|
||||
pub mod web;
|
||||
pub mod windows;
|
||||
|
||||
pub mod desktop;
|
||||
pub mod run_return;
|
||||
|
||||
64
src/platform/run_return.rs
Normal file
64
src/platform/run_return.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
#![cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "android",
|
||||
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`] to return control flow to the caller.
|
||||
pub trait EventLoopExtRunReturn {
|
||||
/// A type provided by the user that can be passed through [`Event::UserEvent`].
|
||||
type UserEvent;
|
||||
|
||||
/// Initializes the `winit` event loop.
|
||||
///
|
||||
/// Unlike [`EventLoop::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 appearance 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
|
||||
/// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions.
|
||||
///
|
||||
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11 / Wayland:** This function returns `1` upon disconnection from
|
||||
/// the display server.
|
||||
fn run_return<F>(&mut self, event_handler: F) -> i32
|
||||
where
|
||||
F: FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtRunReturn for EventLoop<T> {
|
||||
type UserEvent = T;
|
||||
|
||||
fn run_return<F>(&mut self, event_handler: F) -> i32
|
||||
where
|
||||
F: FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
),
|
||||
{
|
||||
self.event_loop.run_return(event_handler)
|
||||
}
|
||||
}
|
||||
@@ -1,298 +1,309 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
#![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, Theme};
|
||||
use std::os::raw;
|
||||
#[cfg(feature = "x11")]
|
||||
use std::{ptr, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event_loop::EventLoop,
|
||||
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
use crate::dpi::Size;
|
||||
#[cfg(feature = "x11")]
|
||||
use crate::platform_impl::{x11::ffi::XVisualInfo, x11::XConnection, XLIB_ERROR_HOOKS};
|
||||
use crate::platform_impl::{
|
||||
x11::{ffi::XVisualInfo, XConnection},
|
||||
EventLoop as LinuxEventLoop, Window as LinuxWindow,
|
||||
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
|
||||
Window as LinuxWindow,
|
||||
};
|
||||
|
||||
// TODO: stupid hack so that glutin can do its work
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "x11")]
|
||||
pub use crate::platform_impl::x11;
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
|
||||
|
||||
/// Theme for wayland client side decorations
|
||||
#[cfg(feature = "wayland")]
|
||||
pub use crate::window::Theme;
|
||||
|
||||
/// The first argument in the provided hook will be the pointer to `XDisplay`
|
||||
/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
|
||||
/// indicator whether the error was handled by the callback.
|
||||
///
|
||||
/// Colors must be in ARGB8888 format
|
||||
pub struct WaylandTheme {
|
||||
/// Primary color when the window is focused
|
||||
pub primary_active: [u8; 4],
|
||||
/// Primary color when the window is unfocused
|
||||
pub primary_inactive: [u8; 4],
|
||||
/// Secondary color when the window is focused
|
||||
pub secondary_active: [u8; 4],
|
||||
/// Secondary color when the window is unfocused
|
||||
pub secondary_inactive: [u8; 4],
|
||||
/// Close button color when hovered over
|
||||
pub close_button_hovered: [u8; 4],
|
||||
/// Close button color
|
||||
pub close_button: [u8; 4],
|
||||
/// Close button color when hovered over
|
||||
pub maximize_button_hovered: [u8; 4],
|
||||
/// Maximize button color
|
||||
pub maximize_button: [u8; 4],
|
||||
/// Minimize button color when hovered over
|
||||
pub minimize_button_hovered: [u8; 4],
|
||||
/// Minimize button color
|
||||
pub minimize_button: [u8; 4],
|
||||
}
|
||||
/// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent
|
||||
#[cfg(feature = "x11")]
|
||||
pub type XlibErrorHook =
|
||||
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
|
||||
|
||||
struct WaylandThemeObject(WaylandTheme);
|
||||
|
||||
impl Theme for WaylandThemeObject {
|
||||
fn get_primary_color(&self, active: bool) -> [u8; 4] {
|
||||
if active {
|
||||
self.0.primary_active
|
||||
} else {
|
||||
self.0.primary_inactive
|
||||
}
|
||||
}
|
||||
|
||||
// Used for division line
|
||||
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
|
||||
if active {
|
||||
self.0.secondary_active
|
||||
} else {
|
||||
self.0.secondary_inactive
|
||||
}
|
||||
}
|
||||
|
||||
fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.close_button_hovered,
|
||||
_ => self.0.close_button,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.maximize_button_hovered,
|
||||
_ => self.0.maximize_button,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.minimize_button_hovered,
|
||||
_ => self.0.minimize_button,
|
||||
}
|
||||
/// Hook to winit's xlib error handling callback.
|
||||
///
|
||||
/// This method is provided as a safe way to handle the errors comming from X11 when using xlib
|
||||
/// in external crates, like glutin for GLX access. Trying to handle errors by speculating with
|
||||
/// `XSetErrorHandler` is [`unsafe`].
|
||||
///
|
||||
/// [`unsafe`]: https://www.remlab.net/op/xlib.shtml
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
|
||||
// Append new hook.
|
||||
unsafe {
|
||||
XLIB_ERROR_HOOKS.lock().push(hook);
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Unix.
|
||||
pub trait EventLoopExtUnix {
|
||||
/// Builds a new `EventLoops` that is forced to use X11.
|
||||
fn new_x11() -> Result<Self, XNotSupported>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Builds a new `EventLoop` that is forced to use Wayland.
|
||||
fn new_wayland() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// True if the `EventLoop` uses Wayland.
|
||||
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Unix.
|
||||
pub trait EventLoopWindowTargetExtUnix {
|
||||
/// True if the [`EventLoopWindowTarget`] uses Wayland.
|
||||
#[cfg(feature = "wayland")]
|
||||
fn is_wayland(&self) -> bool;
|
||||
|
||||
/// True if the `EventLoop` uses X11.
|
||||
/// True if the [`EventLoopWindowTarget`] uses X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn is_x11(&self) -> bool;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "x11")]
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
|
||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this `EventLoop`.
|
||||
/// 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).
|
||||
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `EventLoop` is destroyed.
|
||||
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
|
||||
///
|
||||
/// [`EventLoop`]: crate::event_loop::EventLoop
|
||||
#[cfg(feature = "wayland")]
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtUnix for EventLoop<T> {
|
||||
#[inline]
|
||||
fn new_x11() -> Result<Self, XNotSupported> {
|
||||
LinuxEventLoop::new_x11().map(|ev| EventLoop {
|
||||
event_loop: ev,
|
||||
_marker: ::std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_wayland() -> Self {
|
||||
EventLoop {
|
||||
event_loop: match LinuxEventLoop::new_wayland() {
|
||||
Ok(e) => e,
|
||||
Err(_) => panic!(), // TODO: propagate
|
||||
},
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland")]
|
||||
fn is_wayland(&self) -> bool {
|
||||
self.event_loop.is_wayland()
|
||||
self.p.is_wayland()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn is_x11(&self) -> bool {
|
||||
!self.event_loop.is_wayland()
|
||||
!self.p.is_wayland()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "x11")]
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
match self.event_loop {
|
||||
LinuxEventLoop::X(ref e) => Some(e.x_connection().clone()),
|
||||
match self.p {
|
||||
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
|
||||
#[cfg(feature = "wayland")]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland")]
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.event_loop {
|
||||
LinuxEventLoop::Wayland(ref e) => Some(e.display().get_display_ptr() as *mut _),
|
||||
match self.p {
|
||||
LinuxEventLoopWindowTarget::Wayland(ref p) => {
|
||||
Some(p.display().get_display_ptr() as *mut _)
|
||||
}
|
||||
#[cfg(feature = "x11")]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Unix.
|
||||
/// Additional methods on [`EventLoopBuilder`] that are specific to Unix.
|
||||
pub trait EventLoopBuilderExtUnix {
|
||||
/// Force using X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_x11(&mut self) -> &mut Self;
|
||||
|
||||
/// Force using Wayland.
|
||||
#[cfg(feature = "wayland")]
|
||||
fn with_wayland(&mut self) -> &mut Self;
|
||||
|
||||
/// Whether to allow the event loop to be created off of the main thread.
|
||||
///
|
||||
/// By default, the window is only allowed to be created on the main
|
||||
/// thread, to make platform compatibility easier.
|
||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
|
||||
}
|
||||
|
||||
impl<T> EventLoopBuilderExtUnix for EventLoopBuilder<T> {
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_x11(&mut self) -> &mut Self {
|
||||
self.platform_specific.forced_backend = Some(Backend::X);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland")]
|
||||
fn with_wayland(&mut self) -> &mut Self {
|
||||
self.platform_specific.forced_backend = Some(Backend::Wayland);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
|
||||
self.platform_specific.any_thread = any_thread;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to Unix.
|
||||
pub trait WindowExtUnix {
|
||||
/// Returns the ID of the `Window` xlib object that is used by this window.
|
||||
/// 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).
|
||||
#[cfg(feature = "x11")]
|
||||
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.
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
#[cfg(feature = "x11")]
|
||||
fn xlib_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
fn xlib_screen_id(&self) -> Option<raw::c_int>;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "x11")]
|
||||
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.
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
#[cfg(feature = "x11")]
|
||||
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.
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
#[cfg(feature = "wayland")]
|
||||
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.
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
#[cfg(feature = "wayland")]
|
||||
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(&self, theme: WaylandTheme);
|
||||
/// Updates [`Theme`] of window decorations.
|
||||
///
|
||||
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
|
||||
/// Possible values for env variable are: "dark" and light"
|
||||
#[cfg(feature = "wayland")]
|
||||
fn wayland_set_csd_theme(&self, config: Theme);
|
||||
|
||||
/// 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.
|
||||
/// Always return `true`.
|
||||
#[deprecated]
|
||||
fn is_ready(&self) -> bool;
|
||||
}
|
||||
|
||||
impl WindowExtUnix for Window {
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn xlib_window(&self) -> Option<raw::c_ulong> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_window()),
|
||||
#[cfg(feature = "wayland")]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn xlib_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_display()),
|
||||
#[cfg(feature = "wayland")]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn xlib_screen_id(&self) -> Option<raw::c_int> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
|
||||
#[cfg(feature = "wayland")]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "x11")]
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_xconnection()),
|
||||
#[cfg(feature = "wayland")]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
|
||||
#[cfg(feature = "wayland")]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_urgent(&self, is_urgent: bool) {
|
||||
if let LinuxWindow::X(ref w) = self.window {
|
||||
w.set_urgent(is_urgent);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland")]
|
||||
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 _),
|
||||
#[cfg(feature = "x11")]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland")]
|
||||
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 _),
|
||||
LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _),
|
||||
#[cfg(feature = "x11")]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_wayland_theme(&self, theme: WaylandTheme) {
|
||||
#[cfg(feature = "wayland")]
|
||||
fn wayland_set_csd_theme(&self, theme: Theme) {
|
||||
#[allow(clippy::single_match)]
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)),
|
||||
_ => {}
|
||||
LinuxWindow::Wayland(ref w) => w.set_csd_theme(theme),
|
||||
#[cfg(feature = "x11")]
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,85 +313,139 @@ impl WindowExtUnix for Window {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Unix.
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to Unix.
|
||||
pub trait WindowBuilderExtUnix {
|
||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> WindowBuilder;
|
||||
fn with_x11_screen(self, screen_id: i32) -> WindowBuilder;
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> 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) -> WindowBuilder;
|
||||
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
|
||||
fn with_override_redirect(self, override_redirect: bool) -> WindowBuilder;
|
||||
/// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11.
|
||||
fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder;
|
||||
/// 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) -> WindowBuilder;
|
||||
/// Build window with resize increment hint. Only implemented on X11.
|
||||
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
|
||||
/// Build window with base size hint. Only implemented on X11.
|
||||
fn with_base_size(self, base_size: LogicalSize) -> WindowBuilder;
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_x11_screen(self, screen_id: i32) -> Self;
|
||||
|
||||
/// Build window with a given application ID. It should match the `.desktop` file distributed with
|
||||
/// your program. Only relevant on Wayland.
|
||||
/// Build window with the given `general` and `instance` names.
|
||||
///
|
||||
/// On Wayland, the `general` name sets an application ID, which should match the `.desktop`
|
||||
/// file destributed with your program. The `instance` is a `no-op`.
|
||||
///
|
||||
/// On X11, the `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
|
||||
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
|
||||
///
|
||||
/// 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) -> WindowBuilder;
|
||||
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
|
||||
|
||||
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
|
||||
#[cfg(feature = "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.
|
||||
#[cfg(feature = "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.
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_gtk_theme_variant(self, variant: String) -> Self;
|
||||
|
||||
/// Build window with certain decoration [`Theme`]
|
||||
///
|
||||
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
|
||||
/// Possible values for env variable are: "dark" and light"
|
||||
#[cfg(feature = "wayland")]
|
||||
fn with_wayland_csd_theme(self, theme: Theme) -> Self;
|
||||
|
||||
/// Build window with resize increment hint. Only implemented on X11.
|
||||
///
|
||||
/// ```
|
||||
/// # use winit::dpi::{LogicalSize, PhysicalSize};
|
||||
/// # use winit::window::WindowBuilder;
|
||||
/// # use winit::platform::unix::WindowBuilderExtUnix;
|
||||
/// // Specify the size in logical dimensions like this:
|
||||
/// WindowBuilder::new().with_resize_increments(LogicalSize::new(400.0, 200.0));
|
||||
///
|
||||
/// // Or specify the size in physical dimensions like this:
|
||||
/// WindowBuilder::new().with_resize_increments(PhysicalSize::new(400, 200));
|
||||
/// ```
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
|
||||
|
||||
/// Build window with base size hint. Only implemented on X11.
|
||||
///
|
||||
/// ```
|
||||
/// # use winit::dpi::{LogicalSize, PhysicalSize};
|
||||
/// # use winit::window::WindowBuilder;
|
||||
/// # use winit::platform::unix::WindowBuilderExtUnix;
|
||||
/// // Specify the size in logical dimensions like this:
|
||||
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
|
||||
///
|
||||
/// // Or specify the size in physical dimensions like this:
|
||||
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
|
||||
/// ```
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtUnix 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) });
|
||||
#[cfg(feature = "x11")]
|
||||
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) -> WindowBuilder {
|
||||
#[cfg(feature = "x11")]
|
||||
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) -> WindowBuilder {
|
||||
self.platform_specific.class = Some((instance, class));
|
||||
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
|
||||
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_override_redirect(mut self, override_redirect: bool) -> WindowBuilder {
|
||||
#[cfg(feature = "x11")]
|
||||
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_type: XWindowType) -> WindowBuilder {
|
||||
self.platform_specific.x11_window_type = x11_window_type;
|
||||
#[cfg(feature = "x11")]
|
||||
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_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
|
||||
self.platform_specific.resize_increments = Some(increments.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_base_size(mut self, base_size: LogicalSize) -> WindowBuilder {
|
||||
self.platform_specific.base_size = Some(base_size.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_gtk_theme_variant(mut self, variant: String) -> WindowBuilder {
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_gtk_theme_variant(mut self, variant: String) -> Self {
|
||||
self.platform_specific.gtk_theme_variant = Some(variant);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_app_id(mut self, app_id: String) -> WindowBuilder {
|
||||
self.platform_specific.app_id = Some(app_id);
|
||||
#[cfg(feature = "wayland")]
|
||||
fn with_wayland_csd_theme(mut self, theme: Theme) -> Self {
|
||||
self.platform_specific.csd_theme = Some(theme);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self {
|
||||
self.platform_specific.resize_increments = Some(increments.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
|
||||
self.platform_specific.base_size = Some(base_size.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
91
src/platform/web.rs
Normal file
91
src/platform/web.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
#![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 [`WindowExtWebSys`] trait
|
||||
//! to retrieve the canvas from the Window. Alternatively, use the [`WindowBuilderExtWebSys`] trait
|
||||
//! to provide your own canvas.
|
||||
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::ControlFlow;
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::event_loop::EventLoopWindowTarget;
|
||||
use crate::window::WindowBuilder;
|
||||
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
pub trait WindowExtWebSys {
|
||||
fn canvas(&self) -> HtmlCanvasElement;
|
||||
|
||||
/// Whether the browser reports the preferred color scheme to be "dark".
|
||||
fn is_dark_mode(&self) -> bool;
|
||||
}
|
||||
|
||||
pub trait WindowBuilderExtWebSys {
|
||||
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
|
||||
|
||||
/// Whether `event.preventDefault` should be automatically called to prevent event propagation
|
||||
/// when appropriate.
|
||||
///
|
||||
/// For example, mouse wheel events are only handled by the canvas by default. This avoids
|
||||
/// the default behavior of scrolling the page.
|
||||
fn with_prevent_default(self, prevent_default: bool) -> Self;
|
||||
|
||||
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
|
||||
/// canvas keyboard events.
|
||||
fn with_focusable(self, focusable: bool) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtWebSys for WindowBuilder {
|
||||
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
|
||||
self.platform_specific.canvas = canvas;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn with_prevent_default(mut self, prevent_default: bool) -> Self {
|
||||
self.platform_specific.prevent_default = prevent_default;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn with_focusable(mut self, focusable: bool) -> Self {
|
||||
self.platform_specific.focusable = focusable;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to the web.
|
||||
pub trait EventLoopExtWebSys {
|
||||
/// A type provided by the user that can be passed through `Event::UserEvent`.
|
||||
type UserEvent;
|
||||
|
||||
/// Initializes the winit event loop.
|
||||
///
|
||||
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
|
||||
/// satisfy its `!` return type.
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
type UserEvent = T;
|
||||
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
),
|
||||
{
|
||||
self.event_loop.spawn(event_handler)
|
||||
}
|
||||
}
|
||||
@@ -1,76 +1,268 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use libc;
|
||||
use winapi::shared::windef::HWND;
|
||||
use std::{ffi::c_void, path::Path};
|
||||
|
||||
use crate::{
|
||||
dpi::PhysicalSize,
|
||||
event::DeviceId,
|
||||
event_loop::EventLoop,
|
||||
event_loop::EventLoopBuilder,
|
||||
monitor::MonitorHandle,
|
||||
platform_impl::EventLoop as WindowsEventLoop,
|
||||
window::{Icon, Window, WindowBuilder},
|
||||
platform_impl::{Parent, WinIcon},
|
||||
window::{BadIcon, Icon, Theme, Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Window Handle type used by Win32 API
|
||||
pub type HWND = isize;
|
||||
/// Menu Handle type used by Win32 API
|
||||
pub type HMENU = isize;
|
||||
/// Monitor Handle type used by Win32 API
|
||||
pub type HMONITOR = isize;
|
||||
/// Instance Handle type used by Win32 API
|
||||
pub type HINSTANCE = isize;
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Windows.
|
||||
pub trait EventLoopExtWindows {
|
||||
/// 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
|
||||
pub trait EventLoopBuilderExtWindows {
|
||||
/// Whether to allow the event loop to be created off of the main thread.
|
||||
///
|
||||
/// By default, the window is only allowed to be created on the main
|
||||
/// thread, to make platform compatibility easier.
|
||||
///
|
||||
/// # `Window` caveats
|
||||
///
|
||||
/// Note that any `Window` created on the new thread will be destroyed when the thread
|
||||
/// terminates. Attempting to use a `Window` after its parent thread terminates has
|
||||
/// unspecified, although explicitly not undefined, behavior.
|
||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
|
||||
|
||||
/// Whether to enable process-wide DPI awareness.
|
||||
///
|
||||
/// By default, `winit` will attempt to enable process-wide DPI awareness. If
|
||||
/// that's undesirable, you can disable it with this function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Disable process-wide DPI awareness.
|
||||
///
|
||||
/// ```
|
||||
/// use winit::event_loop::EventLoopBuilder;
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// use winit::platform::windows::EventLoopBuilderExtWindows;
|
||||
///
|
||||
/// let mut builder = EventLoopBuilder::new();
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// builder.with_dpi_aware(false);
|
||||
/// # if false { // We can't test this part
|
||||
/// let event_loop = builder.build();
|
||||
/// # }
|
||||
/// ```
|
||||
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self;
|
||||
|
||||
/// A callback to be executed before dispatching a win32 message to the window procedure.
|
||||
/// Return true to disable winit's internal message dispatching.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use windows_sys::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG};
|
||||
/// use winit::event_loop::EventLoopBuilder;
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// use winit::platform::windows::EventLoopBuilderExtWindows;
|
||||
///
|
||||
/// let mut builder = EventLoopBuilder::new();
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// builder.with_msg_hook(|msg|{
|
||||
/// let msg = msg as *const MSG;
|
||||
/// # let accels: Vec<ACCEL> = Vec::new();
|
||||
/// let translated = unsafe {
|
||||
/// TranslateAcceleratorW(
|
||||
/// (*msg).hwnd,
|
||||
/// CreateAcceleratorTableW(accels.as_ptr() as _, 1),
|
||||
/// msg,
|
||||
/// ) == 1
|
||||
/// };
|
||||
/// translated
|
||||
/// });
|
||||
/// ```
|
||||
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
|
||||
where
|
||||
Self: Sized;
|
||||
F: FnMut(*const c_void) -> bool + 'static;
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtWindows for EventLoop<T> {
|
||||
impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
|
||||
#[inline]
|
||||
fn new_dpi_unaware() -> Self {
|
||||
EventLoop {
|
||||
event_loop: WindowsEventLoop::with_dpi_awareness(false),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
|
||||
self.platform_specific.any_thread = any_thread;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self {
|
||||
self.platform_specific.dpi_aware = dpi_aware;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
|
||||
where
|
||||
F: FnMut(*const c_void) -> bool + 'static,
|
||||
{
|
||||
self.platform_specific.msg_hook = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Windows.
|
||||
pub trait WindowExtWindows {
|
||||
/// Returns the HINSTANCE of the window
|
||||
fn hinstance(&self) -> HINSTANCE;
|
||||
/// 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;
|
||||
fn hwnd(&self) -> HWND;
|
||||
|
||||
/// Enables or disables mouse and keyboard input to the specified window.
|
||||
///
|
||||
/// A window must be enabled before it can be activated.
|
||||
/// If an application has create a modal dialog box by disabling its owner window
|
||||
/// (as described in [`WindowBuilderExtWindows::with_owner_window`]), the application must enable
|
||||
/// the owner window before destroying the dialog box.
|
||||
/// Otherwise, another window will receive the keyboard focus and be activated.
|
||||
///
|
||||
/// If a child window is disabled, it is ignored when the system tries to determine which
|
||||
/// window should receive mouse messages.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks>
|
||||
/// and <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#disabled-windows>
|
||||
fn set_enable(&self, enabled: bool);
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
|
||||
|
||||
/// Returns the current window theme.
|
||||
fn theme(&self) -> Theme;
|
||||
|
||||
/// Whether to show or hide the window icon in the taskbar.
|
||||
fn set_skip_taskbar(&self, skip: bool);
|
||||
|
||||
/// Shows or hides the background drop shadow for undecorated windows.
|
||||
///
|
||||
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
|
||||
fn set_undecorated_shadow(&self, shadow: bool);
|
||||
}
|
||||
|
||||
impl WindowExtWindows for Window {
|
||||
#[inline]
|
||||
fn hwnd(&self) -> *mut libc::c_void {
|
||||
self.window.hwnd() as *mut _
|
||||
fn hinstance(&self) -> HINSTANCE {
|
||||
self.window.hinstance()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hwnd(&self) -> HWND {
|
||||
self.window.hwnd()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_enable(&self, enabled: bool) {
|
||||
self.window.set_enable(enabled)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
|
||||
self.window.set_taskbar_icon(taskbar_icon)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn theme(&self) -> Theme {
|
||||
self.window.theme()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_skip_taskbar(&self, skip: bool) {
|
||||
self.window.set_skip_taskbar(skip)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_undecorated_shadow(&self, shadow: bool) {
|
||||
self.window.set_undecorated_shadow(shadow)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Windows.
|
||||
pub trait WindowBuilderExtWindows {
|
||||
/// Sets a parent to the window to be created.
|
||||
///
|
||||
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
|
||||
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
|
||||
|
||||
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
|
||||
/// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable)
|
||||
/// on the owner window to create a modal dialog box.
|
||||
///
|
||||
/// From MSDN:
|
||||
/// - An owned window is always above its owner in the z-order.
|
||||
/// - The system automatically destroys an owned window when its owner is destroyed.
|
||||
/// - An owned window is hidden when its owner is minimized.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
|
||||
fn with_owner_window(self, parent: HWND) -> WindowBuilder;
|
||||
|
||||
/// Sets a menu on the window to be created.
|
||||
///
|
||||
/// Parent and menu are mutually exclusive; a child window cannot have a menu!
|
||||
///
|
||||
/// The menu must have been manually created beforehand with [`CreateMenu`] or similar.
|
||||
///
|
||||
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
|
||||
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
|
||||
///
|
||||
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu
|
||||
fn with_menu(self, menu: HMENU) -> 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;
|
||||
|
||||
/// Enables or disables drag and drop support (enabled by default). Will interfere with other crates
|
||||
/// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of
|
||||
/// `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still attempt to initialize
|
||||
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future.
|
||||
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions.
|
||||
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
|
||||
fn with_drag_and_drop(self, flag: bool) -> WindowBuilder;
|
||||
|
||||
/// Forces a theme or uses the system settings if `None` was provided.
|
||||
fn with_theme(self, theme: Option<Theme>) -> WindowBuilder;
|
||||
|
||||
/// Whether show or hide the window icon in the taskbar.
|
||||
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
|
||||
|
||||
/// Shows or hides the background drop shadow for undecorated windows.
|
||||
///
|
||||
/// The shadow is hidden by default.
|
||||
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
|
||||
fn with_undecorated_shadow(self, shadow: bool) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtWindows for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
|
||||
self.platform_specific.parent = Some(parent);
|
||||
self.platform_specific.parent = Parent::ChildOf(parent);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_owner_window(mut self, parent: HWND) -> WindowBuilder {
|
||||
self.platform_specific.parent = Parent::OwnedBy(parent);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_menu(mut self, menu: HMENU) -> WindowBuilder {
|
||||
self.platform_specific.menu = Some(menu);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -85,6 +277,30 @@ impl WindowBuilderExtWindows for WindowBuilder {
|
||||
self.platform_specific.no_redirection_bitmap = flag;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_drag_and_drop(mut self, flag: bool) -> WindowBuilder {
|
||||
self.platform_specific.drag_and_drop = flag;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_theme(mut self, theme: Option<Theme>) -> WindowBuilder {
|
||||
self.platform_specific.preferred_theme = theme;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
|
||||
self.platform_specific.skip_taskbar = skip;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder {
|
||||
self.platform_specific.decoration_shadow = shadow;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorHandle` that are specific to Windows.
|
||||
@@ -93,7 +309,7 @@ pub trait MonitorHandleExtWindows {
|
||||
fn native_id(&self) -> String;
|
||||
|
||||
/// Returns the handle of the monitor - `HMONITOR`.
|
||||
fn hmonitor(&self) -> *mut c_void;
|
||||
fn hmonitor(&self) -> HMONITOR;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtWindows for MonitorHandle {
|
||||
@@ -103,8 +319,8 @@ impl MonitorHandleExtWindows for MonitorHandle {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hmonitor(&self) -> *mut c_void {
|
||||
self.inner.hmonitor() as *mut _
|
||||
fn hmonitor(&self) -> HMONITOR {
|
||||
self.inner.hmonitor()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,3 +338,40 @@ impl DeviceIdExtWindows for DeviceId {
|
||||
self.0.persistent_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Icon` that are specific to Windows.
|
||||
pub trait IconExtWindows: Sized {
|
||||
/// Create an icon from a file path.
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
|
||||
-> Result<Self, BadIcon>;
|
||||
|
||||
/// Create an icon from a resource embedded in this executable or library.
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
|
||||
}
|
||||
|
||||
impl IconExtWindows for Icon {
|
||||
fn from_path<P: AsRef<Path>>(
|
||||
path: P,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
let win_icon = WinIcon::from_path(path, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
|
||||
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
|
||||
let win_icon = WinIcon::from_resource(ordinal, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,122 +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 "C" {}
|
||||
|
||||
/**
|
||||
** asset_manager.h
|
||||
**/
|
||||
pub type AAssetManager = raw::c_void;
|
||||
|
||||
/**
|
||||
** native_window.h
|
||||
**/
|
||||
pub type ANativeWindow = raw::c_void;
|
||||
|
||||
extern "C" {
|
||||
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 "C" fn(*mut ANativeActivity),
|
||||
pub onResume: extern "C" fn(*mut ANativeActivity),
|
||||
pub onSaveInstanceState: extern "C" fn(*mut ANativeActivity, *mut libc::size_t),
|
||||
pub onPause: extern "C" fn(*mut ANativeActivity),
|
||||
pub onStop: extern "C" fn(*mut ANativeActivity),
|
||||
pub onDestroy: extern "C" fn(*mut ANativeActivity),
|
||||
pub onWindowFocusChanged: extern "C" fn(*mut ANativeActivity, libc::c_int),
|
||||
pub onNativeWindowCreated: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowResized: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowRedrawNeeded: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowDestroyed: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onInputQueueCreated: extern "C" fn(*mut ANativeActivity, *mut AInputQueue),
|
||||
pub onInputQueueDestroyed: extern "C" fn(*mut ANativeActivity, *mut AInputQueue),
|
||||
pub onContentRectChanged: extern "C" fn(*mut ANativeActivity, *const ARect),
|
||||
pub onConfigurationChanged: extern "C" fn(*mut ANativeActivity),
|
||||
pub onLowMemory: extern "C" fn(*mut ANativeActivity),
|
||||
}
|
||||
|
||||
/**
|
||||
** looper.h
|
||||
**/
|
||||
pub type ALooper = ();
|
||||
|
||||
#[link(name = "android")]
|
||||
extern "C" {
|
||||
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 "C" fn(libc::c_int, libc::c_int, *mut libc::c_void) -> libc::c_int;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,363 +0,0 @@
|
||||
#![allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||
|
||||
#[cfg(test)]
|
||||
use std::mem;
|
||||
use std::os::raw::{c_char, c_double, c_int, c_long, c_ulong, c_ushort, c_void};
|
||||
|
||||
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
File diff suppressed because it is too large
Load Diff
@@ -7,37 +7,68 @@ use std::{
|
||||
sync::mpsc::{self, Receiver, Sender},
|
||||
};
|
||||
|
||||
use objc::runtime::Object;
|
||||
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::Event,
|
||||
event_loop::{
|
||||
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
|
||||
},
|
||||
monitor::MonitorHandle as RootMonitorHandle,
|
||||
platform::ios::Idiom,
|
||||
};
|
||||
|
||||
use crate::platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
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, NSOperatingSystemVersion, NSString,
|
||||
UIApplicationMain, UIUserInterfaceIdiom,
|
||||
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSStringRust, 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>,
|
||||
capabilities: Capabilities,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
pub fn capabilities(&self) -> &Capabilities {
|
||||
&self.capabilities
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
// guaranteed to be on main thread
|
||||
unsafe { monitor::uiscreens() }
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
||||
// guaranteed to be on main thread
|
||||
let monitor = unsafe { monitor::main_uiscreen() };
|
||||
|
||||
Some(RootMonitorHandle { inner: monitor })
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +76,11 @@ pub struct EventLoop<T: 'static> {
|
||||
window_target: RootEventLoopWindowTarget<T>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PlatformSpecificEventLoopAttributes {}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> EventLoop<T> {
|
||||
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
|
||||
static mut SINGLETON_INIT: bool = false;
|
||||
unsafe {
|
||||
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
|
||||
@@ -64,18 +98,11 @@ impl<T: 'static> EventLoop<T> {
|
||||
// this line sets up the main run loop before `UIApplicationMain`
|
||||
setup_control_flow_observers();
|
||||
|
||||
let version: NSOperatingSystemVersion = unsafe {
|
||||
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
|
||||
msg_send![process_info, operatingSystemVersion]
|
||||
};
|
||||
let capabilities = version.into();
|
||||
|
||||
EventLoop {
|
||||
window_target: RootEventLoopWindowTarget {
|
||||
p: EventLoopWindowTarget {
|
||||
receiver,
|
||||
sender_to_clone,
|
||||
capabilities,
|
||||
},
|
||||
_marker: PhantomData,
|
||||
},
|
||||
@@ -84,10 +111,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
pub fn run<F>(self, event_handler: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
unsafe {
|
||||
let application: *mut c_void = msg_send![class!(UIApplication), sharedApplication];
|
||||
let application: *mut Object = msg_send![class!(UIApplication), sharedApplication];
|
||||
assert_eq!(
|
||||
application,
|
||||
ptr::null_mut(),
|
||||
@@ -95,7 +122,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
|
||||
Note: `EventLoop::run` calls `UIApplicationMain` on iOS"
|
||||
);
|
||||
AppState::will_launch(Box::new(EventLoopHandler {
|
||||
app_state::will_launch(Box::new(EventLoopHandler {
|
||||
f: event_handler,
|
||||
event_loop: self.window_target,
|
||||
}));
|
||||
@@ -104,7 +131,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
0,
|
||||
ptr::null(),
|
||||
nil,
|
||||
NSString::alloc(nil).init_str("AppDelegate"),
|
||||
NSStringRust::alloc(nil).init_str("AppDelegate"),
|
||||
);
|
||||
unreachable!()
|
||||
}
|
||||
@@ -114,16 +141,6 @@ impl<T: 'static> EventLoop<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
|
||||
}
|
||||
@@ -142,8 +159,7 @@ pub struct EventLoopProxy<T> {
|
||||
source: CFRunLoopSourceRef,
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for EventLoopProxy<T> {}
|
||||
unsafe impl<T> Sync for EventLoopProxy<T> {}
|
||||
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
|
||||
|
||||
impl<T> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> EventLoopProxy<T> {
|
||||
@@ -163,7 +179,7 @@ impl<T> Drop for EventLoopProxy<T> {
|
||||
impl<T> EventLoopProxy<T> {
|
||||
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
|
||||
unsafe {
|
||||
// just wakeup the eventloop
|
||||
// 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
|
||||
@@ -171,7 +187,7 @@ impl<T> EventLoopProxy<T> {
|
||||
let rl = CFRunLoopGetMain();
|
||||
// we want all the members of context to be zero/null, except one
|
||||
let mut context: CFRunLoopSourceContext = mem::zeroed();
|
||||
context.perform = event_loop_proxy_handler;
|
||||
context.perform = Some(event_loop_proxy_handler);
|
||||
let source =
|
||||
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
@@ -181,8 +197,10 @@ impl<T> EventLoopProxy<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
|
||||
self.sender.send(event).map_err(|_| EventLoopClosed)?;
|
||||
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);
|
||||
@@ -204,15 +222,40 @@ fn setup_control_flow_observers() {
|
||||
unsafe {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopAfterWaiting => AppState::handle_wakeup_transition(),
|
||||
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
|
||||
// without that, LoopDestroyed will get sent after EventsCleared
|
||||
extern "C" fn control_flow_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
activity: CFRunLoopActivity,
|
||||
@@ -221,7 +264,7 @@ fn setup_control_flow_observers() {
|
||||
unsafe {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => AppState::handle_events_cleared(),
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
|
||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -229,6 +272,7 @@ fn setup_control_flow_observers() {
|
||||
}
|
||||
|
||||
let main_loop = CFRunLoopGetMain();
|
||||
|
||||
let begin_observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
|
||||
@@ -238,6 +282,17 @@ fn setup_control_flow_observers() {
|
||||
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,
|
||||
@@ -254,7 +309,7 @@ fn setup_control_flow_observers() {
|
||||
pub enum Never {}
|
||||
|
||||
pub trait EventHandler: Debug {
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
|
||||
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow);
|
||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
|
||||
}
|
||||
|
||||
@@ -273,10 +328,10 @@ impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
|
||||
|
||||
impl<F, T> EventHandler for EventLoopHandler<F, T>
|
||||
where
|
||||
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
T: 'static,
|
||||
{
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
|
||||
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
|
||||
(self.f)(
|
||||
event.map_nonuser_event().unwrap(),
|
||||
&self.event_loop,
|
||||
@@ -297,20 +352,3 @@ pub unsafe fn get_idiom() -> Idiom {
|
||||
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
|
||||
raw_idiom.into()
|
||||
}
|
||||
|
||||
pub struct Capabilities {
|
||||
pub supports_safe_area: bool,
|
||||
}
|
||||
|
||||
impl From<NSOperatingSystemVersion> for Capabilities {
|
||||
fn from(os_version: NSOperatingSystemVersion) -> Capabilities {
|
||||
assert!(
|
||||
os_version.major >= 8,
|
||||
"`winit` current requires iOS version 8 or greater"
|
||||
);
|
||||
|
||||
let supports_safe_area = os_version.major >= 11;
|
||||
|
||||
Capabilities { supports_safe_area }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||
|
||||
use std::{ffi::CString, ops::BitOr, os::raw::*};
|
||||
use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
|
||||
|
||||
use objc::{runtime::Object, Encode, Encoding};
|
||||
|
||||
use crate::platform::ios::{Idiom, ValidOrientations};
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
platform::ios::{Idiom, ScreenEdge, ValidOrientations},
|
||||
};
|
||||
|
||||
pub type id = *mut Object;
|
||||
pub const nil: id = 0 as id;
|
||||
@@ -26,26 +29,41 @@ pub struct NSOperatingSystemVersion {
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct CGPoint {
|
||||
pub x: CGFloat,
|
||||
pub y: CGFloat,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
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)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
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 {
|
||||
@@ -70,6 +88,24 @@ pub enum UITouchPhase {
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UIForceTouchCapability {
|
||||
Unknown = 0,
|
||||
Unavailable,
|
||||
Available,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UITouchType {
|
||||
Direct = 0,
|
||||
Indirect,
|
||||
Pencil,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UIEdgeInsets {
|
||||
@@ -108,10 +144,9 @@ impl From<Idiom> for UIUserInterfaceIdiom {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Idiom> for UIUserInterfaceIdiom {
|
||||
fn into(self) -> Idiom {
|
||||
match self {
|
||||
impl From<UIUserInterfaceIdiom> for Idiom {
|
||||
fn from(ui_idiom: UIUserInterfaceIdiom) -> Idiom {
|
||||
match ui_idiom {
|
||||
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
|
||||
UIUserInterfaceIdiom::Phone => Idiom::Phone,
|
||||
UIUserInterfaceIdiom::Pad => Idiom::Pad,
|
||||
@@ -173,6 +208,51 @@ impl UIInterfaceOrientationMask {
|
||||
}
|
||||
}
|
||||
|
||||
#[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 From<UIRectEdge> for ScreenEdge {
|
||||
fn from(ui_rect_edge: UIRectEdge) -> ScreenEdge {
|
||||
let bits: u8 = ui_rect_edge.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" {
|
||||
@@ -268,17 +348,20 @@ pub enum CFRunLoopTimerContext {}
|
||||
pub struct CFRunLoopSourceContext {
|
||||
pub version: CFIndex,
|
||||
pub info: *mut c_void,
|
||||
pub retain: extern "C" fn(*const c_void) -> *const c_void,
|
||||
pub release: extern "C" fn(*const c_void),
|
||||
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
|
||||
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean,
|
||||
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
|
||||
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
||||
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
||||
pub perform: extern "C" fn(*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 {
|
||||
// This is named NSStringRust rather than NSString because the "Debug View Heirarchy" feature of
|
||||
// Xcode requires a non-ambiguous reference to NSString for unclear reasons. This makes Xcode happy
|
||||
// so please test if you change the name back to NSString.
|
||||
pub trait NSStringRust: Sized {
|
||||
unsafe fn alloc(_: Self) -> id {
|
||||
msg_send![class!(NSString), alloc]
|
||||
}
|
||||
@@ -289,9 +372,9 @@ pub trait NSString: Sized {
|
||||
unsafe fn UTF8String(self) -> *const c_char;
|
||||
}
|
||||
|
||||
impl NSString for id {
|
||||
impl NSStringRust for id {
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
|
||||
msg_send![self, initWithUTF8String: c_string as id]
|
||||
msg_send![self, initWithUTF8String: c_string]
|
||||
}
|
||||
|
||||
unsafe fn stringByAppendingString_(self, other: id) -> id {
|
||||
|
||||
@@ -56,13 +56,15 @@
|
||||
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
|
||||
|
||||
#![cfg(target_os = "ios")]
|
||||
#![allow(clippy::let_unit_value)]
|
||||
|
||||
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
|
||||
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
|
||||
// window size/position.
|
||||
macro_rules! assert_main_thread {
|
||||
($($t:tt)*) => {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
let is_main_thread: ::objc::runtime::BOOL = msg_send!(class!(NSThread), isMainThread);
|
||||
if is_main_thread == ::objc::runtime::NO {
|
||||
panic!($($t)*);
|
||||
}
|
||||
};
|
||||
@@ -77,19 +79,23 @@ mod window;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
pub use self::{
|
||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
pub(crate) use self::{
|
||||
event_loop::{
|
||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
|
||||
},
|
||||
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 {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
DeviceId {
|
||||
uiscreen: std::ptr::null_mut(),
|
||||
}
|
||||
@@ -103,9 +109,7 @@ unsafe impl Sync for DeviceId {}
|
||||
pub enum OsError {}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "os error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,97 @@
|
||||
use std::{
|
||||
collections::{HashSet, VecDeque},
|
||||
collections::{BTreeSet, VecDeque},
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
monitor::VideoMode,
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
platform_impl::platform::{
|
||||
app_state,
|
||||
ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::platform_impl::platform::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_millihertz: u32,
|
||||
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_millihertz: self.refresh_rate_millihertz,
|
||||
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 refresh_rate_millihertz = refresh_rate_millihertz(uiscreen);
|
||||
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_millihertz,
|
||||
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_millihertz(&self) -> u32 {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> RootMonitorHandle {
|
||||
RootMonitorHandle {
|
||||
inner: self.monitor.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Inner {
|
||||
uiscreen: id,
|
||||
}
|
||||
@@ -20,11 +99,12 @@ pub struct Inner {
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let () = msg_send![self.uiscreen, release];
|
||||
let _: () = msg_send![self.uiscreen, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct MonitorHandle {
|
||||
inner: Inner,
|
||||
}
|
||||
@@ -72,19 +152,21 @@ impl Drop for MonitorHandle {
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// TODO: Do this using the proper fmt API
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct MonitorHandle {
|
||||
name: Option<String>,
|
||||
size: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: f64,
|
||||
size: PhysicalSize<u32>,
|
||||
position: PhysicalPosition<i32>,
|
||||
scale_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorHandle {
|
||||
name: self.name(),
|
||||
size: self.size(),
|
||||
position: self.position(),
|
||||
hidpi_factor: self.hidpi_factor(),
|
||||
scale_factor: self.scale_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
@@ -95,7 +177,7 @@ 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];
|
||||
let _: id = msg_send![uiscreen, retain];
|
||||
}
|
||||
MonitorHandle {
|
||||
inner: Inner { uiscreen },
|
||||
@@ -106,9 +188,10 @@ impl MonitorHandle {
|
||||
impl Inner {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
unsafe {
|
||||
if self.uiscreen == main_uiscreen().uiscreen {
|
||||
let main = main_uiscreen();
|
||||
if self.uiscreen == main.uiscreen {
|
||||
Some("Primary".to_string())
|
||||
} else if self.uiscreen == mirrored_uiscreen().uiscreen {
|
||||
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
|
||||
Some("Mirrored".to_string())
|
||||
} else {
|
||||
uiscreens()
|
||||
@@ -119,54 +202,87 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize {
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
unsafe {
|
||||
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
|
||||
(bounds.size.width as f64, bounds.size.height as f64).into()
|
||||
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> PhysicalPosition {
|
||||
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 hidpi_factor(&self) -> f64 {
|
||||
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 = VideoMode> {
|
||||
let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] };
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
Some(refresh_rate_millihertz(self.uiscreen))
|
||||
}
|
||||
|
||||
let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] };
|
||||
let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] };
|
||||
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];
|
||||
|
||||
let mut modes = HashSet::with_capacity(available_mode_count);
|
||||
|
||||
for i in 0..available_mode_count {
|
||||
let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] };
|
||||
let size: CGSize = unsafe { msg_send![mode, size] };
|
||||
modes.insert(VideoMode {
|
||||
size: (size.width as u32, size.height as u32),
|
||||
bit_depth: 32,
|
||||
refresh_rate: refresh_rate as u16,
|
||||
});
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_rate_millihertz(uiscreen: id) -> u32 {
|
||||
let refresh_rate_millihertz: NSInteger = unsafe {
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
refresh_rate_millihertz as u32 * 1000
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -176,8 +292,8 @@ pub unsafe fn main_uiscreen() -> MonitorHandle {
|
||||
}
|
||||
|
||||
// requires being run on main thread
|
||||
unsafe fn mirrored_uiscreen() -> MonitorHandle {
|
||||
let uiscreen: id = msg_send![class!(UIScreen), mirroredScreen];
|
||||
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
|
||||
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
|
||||
MonitorHandle::retained_new(uiscreen)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,18 +6,79 @@ use objc::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent},
|
||||
dpi::PhysicalPosition,
|
||||
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
|
||||
platform::ios::MonitorHandleExtIOS,
|
||||
window::{WindowAttributes, WindowId as RootWindowId},
|
||||
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},
|
||||
};
|
||||
|
||||
use crate::platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
event_loop,
|
||||
ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase},
|
||||
window::PlatformSpecificWindowBuilderAttributes,
|
||||
DeviceId,
|
||||
};
|
||||
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 {
|
||||
@@ -41,33 +102,181 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) {
|
||||
unsafe {
|
||||
let window: id = msg_send![object, window];
|
||||
AppState::handle_nonuser_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
});
|
||||
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];
|
||||
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];
|
||||
let bounds: CGRect = msg_send![window, bounds];
|
||||
assert!(!window.is_null());
|
||||
let window_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:window_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 as f64);
|
||||
|
||||
// If the app is started in landscape, the view frame and window bounds can be mismatched.
|
||||
// The view frame will be in portrait and the window bounds in landscape. So apply the
|
||||
// window bounds to the view frame to make it consistent.
|
||||
let view_frame: CGRect = msg_send![object, frame];
|
||||
if view_frame != window_bounds {
|
||||
let _: () = msg_send![object, setFrame: window_bounds];
|
||||
}
|
||||
|
||||
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 = scale_factor as f64;
|
||||
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,
|
||||
height: screen_frame.size.height,
|
||||
width: screen_frame.size.width as _,
|
||||
height: screen_frame.size.height as _,
|
||||
};
|
||||
AppState::handle_nonuser_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Resized(size),
|
||||
});
|
||||
let superclass: &'static Class = msg_send![object, superclass];
|
||||
let () = msg_send![super(object, superclass), layoutSubviews];
|
||||
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 as f64,
|
||||
)
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,184 +291,9 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
sel!(layoutSubviews),
|
||||
layout_subviews as extern "C" fn(&Object, Sel),
|
||||
);
|
||||
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 uiviewcontroller_class = class!(UIViewController);
|
||||
|
||||
extern "C" fn set_prefers_status_bar_hidden(object: &mut Object, _: Sel, hidden: BOOL) {
|
||||
unsafe {
|
||||
object.set_ivar::<BOOL>("_prefers_status_bar_hidden", hidden);
|
||||
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn prefers_status_bar_hidden(object: &Object, _: Sel) -> BOOL {
|
||||
unsafe { *object.get_ivar::<BOOL>("_prefers_status_bar_hidden") }
|
||||
}
|
||||
|
||||
extern "C" fn set_supported_orientations(
|
||||
object: &mut Object,
|
||||
_: Sel,
|
||||
orientations: UIInterfaceOrientationMask,
|
||||
) {
|
||||
unsafe {
|
||||
object.set_ivar::<UIInterfaceOrientationMask>(
|
||||
"_supported_orientations",
|
||||
orientations,
|
||||
);
|
||||
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn supported_orientations(
|
||||
object: &Object,
|
||||
_: Sel,
|
||||
) -> UIInterfaceOrientationMask {
|
||||
unsafe { *object.get_ivar::<UIInterfaceOrientationMask>("_supported_orientations") }
|
||||
}
|
||||
|
||||
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_ivar::<BOOL>("_prefers_status_bar_hidden");
|
||||
decl.add_ivar::<UIInterfaceOrientationMask>("_supported_orientations");
|
||||
decl.add_method(
|
||||
sel!(setPrefersStatusBarHidden:),
|
||||
set_prefers_status_bar_hidden as extern "C" fn(&mut Object, Sel, BOOL),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(prefersStatusBarHidden),
|
||||
prefers_status_bar_hidden as extern "C" fn(&Object, Sel) -> BOOL,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(setSupportedInterfaceOrientations:),
|
||||
set_supported_orientations
|
||||
as extern "C" fn(&mut Object, Sel, UIInterfaceOrientationMask),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(supportedInterfaceOrientations),
|
||||
supported_orientations as extern "C" fn(&Object, Sel) -> UIInterfaceOrientationMask,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(shouldAutorotate),
|
||||
should_autorotate as extern "C" fn(&Object, Sel) -> BOOL,
|
||||
);
|
||||
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 {
|
||||
AppState::handle_nonuser_event(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 {
|
||||
AppState::handle_nonuser_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(object.into()),
|
||||
event: WindowEvent::Focused(false),
|
||||
});
|
||||
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) {
|
||||
unsafe {
|
||||
let uiscreen = msg_send![object, screen];
|
||||
let touches_enum: id = msg_send![touches, objectEnumerator];
|
||||
let mut touch_events = Vec::new();
|
||||
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: 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),
|
||||
};
|
||||
|
||||
touch_events.push(Event::WindowEvent {
|
||||
window_id: RootWindowId(object.into()),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
device_id: RootDeviceId(DeviceId { uiscreen }),
|
||||
id: touch_id,
|
||||
location: (location.x as f64, location.y as f64).into(),
|
||||
phase,
|
||||
}),
|
||||
});
|
||||
}
|
||||
AppState::handle_nonuser_events(touch_events);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn set_content_scale_factor(object: &mut Object, _: Sel, hidpi_factor: CGFloat) {
|
||||
unsafe {
|
||||
let () = msg_send![
|
||||
super(object, class!(UIWindow)),
|
||||
setContentScaleFactor: hidpi_factor
|
||||
];
|
||||
let view_controller: id = msg_send![object, rootViewController];
|
||||
let view: id = msg_send![view_controller, view];
|
||||
let () = msg_send![view, setContentScaleFactor: hidpi_factor];
|
||||
let bounds: CGRect = msg_send![object, bounds];
|
||||
let screen: id = msg_send![object, 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,
|
||||
height: screen_frame.size.height,
|
||||
};
|
||||
AppState::handle_nonuser_events(
|
||||
std::iter::once(Event::WindowEvent {
|
||||
window_id: RootWindowId(object.into()),
|
||||
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
|
||||
})
|
||||
.chain(std::iter::once(Event::WindowEvent {
|
||||
window_id: RootWindowId(object.into()),
|
||||
event: WindowEvent::Resized(size),
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
sel!(setContentScaleFactor:),
|
||||
set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
@@ -279,9 +313,114 @@ unsafe fn get_window_class() -> &'static Class {
|
||||
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!(setContentScaleFactor:),
|
||||
set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat),
|
||||
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());
|
||||
@@ -290,7 +429,7 @@ unsafe fn get_window_class() -> &'static Class {
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn create_view(
|
||||
pub(crate) unsafe fn create_view(
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
@@ -301,14 +440,17 @@ pub unsafe fn create_view(
|
||||
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];
|
||||
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,
|
||||
pub(crate) unsafe fn create_view_controller(
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
view: id,
|
||||
) -> id {
|
||||
@@ -324,32 +466,48 @@ pub unsafe fn create_view_controller(
|
||||
!view_controller.is_null(),
|
||||
"Failed to initialize `UIViewController` instance"
|
||||
);
|
||||
let status_bar_hidden = if window_attributes.decorations {
|
||||
NO
|
||||
} else {
|
||||
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 () = msg_send![
|
||||
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![
|
||||
let _: () = msg_send![
|
||||
view_controller,
|
||||
setSupportedInterfaceOrientations: supported_orientations
|
||||
];
|
||||
let () = msg_send![view_controller, setView: view];
|
||||
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(
|
||||
pub(crate) unsafe fn create_window(
|
||||
window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
view_controller: id,
|
||||
) -> id {
|
||||
@@ -362,12 +520,25 @@ pub unsafe fn create_window(
|
||||
!window.is_null(),
|
||||
"Failed to initialize `UIWindow` instance"
|
||||
);
|
||||
let () = msg_send![window, setRootViewController: view_controller];
|
||||
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
|
||||
let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat];
|
||||
}
|
||||
if let &Some(ref monitor) = &window_attributes.fullscreen {
|
||||
let () = msg_send![window, setScreen:monitor.ui_screen()];
|
||||
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)) => {
|
||||
let uiscreen: id = match &monitor {
|
||||
Some(monitor) => monitor.ui_screen() as id,
|
||||
None => {
|
||||
let uiscreen: id = msg_send![window, screen];
|
||||
uiscreen
|
||||
}
|
||||
};
|
||||
|
||||
msg_send![window, setScreen: uiscreen]
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
window
|
||||
@@ -376,17 +547,17 @@ pub unsafe fn create_window(
|
||||
pub fn create_delegate_class() {
|
||||
extern "C" fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL {
|
||||
unsafe {
|
||||
AppState::did_finish_launching();
|
||||
app_state::did_finish_launching();
|
||||
}
|
||||
YES
|
||||
}
|
||||
|
||||
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
|
||||
unsafe { AppState::handle_nonuser_event(Event::Resumed) }
|
||||
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
|
||||
}
|
||||
|
||||
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
|
||||
unsafe { AppState::handle_nonuser_event(Event::Suspended) }
|
||||
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
|
||||
}
|
||||
|
||||
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
|
||||
@@ -405,14 +576,14 @@ pub fn create_delegate_class() {
|
||||
}
|
||||
let is_winit_window: BOOL = msg_send![window, isKindOfClass: class!(WinitUIWindow)];
|
||||
if is_winit_window == YES {
|
||||
events.push(Event::WindowEvent {
|
||||
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Destroyed,
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
AppState::handle_nonuser_events(events);
|
||||
AppState::terminated();
|
||||
app_state::handle_nonuser_events(events);
|
||||
app_state::terminated();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,36 +3,44 @@ use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use objc::runtime::{Class, Object, NO, YES};
|
||||
use objc::runtime::{Class, Object, BOOL, NO, YES};
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
|
||||
|
||||
use crate::{
|
||||
dpi::{self, LogicalPosition, LogicalSize},
|
||||
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, ValidOrientations},
|
||||
platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
|
||||
platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
event_loop,
|
||||
ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask},
|
||||
app_state,
|
||||
event_loop::{self, EventProxy, EventWrapper},
|
||||
ffi::{
|
||||
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
|
||||
UIRectEdge, UIScreenOverscanCompensation,
|
||||
},
|
||||
monitor, view, EventLoopWindowTarget, MonitorHandle,
|
||||
},
|
||||
window::{CursorIcon, WindowAttributes},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes,
|
||||
WindowId as RootWindowId,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Inner {
|
||||
pub window: id,
|
||||
pub view_controller: id,
|
||||
pub view: id,
|
||||
supports_safe_area: bool,
|
||||
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];
|
||||
let _: () = msg_send![self.view, release];
|
||||
let _: () = msg_send![self.view_controller, release];
|
||||
let _: () = msg_send![self.window, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,42 +53,65 @@ impl Inner {
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
match visible {
|
||||
true => unsafe {
|
||||
let () = msg_send![self.window, setHidden: NO];
|
||||
let _: () = msg_send![self.window, setHidden: NO];
|
||||
},
|
||||
false => unsafe {
|
||||
let () = msg_send![self.window, setHidden: YES];
|
||||
let _: () = msg_send![self.window, setHidden: YES];
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
warn!("`Window::is_visible` is ignored on iOS");
|
||||
None
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
unsafe {
|
||||
let () = msg_send![self.view, setNeedsDisplay];
|
||||
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<LogicalPosition, NotSupportedError> {
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
unsafe {
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
Ok(LogicalPosition {
|
||||
x: safe_area.origin.x,
|
||||
y: safe_area.origin.y,
|
||||
})
|
||||
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<LogicalPosition, NotSupportedError> {
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
unsafe {
|
||||
let screen_frame = self.screen_frame();
|
||||
Ok(LogicalPosition {
|
||||
x: screen_frame.origin.x,
|
||||
y: screen_frame.origin.y,
|
||||
})
|
||||
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, position: LogicalPosition) {
|
||||
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 {
|
||||
@@ -89,40 +120,44 @@ impl Inner {
|
||||
},
|
||||
size: screen_frame.size,
|
||||
};
|
||||
let bounds = self.from_screen_space(new_screen_frame);
|
||||
let () = msg_send![self.window, setBounds: bounds];
|
||||
let bounds = self.rect_from_screen_space(new_screen_frame);
|
||||
let _: () = msg_send![self.window, setBounds: bounds];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> LogicalSize {
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
unsafe {
|
||||
let scale_factor = self.scale_factor();
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
LogicalSize {
|
||||
width: safe_area.size.width,
|
||||
height: safe_area.size.height,
|
||||
}
|
||||
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) -> LogicalSize {
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
unsafe {
|
||||
let scale_factor = self.scale_factor();
|
||||
let screen_frame = self.screen_frame();
|
||||
LogicalSize {
|
||||
width: screen_frame.size.width,
|
||||
height: screen_frame.size.height,
|
||||
}
|
||||
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: LogicalSize) {
|
||||
unimplemented!("not clear what `Window::set_inner_size` means on iOS");
|
||||
pub fn set_inner_size(&self, _size: Size) {
|
||||
warn!("not clear what `Window::set_inner_size` means on iOS");
|
||||
}
|
||||
|
||||
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
|
||||
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<LogicalSize>) {
|
||||
pub fn set_max_inner_size(&self, _dimensions: Option<Size>) {
|
||||
warn!("`Window::set_max_inner_size` is ignored on iOS")
|
||||
}
|
||||
|
||||
@@ -130,7 +165,12 @@ impl Inner {
|
||||
warn!("`Window::set_resizable` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn hidpi_factor(&self) -> f64 {
|
||||
pub fn is_resizable(&self) -> bool {
|
||||
warn!("`Window::is_resizable` is ignored on iOS");
|
||||
false
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
unsafe {
|
||||
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
|
||||
hidpi as _
|
||||
@@ -141,11 +181,11 @@ impl Inner {
|
||||
debug!("`Window::set_cursor_icon` ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
|
||||
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> {
|
||||
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
@@ -153,32 +193,67 @@ impl Inner {
|
||||
debug!("`Window::set_cursor_visible` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn set_minimized(&self, _minimized: bool) {
|
||||
warn!("`Window::set_minimized` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_maximized(&self, _maximized: bool) {
|
||||
warn!("`Window::set_maximized` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
||||
unsafe {
|
||||
match monitor {
|
||||
Some(monitor) => {
|
||||
let uiscreen = monitor.ui_screen() as id;
|
||||
let current: id = msg_send![self.window, screen];
|
||||
let bounds: CGRect = msg_send![uiscreen, bounds];
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
warn!("`Window::is_maximized` is ignored on iOS");
|
||||
false
|
||||
}
|
||||
|
||||
// this is pretty slow on iOS, so avoid doing it if we can
|
||||
if uiscreen != current {
|
||||
let () = msg_send![self.window, setScreen: uiscreen];
|
||||
}
|
||||
let () = msg_send![self.window, setFrame: bounds];
|
||||
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.0];
|
||||
uiscreen
|
||||
}
|
||||
None => warn!("`Window::set_fullscreen(None)` ignored on iOS"),
|
||||
Some(Fullscreen::Borderless(monitor)) => monitor
|
||||
.unwrap_or_else(|| self.current_monitor_inner())
|
||||
.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<RootMonitorHandle> {
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
unsafe {
|
||||
let monitor = self.current_monitor();
|
||||
let monitor = self.current_monitor_inner();
|
||||
let uiscreen = monitor.inner.ui_screen();
|
||||
let screen_space_bounds = self.screen_frame();
|
||||
let screen_bounds: CGRect = msg_send![uiscreen, bounds];
|
||||
@@ -189,21 +264,20 @@ impl Inner {
|
||||
&& screen_space_bounds.size.width == screen_bounds.size.width
|
||||
&& screen_space_bounds.size.height == screen_bounds.size.height
|
||||
{
|
||||
Some(monitor)
|
||||
Some(Fullscreen::Borderless(Some(monitor)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
unsafe {
|
||||
let status_bar_hidden = if decorations { NO } else { YES };
|
||||
let () = msg_send![
|
||||
self.view_controller,
|
||||
setPrefersStatusBarHidden: status_bar_hidden
|
||||
];
|
||||
}
|
||||
pub fn set_decorations(&self, _decorations: bool) {
|
||||
warn!("`Window::set_decorations` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
warn!("`Window::is_decorated` is ignored on iOS");
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||
@@ -214,11 +288,24 @@ impl Inner {
|
||||
warn!("`Window::set_window_icon` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_ime_position(&self, _position: LogicalPosition) {
|
||||
pub fn set_ime_position(&self, _position: Position) {
|
||||
warn!("`Window::set_ime_position` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn current_monitor(&self) -> RootMonitorHandle {
|
||||
pub fn set_ime_allowed(&self, _allowed: bool) {
|
||||
warn!("`Window::set_ime_allowed` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn focus_window(&self) {
|
||||
warn!("`Window::set_focus` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn request_user_attention(&self, _request_type: Option<UserAttentionType>) {
|
||||
warn!("`Window::request_user_attention` is ignored on iOS")
|
||||
}
|
||||
|
||||
// Allow directly accessing the current monitor internally without unwrapping.
|
||||
fn current_monitor_inner(&self) -> RootMonitorHandle {
|
||||
unsafe {
|
||||
let uiscreen: id = msg_send![self.window, screen];
|
||||
RootMonitorHandle {
|
||||
@@ -227,17 +314,34 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
|
||||
Some(self.current_monitor_inner())
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
unsafe { monitor::uiscreens() }
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
unsafe { monitor::main_uiscreen() }
|
||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
||||
let monitor = unsafe { monitor::main_uiscreen() };
|
||||
Some(RootMonitorHandle { inner: monitor })
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WindowId {
|
||||
self.window.into()
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let mut window_handle = UiKitWindowHandle::empty();
|
||||
window_handle.ui_window = self.window as _;
|
||||
window_handle.ui_view = self.view as _;
|
||||
window_handle.ui_view_controller = self.view_controller as _;
|
||||
RawWindowHandle::UiKit(window_handle)
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
@@ -276,15 +380,15 @@ impl DerefMut for Window {
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new<T>(
|
||||
event_loop: &EventLoopWindowTarget<T>,
|
||||
pub(crate) fn new<T>(
|
||||
_event_loop: &EventLoopWindowTarget<T>,
|
||||
window_attributes: WindowAttributes,
|
||||
platform_attributes: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, RootOsError> {
|
||||
if let Some(_) = window_attributes.min_inner_size {
|
||||
if window_attributes.min_inner_size.is_some() {
|
||||
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
|
||||
}
|
||||
if let Some(_) = window_attributes.max_inner_size {
|
||||
if window_attributes.max_inner_size.is_some() {
|
||||
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
|
||||
}
|
||||
if window_attributes.always_on_top {
|
||||
@@ -293,25 +397,44 @@ impl Window {
|
||||
// TODO: transparency, visible
|
||||
|
||||
unsafe {
|
||||
let screen = window_attributes
|
||||
.fullscreen
|
||||
.as_ref()
|
||||
.map(|screen| screen.ui_screen() as _)
|
||||
.unwrap_or_else(|| monitor::main_uiscreen().ui_screen());
|
||||
let screen = match window_attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
video_mode.video_mode.monitor.ui_screen() as id
|
||||
}
|
||||
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.inner.ui_screen(),
|
||||
Some(Fullscreen::Borderless(None)) | None => {
|
||||
monitor::main_uiscreen().ui_screen() as id
|
||||
}
|
||||
};
|
||||
|
||||
let screen_bounds: CGRect = msg_send![screen, bounds];
|
||||
|
||||
let frame = match window_attributes.inner_size {
|
||||
Some(dim) => CGRect {
|
||||
origin: screen_bounds.origin,
|
||||
size: CGSize {
|
||||
width: dim.width,
|
||||
height: dim.height,
|
||||
},
|
||||
},
|
||||
Some(dim) => {
|
||||
let scale_factor: CGFloat = msg_send![screen, scale];
|
||||
let size = dim.to_logical::<f64>(scale_factor as f64);
|
||||
CGRect {
|
||||
origin: screen_bounds.origin,
|
||||
size: CGSize {
|
||||
width: size.width as _,
|
||||
height: size.height as _,
|
||||
},
|
||||
}
|
||||
}
|
||||
None => screen_bounds,
|
||||
};
|
||||
|
||||
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
|
||||
let view = view::create_view(&window_attributes, &platform_attributes, frame);
|
||||
|
||||
let gl_or_metal_backed = {
|
||||
let view_class: *const Class = msg_send![view, class];
|
||||
let layer_class: *const Class = 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(
|
||||
@@ -321,17 +444,45 @@ impl Window {
|
||||
view_controller,
|
||||
);
|
||||
|
||||
let supports_safe_area = event_loop.capabilities().supports_safe_area;
|
||||
|
||||
let result = Window {
|
||||
inner: Inner {
|
||||
window,
|
||||
view_controller,
|
||||
view,
|
||||
supports_safe_area,
|
||||
gl_or_metal_backed,
|
||||
},
|
||||
};
|
||||
AppState::set_key_window(window);
|
||||
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 = scale_factor as f64;
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -349,14 +500,14 @@ impl Inner {
|
||||
self.view
|
||||
}
|
||||
|
||||
pub fn set_hidpi_factor(&self, hidpi_factor: f64) {
|
||||
pub fn set_scale_factor(&self, scale_factor: f64) {
|
||||
unsafe {
|
||||
assert!(
|
||||
dpi::validate_hidpi_factor(hidpi_factor),
|
||||
"`WindowExtIOS::set_hidpi_factor` received an invalid hidpi factor"
|
||||
dpi::validate_scale_factor(scale_factor),
|
||||
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
|
||||
);
|
||||
let hidpi_factor = hidpi_factor as CGFloat;
|
||||
let () = msg_send![self.view, setContentScaleFactor: hidpi_factor];
|
||||
let scale_factor = scale_factor as CGFloat;
|
||||
let _: () = msg_send![self.view, setContentScaleFactor: scale_factor];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,6 +521,36 @@ impl Inner {
|
||||
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
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -378,11 +559,11 @@ impl Inner {
|
||||
impl Inner {
|
||||
// requires main thread
|
||||
unsafe fn screen_frame(&self) -> CGRect {
|
||||
self.to_screen_space(msg_send![self.window, bounds])
|
||||
self.rect_to_screen_space(msg_send![self.window, bounds])
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
unsafe fn rect_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];
|
||||
@@ -393,7 +574,7 @@ impl Inner {
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
unsafe fn rect_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];
|
||||
@@ -406,7 +587,7 @@ impl Inner {
|
||||
// requires main thread
|
||||
unsafe fn safe_area_screen_space(&self) -> CGRect {
|
||||
let bounds: CGRect = msg_send![self.window, bounds];
|
||||
if self.supports_safe_area {
|
||||
if app_state::os_capabilities().safe_area {
|
||||
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
|
||||
let safe_bounds = CGRect {
|
||||
origin: CGPoint {
|
||||
@@ -418,9 +599,9 @@ impl Inner {
|
||||
height: bounds.size.height - safe_area.top - safe_area.bottom,
|
||||
},
|
||||
};
|
||||
self.to_screen_space(safe_bounds)
|
||||
self.rect_to_screen_space(safe_bounds)
|
||||
} else {
|
||||
let screen_frame = self.to_screen_space(bounds);
|
||||
let screen_frame = self.rect_to_screen_space(bounds);
|
||||
let status_bar_frame: CGRect = {
|
||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
||||
assert!(
|
||||
@@ -457,13 +638,27 @@ pub struct WindowId {
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
WindowId {
|
||||
window: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WindowId> for u64 {
|
||||
fn from(window_id: WindowId) -> Self {
|
||||
window_id.window as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for WindowId {
|
||||
fn from(raw_id: u64) -> Self {
|
||||
Self {
|
||||
window: raw_id as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for WindowId {}
|
||||
unsafe impl Sync for WindowId {}
|
||||
|
||||
@@ -492,16 +687,22 @@ impl From<id> for WindowId {
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub root_view_class: &'static Class,
|
||||
pub hidpi_factor: Option<f64>,
|
||||
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),
|
||||
hidpi_factor: None,
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
|
||||
pub const RTLD_LAZY: c_int = 0x001;
|
||||
pub const RTLD_NOW: c_int = 0x002;
|
||||
|
||||
#[link(name = "dl")]
|
||||
extern "C" {
|
||||
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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
166
src/platform_impl/linux/wayland/env.rs
Normal file
166
src/platform_impl/linux/wayland/env.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
//! SCTK environment setup.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6::ZxdgShellV6;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
|
||||
use sctk::reexports::client::protocol::wl_shell::WlShell;
|
||||
use sctk::reexports::client::protocol::wl_subcompositor::WlSubcompositor;
|
||||
use sctk::reexports::client::{Attached, DispatchData};
|
||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
||||
use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
||||
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
|
||||
|
||||
use sctk::environment::{Environment, SimpleGlobal};
|
||||
use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener};
|
||||
use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener};
|
||||
use sctk::shell::{Shell, ShellHandler, ShellHandling};
|
||||
use sctk::shm::ShmHandler;
|
||||
|
||||
/// Set of extra features that are supported by the compositor.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct WindowingFeatures {
|
||||
pointer_constraints: bool,
|
||||
xdg_activation: bool,
|
||||
}
|
||||
|
||||
impl WindowingFeatures {
|
||||
/// Create `WindowingFeatures` based on the presented interfaces.
|
||||
pub fn new(env: &Environment<WinitEnv>) -> Self {
|
||||
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>().is_some();
|
||||
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
|
||||
Self {
|
||||
pointer_constraints,
|
||||
xdg_activation,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pointer_constraints(&self) -> bool {
|
||||
self.pointer_constraints
|
||||
}
|
||||
|
||||
pub fn xdg_activation(&self) -> bool {
|
||||
self.xdg_activation
|
||||
}
|
||||
}
|
||||
|
||||
sctk::environment!(WinitEnv,
|
||||
singles = [
|
||||
WlShm => shm,
|
||||
WlCompositor => compositor,
|
||||
WlSubcompositor => subcompositor,
|
||||
WlShell => shell,
|
||||
XdgWmBase => shell,
|
||||
ZxdgShellV6 => shell,
|
||||
ZxdgDecorationManagerV1 => decoration_manager,
|
||||
ZwpRelativePointerManagerV1 => relative_pointer_manager,
|
||||
ZwpPointerConstraintsV1 => pointer_constraints,
|
||||
ZwpTextInputManagerV3 => text_input_manager,
|
||||
XdgActivationV1 => xdg_activation,
|
||||
],
|
||||
multis = [
|
||||
WlSeat => seats,
|
||||
WlOutput => outputs,
|
||||
]
|
||||
);
|
||||
|
||||
/// The environment that we utilize.
|
||||
pub struct WinitEnv {
|
||||
seats: SeatHandler,
|
||||
|
||||
outputs: OutputHandler,
|
||||
|
||||
shm: ShmHandler,
|
||||
|
||||
compositor: SimpleGlobal<WlCompositor>,
|
||||
|
||||
subcompositor: SimpleGlobal<WlSubcompositor>,
|
||||
|
||||
shell: ShellHandler,
|
||||
|
||||
relative_pointer_manager: SimpleGlobal<ZwpRelativePointerManagerV1>,
|
||||
|
||||
pointer_constraints: SimpleGlobal<ZwpPointerConstraintsV1>,
|
||||
|
||||
text_input_manager: SimpleGlobal<ZwpTextInputManagerV3>,
|
||||
|
||||
decoration_manager: SimpleGlobal<ZxdgDecorationManagerV1>,
|
||||
|
||||
xdg_activation: SimpleGlobal<XdgActivationV1>,
|
||||
}
|
||||
|
||||
impl WinitEnv {
|
||||
pub fn new() -> Self {
|
||||
// Output tracking for available_monitors, etc.
|
||||
let outputs = OutputHandler::new();
|
||||
|
||||
// Keyboard/Pointer/Touch input.
|
||||
let seats = SeatHandler::new();
|
||||
|
||||
// Essential globals.
|
||||
let shm = ShmHandler::new();
|
||||
let compositor = SimpleGlobal::new();
|
||||
let subcompositor = SimpleGlobal::new();
|
||||
|
||||
// Gracefully handle shell picking, since SCTK automatically supports multiple
|
||||
// backends.
|
||||
let shell = ShellHandler::new();
|
||||
|
||||
// Server side decorations.
|
||||
let decoration_manager = SimpleGlobal::new();
|
||||
|
||||
// Device events for pointer.
|
||||
let relative_pointer_manager = SimpleGlobal::new();
|
||||
|
||||
// Pointer grab functionality.
|
||||
let pointer_constraints = SimpleGlobal::new();
|
||||
|
||||
// IME handling.
|
||||
let text_input_manager = SimpleGlobal::new();
|
||||
|
||||
// Surface activation.
|
||||
let xdg_activation = SimpleGlobal::new();
|
||||
|
||||
Self {
|
||||
seats,
|
||||
outputs,
|
||||
shm,
|
||||
compositor,
|
||||
subcompositor,
|
||||
shell,
|
||||
decoration_manager,
|
||||
relative_pointer_manager,
|
||||
pointer_constraints,
|
||||
text_input_manager,
|
||||
xdg_activation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellHandling for WinitEnv {
|
||||
fn get_shell(&self) -> Option<Shell> {
|
||||
self.shell.get_shell()
|
||||
}
|
||||
}
|
||||
|
||||
impl SeatHandling for WinitEnv {
|
||||
fn listen<F: FnMut(Attached<WlSeat>, &SeatData, DispatchData<'_>) + 'static>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> SeatListener {
|
||||
self.seats.listen(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputHandling for WinitEnv {
|
||||
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData<'_>) + 'static>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> OutputStatusListener {
|
||||
self.outputs.listen(f)
|
||||
}
|
||||
}
|
||||
@@ -1,718 +0,0 @@
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::VecDeque,
|
||||
fmt,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
zwp_relative_pointer_v1::ZwpRelativePointerV1,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
event::ModifiersState,
|
||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
monitor::VideoMode,
|
||||
platform_impl::platform::sticky_exit_callback,
|
||||
};
|
||||
|
||||
use super::{window::WindowStore, DeviceId, WindowId};
|
||||
|
||||
use smithay_client_toolkit::{
|
||||
output::OutputMgr,
|
||||
reexports::client::{
|
||||
protocol::{wl_keyboard, wl_output, wl_pointer, wl_registry, wl_seat, wl_touch},
|
||||
ConnectError, Display, EventQueue, GlobalEvent,
|
||||
},
|
||||
Environment,
|
||||
};
|
||||
|
||||
pub struct WindowEventsSink<T> {
|
||||
buffer: VecDeque<crate::event::Event<T>>,
|
||||
}
|
||||
|
||||
impl<T> WindowEventsSink<T> {
|
||||
pub fn new() -> WindowEventsSink<T> {
|
||||
WindowEventsSink {
|
||||
buffer: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_window_event(&mut self, evt: crate::event::WindowEvent, wid: WindowId) {
|
||||
self.buffer.push_back(crate::event::Event::WindowEvent {
|
||||
event: evt,
|
||||
window_id: crate::window::WindowId(crate::platform_impl::WindowId::Wayland(wid)),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_device_event(&mut self, evt: crate::event::DeviceEvent, dev_id: DeviceId) {
|
||||
self.buffer.push_back(crate::event::Event::DeviceEvent {
|
||||
event: evt,
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(dev_id)),
|
||||
});
|
||||
}
|
||||
|
||||
fn empty_with<F>(&mut self, mut callback: F)
|
||||
where
|
||||
F: FnMut(crate::event::Event<T>),
|
||||
{
|
||||
for evt in self.buffer.drain(..) {
|
||||
callback(evt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
// The loop
|
||||
inner_loop: ::calloop::EventLoop<()>,
|
||||
// The wayland display
|
||||
pub display: Arc<Display>,
|
||||
// the output manager
|
||||
pub outputs: OutputMgr,
|
||||
// our sink, shared with some handlers, buffering the events
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
pending_user_events: Rc<RefCell<VecDeque<T>>>,
|
||||
_user_source: ::calloop::Source<::calloop::channel::Channel<T>>,
|
||||
user_sender: ::calloop::channel::Sender<T>,
|
||||
_kbd_source: ::calloop::Source<
|
||||
::calloop::channel::Channel<(crate::event::WindowEvent, super::WindowId)>,
|
||||
>,
|
||||
window_target: RootELW<T>,
|
||||
}
|
||||
|
||||
// A handle that can be sent across threads and used to wake up the `EventLoop`.
|
||||
//
|
||||
// We should only try and wake up the `EventLoop` if it still exists, so we hold Weak ptrs.
|
||||
#[derive(Clone)]
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
user_sender: ::calloop::channel::Sender<T>,
|
||||
}
|
||||
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
// the event queue
|
||||
pub evq: RefCell<::calloop::Source<EventQueue>>,
|
||||
// The window store
|
||||
pub store: Arc<Mutex<WindowStore>>,
|
||||
// the env
|
||||
pub env: Environment,
|
||||
// a cleanup switch to prune dead windows
|
||||
pub cleanup_needed: Arc<Mutex<bool>>,
|
||||
// The wayland display
|
||||
pub display: Arc<Display>,
|
||||
// The list of seats
|
||||
pub seats: Arc<Mutex<Vec<(u32, wl_seat::WlSeat)>>>,
|
||||
_marker: ::std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopProxy<T> {
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
|
||||
self.user_sender.send(event).map_err(|_| EventLoopClosed)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> Result<EventLoop<T>, ConnectError> {
|
||||
let (display, mut event_queue) = Display::connect_to_env()?;
|
||||
|
||||
let display = Arc::new(display);
|
||||
let sink = Arc::new(Mutex::new(WindowEventsSink::new()));
|
||||
let store = Arc::new(Mutex::new(WindowStore::new()));
|
||||
let seats = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
let inner_loop = ::calloop::EventLoop::new().unwrap();
|
||||
|
||||
let (kbd_sender, kbd_channel) = ::calloop::channel::channel();
|
||||
let kbd_sink = sink.clone();
|
||||
let kbd_source = inner_loop
|
||||
.handle()
|
||||
.insert_source(kbd_channel, move |evt, &mut ()| {
|
||||
if let ::calloop::channel::Event::Msg((evt, wid)) = evt {
|
||||
kbd_sink.lock().unwrap().send_window_event(evt, wid);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut seat_manager = SeatManager {
|
||||
sink: sink.clone(),
|
||||
relative_pointer_manager_proxy: None,
|
||||
store: store.clone(),
|
||||
seats: seats.clone(),
|
||||
kbd_sender,
|
||||
};
|
||||
|
||||
let env = Environment::from_display_with_cb(
|
||||
&display,
|
||||
&mut event_queue,
|
||||
move |event, registry| match event {
|
||||
GlobalEvent::New {
|
||||
id,
|
||||
ref interface,
|
||||
version,
|
||||
} => {
|
||||
if interface == "zwp_relative_pointer_manager_v1" {
|
||||
seat_manager.relative_pointer_manager_proxy = Some(
|
||||
registry
|
||||
.bind(version, id, move |pointer_manager| {
|
||||
pointer_manager.implement_closure(|_, _| (), ())
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
if interface == "wl_seat" {
|
||||
seat_manager.add_seat(id, version, registry)
|
||||
}
|
||||
}
|
||||
GlobalEvent::Removed { id, ref interface } => {
|
||||
if interface == "wl_seat" {
|
||||
seat_manager.remove_seat(id)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let source = inner_loop
|
||||
.handle()
|
||||
.insert_source(event_queue, |(), &mut ()| {})
|
||||
.unwrap();
|
||||
|
||||
let pending_user_events = Rc::new(RefCell::new(VecDeque::new()));
|
||||
let pending_user_events2 = pending_user_events.clone();
|
||||
|
||||
let (user_sender, user_channel) = ::calloop::channel::channel();
|
||||
|
||||
let user_source = inner_loop
|
||||
.handle()
|
||||
.insert_source(user_channel, move |evt, &mut ()| {
|
||||
if let ::calloop::channel::Event::Msg(msg) = evt {
|
||||
pending_user_events2.borrow_mut().push_back(msg);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Ok(EventLoop {
|
||||
inner_loop,
|
||||
sink,
|
||||
pending_user_events,
|
||||
display: display.clone(),
|
||||
outputs: env.outputs.clone(),
|
||||
_user_source: user_source,
|
||||
user_sender,
|
||||
_kbd_source: kbd_source,
|
||||
window_target: RootELW {
|
||||
p: crate::platform_impl::EventLoopWindowTarget::Wayland(EventLoopWindowTarget {
|
||||
evq: RefCell::new(source),
|
||||
store,
|
||||
env,
|
||||
cleanup_needed: Arc::new(Mutex::new(false)),
|
||||
seats,
|
||||
display,
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy {
|
||||
user_sender: self.user_sender.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run<F>(mut self, callback: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
self.run_return(callback);
|
||||
::std::process::exit(0);
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, mut callback: F)
|
||||
where
|
||||
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
// send pending events to the server
|
||||
self.display.flush().expect("Wayland connection lost.");
|
||||
|
||||
let mut control_flow = ControlFlow::default();
|
||||
|
||||
let sink = self.sink.clone();
|
||||
let user_events = self.pending_user_events.clone();
|
||||
|
||||
callback(
|
||||
crate::event::Event::NewEvents(crate::event::StartCause::Init),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
|
||||
loop {
|
||||
self.post_dispatch_triggers();
|
||||
|
||||
// empty buffer of events
|
||||
{
|
||||
let mut guard = sink.lock().unwrap();
|
||||
guard.empty_with(|evt| {
|
||||
sticky_exit_callback(
|
||||
evt,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
});
|
||||
}
|
||||
// empty user events
|
||||
{
|
||||
let mut guard = user_events.borrow_mut();
|
||||
for evt in guard.drain(..) {
|
||||
sticky_exit_callback(
|
||||
crate::event::Event::UserEvent(evt),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
// do a second run of post-dispatch-triggers, to handle user-generated "request-redraw"
|
||||
// in response of resize & friends
|
||||
self.post_dispatch_triggers();
|
||||
{
|
||||
let mut guard = sink.lock().unwrap();
|
||||
guard.empty_with(|evt| {
|
||||
sticky_exit_callback(
|
||||
evt,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
});
|
||||
}
|
||||
// send Events cleared
|
||||
{
|
||||
sticky_exit_callback(
|
||||
crate::event::Event::EventsCleared,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
|
||||
// send pending events to the server
|
||||
self.display.flush().expect("Wayland connection lost.");
|
||||
|
||||
match control_flow {
|
||||
ControlFlow::Exit => break,
|
||||
ControlFlow::Poll => {
|
||||
// non-blocking dispatch
|
||||
self.inner_loop
|
||||
.dispatch(Some(::std::time::Duration::from_millis(0)), &mut ())
|
||||
.unwrap();
|
||||
callback(
|
||||
crate::event::Event::NewEvents(crate::event::StartCause::Poll),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
self.inner_loop.dispatch(None, &mut ()).unwrap();
|
||||
callback(
|
||||
crate::event::Event::NewEvents(crate::event::StartCause::WaitCancelled {
|
||||
start: Instant::now(),
|
||||
requested_resume: None,
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
ControlFlow::WaitUntil(deadline) => {
|
||||
let start = Instant::now();
|
||||
// compute the blocking duration
|
||||
let duration = if deadline > start {
|
||||
deadline - start
|
||||
} else {
|
||||
::std::time::Duration::from_millis(0)
|
||||
};
|
||||
self.inner_loop.dispatch(Some(duration), &mut ()).unwrap();
|
||||
let now = Instant::now();
|
||||
if now < deadline {
|
||||
callback(
|
||||
crate::event::Event::NewEvents(
|
||||
crate::event::StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(deadline),
|
||||
},
|
||||
),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
} else {
|
||||
callback(
|
||||
crate::event::Event::NewEvents(
|
||||
crate::event::StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume: deadline,
|
||||
},
|
||||
),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback(
|
||||
crate::event::Event::LoopDestroyed,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
primary_monitor(&self.outputs)
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
available_monitors(&self.outputs)
|
||||
}
|
||||
|
||||
pub fn display(&self) -> &Display {
|
||||
&*self.display
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &RootELW<T> {
|
||||
&self.window_target
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Private EventLoop Internals
|
||||
*/
|
||||
|
||||
impl<T> EventLoop<T> {
|
||||
fn post_dispatch_triggers(&mut self) {
|
||||
let mut sink = self.sink.lock().unwrap();
|
||||
let window_target = match self.window_target.p {
|
||||
crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// prune possible dead windows
|
||||
{
|
||||
let mut cleanup_needed = window_target.cleanup_needed.lock().unwrap();
|
||||
if *cleanup_needed {
|
||||
let pruned = window_target.store.lock().unwrap().cleanup();
|
||||
*cleanup_needed = false;
|
||||
for wid in pruned {
|
||||
sink.send_window_event(crate::event::WindowEvent::Destroyed, wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
// process pending resize/refresh
|
||||
window_target.store.lock().unwrap().for_each(
|
||||
|newsize, size, new_dpi, refresh, frame_refresh, closed, wid, frame| {
|
||||
if let Some(frame) = frame {
|
||||
if let Some((w, h)) = newsize {
|
||||
frame.resize(w, h);
|
||||
frame.refresh();
|
||||
let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64);
|
||||
sink.send_window_event(
|
||||
crate::event::WindowEvent::Resized(logical_size),
|
||||
wid,
|
||||
);
|
||||
*size = (w, h);
|
||||
} else if frame_refresh {
|
||||
frame.refresh();
|
||||
if !refresh {
|
||||
frame.surface().commit()
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(dpi) = new_dpi {
|
||||
sink.send_window_event(
|
||||
crate::event::WindowEvent::HiDpiFactorChanged(dpi as f64),
|
||||
wid,
|
||||
);
|
||||
}
|
||||
if refresh {
|
||||
sink.send_window_event(crate::event::WindowEvent::RedrawRequested, wid);
|
||||
}
|
||||
if closed {
|
||||
sink.send_window_event(crate::event::WindowEvent::CloseRequested, wid);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Wayland protocol implementations
|
||||
*/
|
||||
|
||||
struct SeatManager<T: 'static> {
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
seats: Arc<Mutex<Vec<(u32, wl_seat::WlSeat)>>>,
|
||||
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
|
||||
relative_pointer_manager_proxy: Option<ZwpRelativePointerManagerV1>,
|
||||
}
|
||||
|
||||
impl<T: 'static> SeatManager<T> {
|
||||
fn add_seat(&mut self, id: u32, version: u32, registry: wl_registry::WlRegistry) {
|
||||
use std::cmp::min;
|
||||
|
||||
let mut seat_data = SeatData {
|
||||
sink: self.sink.clone(),
|
||||
store: self.store.clone(),
|
||||
pointer: None,
|
||||
relative_pointer: None,
|
||||
relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.as_ref().cloned(),
|
||||
keyboard: None,
|
||||
touch: None,
|
||||
kbd_sender: self.kbd_sender.clone(),
|
||||
modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())),
|
||||
};
|
||||
let seat = registry
|
||||
.bind(min(version, 5), id, move |seat| {
|
||||
seat.implement_closure(move |event, seat| seat_data.receive(event, seat), ())
|
||||
})
|
||||
.unwrap();
|
||||
self.store.lock().unwrap().new_seat(&seat);
|
||||
self.seats.lock().unwrap().push((id, seat));
|
||||
}
|
||||
|
||||
fn remove_seat(&mut self, id: u32) {
|
||||
let mut seats = self.seats.lock().unwrap();
|
||||
if let Some(idx) = seats.iter().position(|&(i, _)| i == id) {
|
||||
let (_, seat) = seats.swap_remove(idx);
|
||||
if seat.as_ref().version() >= 5 {
|
||||
seat.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SeatData<T> {
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
|
||||
pointer: Option<wl_pointer::WlPointer>,
|
||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
||||
relative_pointer_manager_proxy: Option<ZwpRelativePointerManagerV1>,
|
||||
keyboard: Option<wl_keyboard::WlKeyboard>,
|
||||
touch: Option<wl_touch::WlTouch>,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
}
|
||||
|
||||
impl<T: 'static> SeatData<T> {
|
||||
fn receive(&mut self, evt: wl_seat::Event, seat: wl_seat::WlSeat) {
|
||||
match evt {
|
||||
wl_seat::Event::Name { .. } => (),
|
||||
wl_seat::Event::Capabilities { capabilities } => {
|
||||
// create pointer if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Pointer) && self.pointer.is_none() {
|
||||
self.pointer = Some(super::pointer::implement_pointer(
|
||||
&seat,
|
||||
self.sink.clone(),
|
||||
self.store.clone(),
|
||||
self.modifiers_tracker.clone(),
|
||||
));
|
||||
|
||||
self.relative_pointer =
|
||||
self.relative_pointer_manager_proxy
|
||||
.as_ref()
|
||||
.and_then(|manager| {
|
||||
super::pointer::implement_relative_pointer(
|
||||
self.sink.clone(),
|
||||
self.pointer.as_ref().unwrap(),
|
||||
manager,
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
// destroy pointer if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Pointer) {
|
||||
if let Some(pointer) = self.pointer.take() {
|
||||
if pointer.as_ref().version() >= 3 {
|
||||
pointer.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
// create keyboard if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Keyboard) && self.keyboard.is_none() {
|
||||
self.keyboard = Some(super::keyboard::init_keyboard(
|
||||
&seat,
|
||||
self.kbd_sender.clone(),
|
||||
self.modifiers_tracker.clone(),
|
||||
))
|
||||
}
|
||||
// destroy keyboard if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Keyboard) {
|
||||
if let Some(kbd) = self.keyboard.take() {
|
||||
if kbd.as_ref().version() >= 3 {
|
||||
kbd.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
// create touch if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Touch) && self.touch.is_none() {
|
||||
self.touch = Some(super::touch::implement_touch(
|
||||
&seat,
|
||||
self.sink.clone(),
|
||||
self.store.clone(),
|
||||
))
|
||||
}
|
||||
// destroy touch if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Touch) {
|
||||
if let Some(touch) = self.touch.take() {
|
||||
if touch.as_ref().version() >= 3 {
|
||||
touch.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for SeatData<T> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(pointer) = self.pointer.take() {
|
||||
if pointer.as_ref().version() >= 3 {
|
||||
pointer.release();
|
||||
}
|
||||
}
|
||||
if let Some(kbd) = self.keyboard.take() {
|
||||
if kbd.as_ref().version() >= 3 {
|
||||
kbd.release();
|
||||
}
|
||||
}
|
||||
if let Some(touch) = self.touch.take() {
|
||||
if touch.as_ref().version() >= 3 {
|
||||
touch.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Monitor stuff
|
||||
*/
|
||||
|
||||
pub struct MonitorHandle {
|
||||
pub(crate) proxy: wl_output::WlOutput,
|
||||
pub(crate) mgr: OutputMgr,
|
||||
}
|
||||
|
||||
impl Clone for MonitorHandle {
|
||||
fn clone(&self) -> MonitorHandle {
|
||||
MonitorHandle {
|
||||
proxy: self.proxy.clone(),
|
||||
mgr: self.mgr.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorHandle {
|
||||
name: Option<String>,
|
||||
native_identifier: u32,
|
||||
size: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: i32,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorHandle {
|
||||
name: self.name(),
|
||||
native_identifier: self.native_identifier(),
|
||||
size: self.size(),
|
||||
position: self.position(),
|
||||
hidpi_factor: self.hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
self.mgr.with_info(&self.proxy, |_, info| {
|
||||
format!("{} ({})", info.model, info.make)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_identifier(&self) -> u32 {
|
||||
self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize {
|
||||
match self.mgr.with_info(&self.proxy, |_, info| {
|
||||
info.modes
|
||||
.iter()
|
||||
.find(|m| m.is_current)
|
||||
.map(|m| m.dimensions)
|
||||
}) {
|
||||
Some(Some((w, h))) => (w as u32, h as u32),
|
||||
_ => (0, 0),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn position(&self) -> PhysicalPosition {
|
||||
self.mgr
|
||||
.with_info(&self.proxy, |_, info| info.location)
|
||||
.unwrap_or((0, 0))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> i32 {
|
||||
self.mgr
|
||||
.with_info(&self.proxy, |_, info| info.scale_factor)
|
||||
.unwrap_or(1)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.mgr
|
||||
.with_info(&self.proxy, |_, info| info.modes.clone())
|
||||
.unwrap_or(vec![])
|
||||
.into_iter()
|
||||
.map(|x| VideoMode {
|
||||
size: (x.dimensions.0 as u32, x.dimensions.1 as u32),
|
||||
refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16,
|
||||
bit_depth: 32,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary_monitor(outputs: &OutputMgr) -> MonitorHandle {
|
||||
outputs.with_all(|list| {
|
||||
if let Some(&(_, ref proxy, _)) = list.first() {
|
||||
MonitorHandle {
|
||||
proxy: proxy.clone(),
|
||||
mgr: outputs.clone(),
|
||||
}
|
||||
} else {
|
||||
panic!("No monitor is available.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn available_monitors(outputs: &OutputMgr) -> VecDeque<MonitorHandle> {
|
||||
outputs.with_all(|list| {
|
||||
list.iter()
|
||||
.map(|&(_, ref proxy, _)| MonitorHandle {
|
||||
proxy: proxy.clone(),
|
||||
mgr: outputs.clone(),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
582
src/platform_impl/linux/wayland/event_loop/mod.rs
Normal file
582
src/platform_impl/linux/wayland/event_loop/mod.rs
Normal file
@@ -0,0 +1,582 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::io::Result as IOResult;
|
||||
use std::mem;
|
||||
use std::process;
|
||||
use std::rc::Rc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
||||
use sctk::reexports::client::Display;
|
||||
|
||||
use sctk::reexports::calloop;
|
||||
|
||||
use sctk::environment::Environment;
|
||||
use sctk::seat::pointer::{ThemeManager, ThemeSpec};
|
||||
use sctk::WaylandSource;
|
||||
|
||||
use crate::event::{Event, StartCause, WindowEvent};
|
||||
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
|
||||
use crate::platform_impl::platform::sticky_exit_callback;
|
||||
use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
|
||||
|
||||
use super::env::{WindowingFeatures, WinitEnv};
|
||||
use super::output::OutputManager;
|
||||
use super::seat::SeatManager;
|
||||
use super::window::shim::{self, WindowCompositorUpdate, WindowUserRequest};
|
||||
use super::{DeviceId, WindowId};
|
||||
|
||||
mod proxy;
|
||||
mod sink;
|
||||
mod state;
|
||||
|
||||
pub use proxy::EventLoopProxy;
|
||||
pub use sink::EventSink;
|
||||
pub use state::WinitState;
|
||||
|
||||
type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>;
|
||||
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
/// Wayland display.
|
||||
pub display: Display,
|
||||
|
||||
/// Environment to handle object creation, etc.
|
||||
pub env: Environment<WinitEnv>,
|
||||
|
||||
/// Event loop handle.
|
||||
pub event_loop_handle: calloop::LoopHandle<'static, WinitState>,
|
||||
|
||||
/// Output manager.
|
||||
pub output_manager: OutputManager,
|
||||
|
||||
/// State that we share across callbacks.
|
||||
pub state: RefCell<WinitState>,
|
||||
|
||||
/// Dispatcher of Wayland events.
|
||||
pub wayland_dispatcher: WinitDispatcher,
|
||||
|
||||
/// A proxy to wake up event loop.
|
||||
pub event_loop_awakener: calloop::ping::Ping,
|
||||
|
||||
/// The available windowing features.
|
||||
pub windowing_features: WindowingFeatures,
|
||||
|
||||
/// Theme manager to manage cursors.
|
||||
///
|
||||
/// It's being shared between all windows to avoid loading
|
||||
/// multiple similar themes.
|
||||
pub theme_manager: ThemeManager,
|
||||
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
let mut display_handle = WaylandDisplayHandle::empty();
|
||||
display_handle.display = self.display.get_display_ptr() as *mut _;
|
||||
RawDisplayHandle::Wayland(display_handle)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
/// Dispatcher of Wayland events.
|
||||
pub wayland_dispatcher: WinitDispatcher,
|
||||
|
||||
/// Event loop.
|
||||
event_loop: calloop::EventLoop<'static, WinitState>,
|
||||
|
||||
/// Wayland display.
|
||||
display: Display,
|
||||
|
||||
/// Pending user events.
|
||||
pending_user_events: Rc<RefCell<Vec<T>>>,
|
||||
|
||||
/// Sender of user events.
|
||||
user_events_sender: calloop::channel::Sender<T>,
|
||||
|
||||
/// Window target.
|
||||
window_target: RootEventLoopWindowTarget<T>,
|
||||
|
||||
/// Output manager.
|
||||
_seat_manager: SeatManager,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> Result<EventLoop<T>, Box<dyn Error>> {
|
||||
// Connect to wayland server and setup event queue.
|
||||
let display = Display::connect_to_env()?;
|
||||
let mut event_queue = display.create_event_queue();
|
||||
let display_proxy = display.attach(event_queue.token());
|
||||
|
||||
// Setup environment.
|
||||
let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?;
|
||||
|
||||
// Create event loop.
|
||||
let event_loop = calloop::EventLoop::<'static, WinitState>::try_new()?;
|
||||
// Build windowing features.
|
||||
let windowing_features = WindowingFeatures::new(&env);
|
||||
|
||||
// Create a theme manager.
|
||||
let compositor = env.require_global::<WlCompositor>();
|
||||
let shm = env.require_global::<WlShm>();
|
||||
let theme_manager = ThemeManager::init(ThemeSpec::System, compositor, shm);
|
||||
|
||||
// Setup theme seat and output managers.
|
||||
let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone());
|
||||
let output_manager = OutputManager::new(&env);
|
||||
|
||||
// A source of events that we plug into our event loop.
|
||||
let wayland_source = WaylandSource::new(event_queue);
|
||||
let wayland_dispatcher =
|
||||
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
|
||||
queue.dispatch_pending(winit_state, |event, object, _| {
|
||||
panic!(
|
||||
"[calloop] Encountered an orphan event: {}@{} : {}",
|
||||
event.interface,
|
||||
object.as_ref().id(),
|
||||
event.name
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
let _wayland_source_dispatcher = event_loop
|
||||
.handle()
|
||||
.register_dispatcher(wayland_dispatcher.clone())?;
|
||||
|
||||
// A source of user events.
|
||||
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
|
||||
let pending_user_events_clone = pending_user_events.clone();
|
||||
let (user_events_sender, user_events_channel) = calloop::channel::channel();
|
||||
|
||||
// User events channel.
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(user_events_channel, move |event, _, _| {
|
||||
if let calloop::channel::Event::Msg(msg) = event {
|
||||
pending_user_events_clone.borrow_mut().push(msg);
|
||||
}
|
||||
})?;
|
||||
|
||||
// An event's loop awakener to wake up for window events from winit's windows.
|
||||
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
|
||||
|
||||
// Handler of window requests.
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(event_loop_awakener_source, move |_, _, state| {
|
||||
// Drain events here as well to account for application doing batch event processing
|
||||
// on RedrawEventsCleared.
|
||||
shim::handle_window_requests(state);
|
||||
})?;
|
||||
|
||||
let event_loop_handle = event_loop.handle();
|
||||
let window_map = HashMap::new();
|
||||
let event_sink = EventSink::new();
|
||||
let window_user_requests = HashMap::new();
|
||||
let window_compositor_updates = HashMap::new();
|
||||
|
||||
// Create event loop window target.
|
||||
let event_loop_window_target = EventLoopWindowTarget {
|
||||
display: display.clone(),
|
||||
env,
|
||||
state: RefCell::new(WinitState {
|
||||
window_map,
|
||||
event_sink,
|
||||
window_user_requests,
|
||||
window_compositor_updates,
|
||||
}),
|
||||
event_loop_handle,
|
||||
output_manager,
|
||||
event_loop_awakener,
|
||||
wayland_dispatcher: wayland_dispatcher.clone(),
|
||||
windowing_features,
|
||||
theme_manager,
|
||||
_marker: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
// Create event loop itself.
|
||||
let event_loop = Self {
|
||||
event_loop,
|
||||
display,
|
||||
pending_user_events,
|
||||
wayland_dispatcher,
|
||||
_seat_manager: seat_manager,
|
||||
user_events_sender,
|
||||
window_target: RootEventLoopWindowTarget {
|
||||
p: PlatformEventLoopWindowTarget::Wayland(event_loop_window_target),
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(event_loop)
|
||||
}
|
||||
|
||||
pub fn run<F>(mut self, callback: F) -> !
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
|
||||
{
|
||||
let exit_code = self.run_return(callback);
|
||||
process::exit(exit_code);
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, mut callback: F) -> i32
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
let mut control_flow = ControlFlow::Poll;
|
||||
let pending_user_events = self.pending_user_events.clone();
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::Init),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
|
||||
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
|
||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
||||
callback(Event::Resumed, &self.window_target, &mut control_flow);
|
||||
|
||||
let mut window_compositor_updates: Vec<(WindowId, WindowCompositorUpdate)> = Vec::new();
|
||||
let mut window_user_requests: Vec<(WindowId, WindowUserRequest)> = Vec::new();
|
||||
let mut event_sink_back_buffer = Vec::new();
|
||||
|
||||
// NOTE We break on errors from dispatches, since if we've got protocol error
|
||||
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
|
||||
// really an option. Instead we inform that the event loop got destroyed. We may
|
||||
// communicate an error that something was terminated, but winit doesn't provide us
|
||||
// with an API to do that via some event.
|
||||
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
|
||||
let exit_code = loop {
|
||||
// Send pending events to the server.
|
||||
let _ = self.display.flush();
|
||||
|
||||
// During the run of the user callback, some other code monitoring and reading the
|
||||
// Wayland socket may have been run (mesa for example does this with vsync), if that
|
||||
// is the case, some events may have been enqueued in our event queue.
|
||||
//
|
||||
// If some messages are there, the event loop needs to behave as if it was instantly
|
||||
// woken up by messages arriving from the Wayland socket, to avoid delaying the
|
||||
// dispatch of these events until we're woken up again.
|
||||
let instant_wakeup = {
|
||||
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
|
||||
let queue = wayland_source.queue();
|
||||
let state = match &mut self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => {
|
||||
window_target.state.get_mut()
|
||||
}
|
||||
#[cfg(feature = "x11")]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
|
||||
Ok(dispatched) => dispatched > 0,
|
||||
Err(error) => break error.raw_os_error().unwrap_or(1),
|
||||
}
|
||||
};
|
||||
|
||||
match control_flow {
|
||||
ControlFlow::ExitWithCode(code) => break code,
|
||||
ControlFlow::Poll => {
|
||||
// Non-blocking dispatch.
|
||||
let timeout = Duration::from_millis(0);
|
||||
if let Err(error) = self.loop_dispatch(Some(timeout)) {
|
||||
break error.raw_os_error().unwrap_or(1);
|
||||
}
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::Poll),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
let timeout = if instant_wakeup {
|
||||
Some(Duration::from_millis(0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Err(error) = self.loop_dispatch(timeout) {
|
||||
break error.raw_os_error().unwrap_or(1);
|
||||
}
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::WaitCancelled {
|
||||
start: Instant::now(),
|
||||
requested_resume: None,
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
ControlFlow::WaitUntil(deadline) => {
|
||||
let start = Instant::now();
|
||||
|
||||
// Compute the amount of time we'll block for.
|
||||
let duration = if deadline > start && !instant_wakeup {
|
||||
deadline - start
|
||||
} else {
|
||||
Duration::from_millis(0)
|
||||
};
|
||||
|
||||
if let Err(error) = self.loop_dispatch(Some(duration)) {
|
||||
break error.raw_os_error().unwrap_or(1);
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
if now < deadline {
|
||||
callback(
|
||||
Event::NewEvents(StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(deadline),
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
)
|
||||
} else {
|
||||
callback(
|
||||
Event::NewEvents(StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume: deadline,
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle pending user events. We don't need back buffer, since we can't dispatch
|
||||
// user events indirectly via callback to the user.
|
||||
for user_event in pending_user_events.borrow_mut().drain(..) {
|
||||
sticky_exit_callback(
|
||||
Event::UserEvent(user_event),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
|
||||
// Process 'new' pending updates from compositor.
|
||||
self.with_state(|state| {
|
||||
window_compositor_updates.clear();
|
||||
window_compositor_updates.extend(
|
||||
state
|
||||
.window_compositor_updates
|
||||
.iter_mut()
|
||||
.map(|(wid, window_update)| (*wid, mem::take(window_update))),
|
||||
);
|
||||
});
|
||||
|
||||
for (window_id, window_compositor_update) in window_compositor_updates.iter_mut() {
|
||||
if let Some(scale_factor) = window_compositor_update.scale_factor.map(|f| f as f64)
|
||||
{
|
||||
let mut physical_size = self.with_state(|state| {
|
||||
let window_handle = state.window_map.get(window_id).unwrap();
|
||||
let mut size = window_handle.size.lock().unwrap();
|
||||
|
||||
// Update the new logical size if it was changed.
|
||||
let window_size = window_compositor_update.size.unwrap_or(*size);
|
||||
*size = window_size;
|
||||
|
||||
window_size.to_physical(scale_factor)
|
||||
});
|
||||
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size: &mut physical_size,
|
||||
},
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
// We don't update size on a window handle since we'll do that later
|
||||
// when handling size update.
|
||||
let new_logical_size = physical_size.to_logical(scale_factor);
|
||||
window_compositor_update.size = Some(new_logical_size);
|
||||
}
|
||||
|
||||
if let Some(size) = window_compositor_update.size.take() {
|
||||
let physical_size = self.with_state(|state| {
|
||||
let window_handle = state.window_map.get_mut(window_id).unwrap();
|
||||
let mut window_size = window_handle.size.lock().unwrap();
|
||||
|
||||
// Always issue resize event on scale factor change.
|
||||
let physical_size = if window_compositor_update.scale_factor.is_none()
|
||||
&& *window_size == size
|
||||
{
|
||||
// The size hasn't changed, don't inform downstream about that.
|
||||
None
|
||||
} else {
|
||||
*window_size = size;
|
||||
let scale_factor =
|
||||
sctk::get_surface_scale_factor(window_handle.window.surface());
|
||||
let physical_size = size.to_physical(scale_factor as f64);
|
||||
Some(physical_size)
|
||||
};
|
||||
|
||||
// We still perform all of those resize related logic even if the size
|
||||
// hasn't changed, since GNOME relies on `set_geometry` calls after
|
||||
// configures.
|
||||
window_handle.window.resize(size.width, size.height);
|
||||
window_handle.window.refresh();
|
||||
|
||||
// Mark that refresh isn't required, since we've done it right now.
|
||||
state
|
||||
.window_user_requests
|
||||
.get_mut(window_id)
|
||||
.unwrap()
|
||||
.refresh_frame = false;
|
||||
|
||||
physical_size
|
||||
});
|
||||
|
||||
if let Some(physical_size) = physical_size {
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If the close is requested, send it here.
|
||||
if window_compositor_update.close_window {
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event: WindowEvent::CloseRequested,
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The purpose of the back buffer and that swap is to not hold borrow_mut when
|
||||
// we're doing callback to the user, since we can double borrow if the user decides
|
||||
// to create a window in one of those callbacks.
|
||||
self.with_state(|state| {
|
||||
std::mem::swap(
|
||||
&mut event_sink_back_buffer,
|
||||
&mut state.event_sink.window_events,
|
||||
)
|
||||
});
|
||||
|
||||
// Handle pending window events.
|
||||
for event in event_sink_back_buffer.drain(..) {
|
||||
let event = event.map_nonuser_event().unwrap();
|
||||
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
|
||||
}
|
||||
|
||||
// Send events cleared.
|
||||
sticky_exit_callback(
|
||||
Event::MainEventsCleared,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
// Apply user requests, so every event required resize and latter surface commit will
|
||||
// be applied right before drawing. This will also ensure that every `RedrawRequested`
|
||||
// event will be delivered in time.
|
||||
self.with_state(|state| {
|
||||
shim::handle_window_requests(state);
|
||||
});
|
||||
|
||||
// Process 'new' pending updates from compositor.
|
||||
self.with_state(|state| {
|
||||
window_user_requests.clear();
|
||||
window_user_requests.extend(
|
||||
state
|
||||
.window_user_requests
|
||||
.iter_mut()
|
||||
.map(|(wid, window_request)| (*wid, mem::take(window_request))),
|
||||
);
|
||||
});
|
||||
|
||||
// Handle RedrawRequested events.
|
||||
for (window_id, mut window_request) in window_user_requests.iter() {
|
||||
// Handle refresh of the frame.
|
||||
if window_request.refresh_frame {
|
||||
self.with_state(|state| {
|
||||
let window_handle = state.window_map.get_mut(window_id).unwrap();
|
||||
window_handle.window.refresh();
|
||||
});
|
||||
|
||||
// In general refreshing the frame requires surface commit, those force user
|
||||
// to redraw.
|
||||
window_request.redraw_requested = true;
|
||||
}
|
||||
|
||||
// Handle redraw request.
|
||||
if window_request.redraw_requested {
|
||||
sticky_exit_callback(
|
||||
Event::RedrawRequested(crate::window::WindowId(*window_id)),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Send RedrawEventCleared.
|
||||
sticky_exit_callback(
|
||||
Event::RedrawEventsCleared,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
};
|
||||
|
||||
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
|
||||
exit_code
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy::new(self.user_events_sender.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
|
||||
&self.window_target
|
||||
}
|
||||
|
||||
fn with_state<U, F: FnOnce(&mut WinitState) -> U>(&mut self, f: F) -> U {
|
||||
let state = match &mut self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
|
||||
#[cfg(feature = "x11")]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
f(state)
|
||||
}
|
||||
|
||||
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
|
||||
let state = match &mut self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
|
||||
#[cfg(feature = "x11")]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.event_loop
|
||||
.dispatch(timeout, state)
|
||||
.map_err(|error| error.into())
|
||||
}
|
||||
}
|
||||
32
src/platform_impl/linux/wayland/event_loop/proxy.rs
Normal file
32
src/platform_impl/linux/wayland/event_loop/proxy.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! An event loop proxy.
|
||||
|
||||
use std::sync::mpsc::SendError;
|
||||
|
||||
use sctk::reexports::calloop::channel::Sender;
|
||||
|
||||
use crate::event_loop::EventLoopClosed;
|
||||
|
||||
/// A handle that can be sent across the threads and used to wake up the `EventLoop`.
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
user_events_sender: Sender<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
EventLoopProxy {
|
||||
user_events_sender: self.user_events_sender.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopProxy<T> {
|
||||
pub fn new(user_events_sender: Sender<T>) -> Self {
|
||||
Self { user_events_sender }
|
||||
}
|
||||
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
self.user_events_sender
|
||||
.send(event)
|
||||
.map_err(|SendError(error)| EventLoopClosed(error))
|
||||
}
|
||||
}
|
||||
36
src/platform_impl/linux/wayland/event_loop/sink.rs
Normal file
36
src/platform_impl/linux/wayland/event_loop/sink.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
//! An event loop's sink to deliver events from the Wayland event callbacks.
|
||||
|
||||
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
|
||||
use crate::platform_impl::platform::DeviceId as PlatformDeviceId;
|
||||
use crate::window::WindowId as RootWindowId;
|
||||
|
||||
use super::{DeviceId, WindowId};
|
||||
|
||||
/// An event loop's sink to deliver events from the Wayland event callbacks
|
||||
/// to the winit's user.
|
||||
#[derive(Default)]
|
||||
pub struct EventSink {
|
||||
pub window_events: Vec<Event<'static, ()>>,
|
||||
}
|
||||
|
||||
impl EventSink {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Add new device event to a queue.
|
||||
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
|
||||
self.window_events.push(Event::DeviceEvent {
|
||||
event,
|
||||
device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)),
|
||||
});
|
||||
}
|
||||
|
||||
/// Add new window event to a queue.
|
||||
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
|
||||
self.window_events.push(Event::WindowEvent {
|
||||
event,
|
||||
window_id: RootWindowId(window_id),
|
||||
});
|
||||
}
|
||||
}
|
||||
31
src/platform_impl/linux/wayland/event_loop/state.rs
Normal file
31
src/platform_impl/linux/wayland/event_loop/state.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! A state that we pass around in a dispatch.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::EventSink;
|
||||
use crate::platform_impl::wayland::window::shim::{
|
||||
WindowCompositorUpdate, WindowHandle, WindowUserRequest,
|
||||
};
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
|
||||
/// Wrapper to carry winit's state.
|
||||
pub struct WinitState {
|
||||
/// A sink for window and device events that is being filled during dispatching
|
||||
/// event loop and forwarded downstream afterwards.
|
||||
pub event_sink: EventSink,
|
||||
|
||||
/// Window updates comming from the user requests. Those are separatelly dispatched right after
|
||||
/// `MainEventsCleared`.
|
||||
pub window_user_requests: HashMap<WindowId, WindowUserRequest>,
|
||||
|
||||
/// Window updates, which are coming from SCTK or the compositor, which require
|
||||
/// calling back to the winit's downstream. They are handled right in the event loop,
|
||||
/// unlike the ones coming from buffers on the `WindowHandle`'s.
|
||||
pub window_compositor_updates: HashMap<WindowId, WindowCompositorUpdate>,
|
||||
|
||||
/// Window map containing all SCTK windows. Since those windows aren't allowed
|
||||
/// to be sent to other threads, they live on the event loop's thread
|
||||
/// and requests from winit's windows are being forwarded to them either via
|
||||
/// `WindowUpdate` or buffer on the associated with it `WindowHandle`.
|
||||
pub window_map: HashMap<WindowId, WindowHandle>,
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::{make_wid, DeviceId};
|
||||
use smithay_client_toolkit::{
|
||||
keyboard::{
|
||||
self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind,
|
||||
},
|
||||
reexports::client::protocol::{wl_keyboard, wl_seat},
|
||||
};
|
||||
|
||||
use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
|
||||
|
||||
pub fn init_keyboard(
|
||||
seat: &wl_seat::WlSeat,
|
||||
sink: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
) -> wl_keyboard::WlKeyboard {
|
||||
// { variables to be captured by the closures
|
||||
let target = Arc::new(Mutex::new(None));
|
||||
let my_sink = sink.clone();
|
||||
let repeat_sink = sink.clone();
|
||||
let repeat_target = target.clone();
|
||||
let my_modifiers = modifiers_tracker.clone();
|
||||
// }
|
||||
let ret = map_keyboard_auto_with_repeat(
|
||||
seat,
|
||||
KeyRepeatKind::System,
|
||||
move |evt: KbEvent<'_>, _| {
|
||||
match evt {
|
||||
KbEvent::Enter { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send((WindowEvent::Focused(true), wid)).unwrap();
|
||||
*target.lock().unwrap() = Some(wid);
|
||||
}
|
||||
KbEvent::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send((WindowEvent::Focused(false), wid)).unwrap();
|
||||
*target.lock().unwrap() = None;
|
||||
}
|
||||
KbEvent::Key {
|
||||
rawkey,
|
||||
keysym,
|
||||
state,
|
||||
utf8,
|
||||
..
|
||||
} => {
|
||||
if let Some(wid) = *target.lock().unwrap() {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let vkcode = key_to_vkey(rawkey, keysym);
|
||||
my_sink
|
||||
.send((
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
},
|
||||
wid,
|
||||
))
|
||||
.unwrap();
|
||||
// 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() {
|
||||
my_sink
|
||||
.send((WindowEvent::ReceivedCharacter(chr), wid))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
|
||||
KbEvent::Modifiers {
|
||||
modifiers: event_modifiers,
|
||||
} => *modifiers_tracker.lock().unwrap() = event_modifiers.into(),
|
||||
}
|
||||
},
|
||||
move |repeat_event: KeyRepeatEvent, _| {
|
||||
if let Some(wid) = *repeat_target.lock().unwrap() {
|
||||
let state = ElementState::Pressed;
|
||||
let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym);
|
||||
repeat_sink
|
||||
.send((
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: repeat_event.rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: my_modifiers.lock().unwrap().clone(),
|
||||
},
|
||||
},
|
||||
wid,
|
||||
))
|
||||
.unwrap();
|
||||
if let Some(txt) = repeat_event.utf8 {
|
||||
for chr in txt.chars() {
|
||||
repeat_sink
|
||||
.send((WindowEvent::ReceivedCharacter(chr), wid))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
match ret {
|
||||
Ok(keyboard) => keyboard,
|
||||
Err(_) => {
|
||||
// This is a 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)
|
||||
|
||||
seat.get_keyboard(|keyboard| {
|
||||
// { variables to be captured by the closure
|
||||
let mut target = None;
|
||||
let my_sink = sink;
|
||||
// }
|
||||
|
||||
keyboard.implement_closure(
|
||||
move |evt, _| match evt {
|
||||
wl_keyboard::Event::Enter { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send((WindowEvent::Focused(true), wid)).unwrap();
|
||||
target = Some(wid);
|
||||
}
|
||||
wl_keyboard::Event::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send((WindowEvent::Focused(false), wid)).unwrap();
|
||||
target = None;
|
||||
}
|
||||
wl_keyboard::Event::Key { key, state, .. } => {
|
||||
if let Some(wid) = target {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
my_sink
|
||||
.send((
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: key,
|
||||
virtual_keycode: None,
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
},
|
||||
wid,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 smithay_client_toolkit::keyboard::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),
|
||||
keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16),
|
||||
keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17),
|
||||
keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18),
|
||||
keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19),
|
||||
keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20),
|
||||
keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21),
|
||||
keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22),
|
||||
keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23),
|
||||
keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24),
|
||||
// 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),
|
||||
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),
|
||||
keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::Add),
|
||||
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::Subtract),
|
||||
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::Divide),
|
||||
keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp),
|
||||
keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown),
|
||||
keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home),
|
||||
keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End),
|
||||
// => 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),
|
||||
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),
|
||||
keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy),
|
||||
keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste),
|
||||
keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut),
|
||||
// fallback
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<keyboard::ModifiersState> for ModifiersState {
|
||||
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
|
||||
ModifiersState {
|
||||
shift: mods.shift,
|
||||
ctrl: mods.ctrl,
|
||||
alt: mods.alt,
|
||||
logo: mods.logo,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,34 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd",
|
||||
target_os = "netbsd", target_os = "openbsd"))]
|
||||
#![cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
|
||||
pub use self::{
|
||||
event_loop::{
|
||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, WindowEventsSink,
|
||||
},
|
||||
window::Window,
|
||||
};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::wl_surface;
|
||||
pub use crate::platform_impl::platform::WindowId;
|
||||
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
pub use output::{MonitorHandle, VideoMode};
|
||||
pub use window::Window;
|
||||
|
||||
mod env;
|
||||
mod event_loop;
|
||||
mod keyboard;
|
||||
mod pointer;
|
||||
mod touch;
|
||||
mod output;
|
||||
mod seat;
|
||||
mod window;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(usize);
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn make_wid(s: &wl_surface::WlSurface) -> WindowId {
|
||||
WindowId(s.as_ref().c_ptr() as usize)
|
||||
fn make_wid(surface: &WlSurface) -> WindowId {
|
||||
WindowId(surface.as_ref().c_ptr() as u64)
|
||||
}
|
||||
|
||||
250
src/platform_impl/linux/wayland/output.rs
Normal file
250
src/platform_impl/linux/wayland/output.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::client::Display;
|
||||
|
||||
use sctk::environment::Environment;
|
||||
use sctk::output::OutputStatusListener;
|
||||
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode};
|
||||
use crate::platform_impl::platform::{
|
||||
MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode,
|
||||
};
|
||||
|
||||
use super::env::WinitEnv;
|
||||
use super::event_loop::EventLoopWindowTarget;
|
||||
|
||||
/// Output manager.
|
||||
pub struct OutputManager {
|
||||
/// A handle that actually performs all operations on outputs.
|
||||
handle: OutputManagerHandle,
|
||||
|
||||
_output_listener: OutputStatusListener,
|
||||
}
|
||||
|
||||
impl OutputManager {
|
||||
pub fn new(env: &Environment<WinitEnv>) -> Self {
|
||||
let handle = OutputManagerHandle::new();
|
||||
|
||||
// Handle existing outputs.
|
||||
for output in env.get_all_outputs() {
|
||||
match sctk::output::with_output_info(&output, |info| info.obsolete) {
|
||||
Some(false) => (),
|
||||
// The output is obsolete or we've failed to access its data, skipping.
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
// The output is present and unusable, add it to the output manager manager.
|
||||
handle.add_output(output);
|
||||
}
|
||||
|
||||
let handle_for_listener = handle.clone();
|
||||
|
||||
let output_listener = env.listen_for_outputs(move |output, info, _| {
|
||||
if info.obsolete {
|
||||
handle_for_listener.remove_output(output)
|
||||
} else {
|
||||
handle_for_listener.add_output(output)
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
handle,
|
||||
_output_listener: output_listener,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> OutputManagerHandle {
|
||||
self.handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to output manager.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutputManagerHandle {
|
||||
outputs: Arc<Mutex<VecDeque<MonitorHandle>>>,
|
||||
}
|
||||
|
||||
impl OutputManagerHandle {
|
||||
fn new() -> Self {
|
||||
let outputs = Arc::new(Mutex::new(VecDeque::new()));
|
||||
Self { outputs }
|
||||
}
|
||||
|
||||
/// Handle addition of the output.
|
||||
fn add_output(&self, output: WlOutput) {
|
||||
let mut outputs = self.outputs.lock().unwrap();
|
||||
let position = outputs.iter().position(|handle| handle.proxy == output);
|
||||
if position.is_none() {
|
||||
outputs.push_back(MonitorHandle::new(output));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle removal of the output.
|
||||
fn remove_output(&self, output: WlOutput) {
|
||||
let mut outputs = self.outputs.lock().unwrap();
|
||||
let position = outputs.iter().position(|handle| handle.proxy == output);
|
||||
if let Some(position) = position {
|
||||
outputs.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all observed outputs.
|
||||
pub fn available_outputs(&self) -> VecDeque<MonitorHandle> {
|
||||
self.outputs.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MonitorHandle {
|
||||
pub(crate) proxy: WlOutput,
|
||||
}
|
||||
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.native_identifier() == other.native_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MonitorHandle {}
|
||||
|
||||
impl PartialOrd for MonitorHandle {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for MonitorHandle {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.native_identifier().cmp(&other.native_identifier())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for MonitorHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.native_identifier().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
#[inline]
|
||||
pub(crate) fn new(proxy: WlOutput) -> Self {
|
||||
Self { proxy }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
sctk::output::with_output_info(&self.proxy, |info| {
|
||||
format!("{} ({})", info.model, info.make)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_identifier(&self) -> u32 {
|
||||
sctk::output::with_output_info(&self.proxy, |info| info.id).unwrap_or(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
match sctk::output::with_output_info(&self.proxy, |info| {
|
||||
info.modes
|
||||
.iter()
|
||||
.find(|mode| mode.is_current)
|
||||
.map(|mode| mode.dimensions)
|
||||
}) {
|
||||
Some(Some((w, h))) => (w as u32, h as u32),
|
||||
_ => (0, 0),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
sctk::output::with_output_info(&self.proxy, |info| info.location)
|
||||
.unwrap_or((0, 0))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
sctk::output::with_output_info(&self.proxy, |info| {
|
||||
info.modes
|
||||
.iter()
|
||||
.find_map(|mode| mode.is_current.then(|| mode.refresh_rate as u32))
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> i32 {
|
||||
sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||
let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let monitor = self.clone();
|
||||
|
||||
modes.into_iter().map(move |mode| RootVideoMode {
|
||||
video_mode: PlatformVideoMode::Wayland(VideoMode {
|
||||
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
|
||||
refresh_rate_millihertz: mode.refresh_rate as u32,
|
||||
bit_depth: 32,
|
||||
monitor: monitor.clone(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: PhysicalSize<u32>,
|
||||
pub(crate) bit_depth: u16,
|
||||
pub(crate) refresh_rate_millihertz: u32,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> u16 {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> RootMonitorHandle {
|
||||
RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(self.monitor.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
#[inline]
|
||||
pub fn display(&self) -> &Display {
|
||||
&self.display
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
self.output_manager.handle.available_outputs()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
||||
// There's no primary monitor on Wayland.
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
WindowEvent,
|
||||
};
|
||||
|
||||
use super::{event_loop::WindowEventsSink, window::WindowStore, DeviceId};
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::{
|
||||
wl_pointer::{self, Event as PtrEvent, WlPointer},
|
||||
wl_seat,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, zwp_relative_pointer_v1::Event,
|
||||
zwp_relative_pointer_v1::ZwpRelativePointerV1,
|
||||
};
|
||||
|
||||
pub fn implement_pointer<T: 'static>(
|
||||
seat: &wl_seat::WlSeat,
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
) -> WlPointer {
|
||||
seat.get_pointer(|pointer| {
|
||||
let mut mouse_focus = None;
|
||||
let mut axis_buffer = None;
|
||||
let mut axis_discrete_buffer = None;
|
||||
let mut axis_state = TouchPhase::Ended;
|
||||
|
||||
pointer.implement_closure(
|
||||
move |evt, pointer| {
|
||||
let mut sink = sink.lock().unwrap();
|
||||
let store = store.lock().unwrap();
|
||||
match evt {
|
||||
PtrEvent::Enter {
|
||||
surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
mouse_focus = Some(wid);
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorEntered {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
position: (surface_x, surface_y).into(),
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Leave { surface, .. } => {
|
||||
mouse_focus = None;
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorLeft {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Motion {
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
position: (surface_x, surface_y).into(),
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Button { button, state, .. } => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
let state = match state {
|
||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
||||
wl_pointer::ButtonState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let button = match button {
|
||||
0x110 => MouseButton::Left,
|
||||
0x111 => MouseButton::Right,
|
||||
0x112 => MouseButton::Middle,
|
||||
// TODO figure out the translation ?
|
||||
_ => return,
|
||||
};
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseInput {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
state,
|
||||
button,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Axis { axis, value, .. } => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
if pointer.as_ref().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,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
delta: MouseScrollDelta::PixelDelta(
|
||||
(x as f64, y as f64).into(),
|
||||
),
|
||||
phase: TouchPhase::Moved,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
} else {
|
||||
let (mut x, mut y) = 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,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
axis_buffer = Some((x, y));
|
||||
axis_state = match axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PtrEvent::Frame => {
|
||||
let axis_buffer = axis_buffer.take();
|
||||
let axis_discrete_buffer = axis_discrete_buffer.take();
|
||||
if let Some(wid) = mouse_focus {
|
||||
if let Some((x, y)) = axis_discrete_buffer {
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
delta: MouseScrollDelta::LineDelta(x as f32, y as f32),
|
||||
phase: axis_state,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
} else if let Some((x, y)) = axis_buffer {
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
delta: MouseScrollDelta::PixelDelta(
|
||||
(x as f64, y as f64).into(),
|
||||
),
|
||||
phase: axis_state,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
PtrEvent::AxisSource { .. } => (),
|
||||
PtrEvent::AxisStop { .. } => {
|
||||
axis_state = TouchPhase::Ended;
|
||||
}
|
||||
PtrEvent::AxisDiscrete { axis, discrete } => {
|
||||
let (mut x, mut y) = 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,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
axis_discrete_buffer = Some((x, y));
|
||||
axis_state = match axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn implement_relative_pointer<T: 'static>(
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
pointer: &WlPointer,
|
||||
manager: &ZwpRelativePointerManagerV1,
|
||||
) -> Result<ZwpRelativePointerV1, ()> {
|
||||
manager.get_relative_pointer(pointer, |rel_pointer| {
|
||||
rel_pointer.implement_closure(
|
||||
move |evt, _rel_pointer| {
|
||||
let mut sink = sink.lock().unwrap();
|
||||
match evt {
|
||||
Event::RelativeMotion { dx, dy, .. } => sink
|
||||
.send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
}
|
||||
151
src/platform_impl/linux/wayland/seat/keyboard/handlers.rs
Normal file
151
src/platform_impl/linux/wayland/seat/keyboard/handlers.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
//! Handling of various keyboard events.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_keyboard::KeyState;
|
||||
|
||||
use sctk::seat::keyboard::Event as KeyboardEvent;
|
||||
|
||||
use crate::event::{ElementState, KeyboardInput, ModifiersState, WindowEvent};
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId};
|
||||
|
||||
use super::keymap;
|
||||
use super::KeyboardInner;
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_keyboard(
|
||||
event: KeyboardEvent<'_>,
|
||||
inner: &mut KeyboardInner,
|
||||
winit_state: &mut WinitState,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
match event {
|
||||
KeyboardEvent::Enter { surface, .. } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
// Window gained focus.
|
||||
event_sink.push_window_event(WindowEvent::Focused(true), window_id);
|
||||
|
||||
// Dispatch modifers changes that we've received before getting `Enter` event.
|
||||
if let Some(modifiers) = inner.pending_modifers_state.take() {
|
||||
*inner.modifiers_state.borrow_mut() = modifiers;
|
||||
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
|
||||
}
|
||||
|
||||
inner.target_window_id = Some(window_id);
|
||||
}
|
||||
KeyboardEvent::Leave { surface, .. } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
// Notify that no modifiers are being pressed.
|
||||
if !inner.modifiers_state.borrow().is_empty() {
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(ModifiersState::empty()),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
// Window lost focus.
|
||||
event_sink.push_window_event(WindowEvent::Focused(false), window_id);
|
||||
|
||||
// Reset the id.
|
||||
inner.target_window_id = None;
|
||||
}
|
||||
KeyboardEvent::Key {
|
||||
rawkey,
|
||||
keysym,
|
||||
state,
|
||||
utf8,
|
||||
..
|
||||
} => {
|
||||
let window_id = match inner.target_window_id {
|
||||
Some(window_id) => window_id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let state = match state {
|
||||
KeyState::Pressed => ElementState::Pressed,
|
||||
KeyState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let virtual_keycode = keymap::keysym_to_vkey(keysym);
|
||||
|
||||
event_sink.push_window_event(
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: rawkey,
|
||||
virtual_keycode,
|
||||
modifiers: *inner.modifiers_state.borrow(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
|
||||
// Send ReceivedCharacter event only on ElementState::Pressed.
|
||||
if ElementState::Released == state {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(txt) = utf8 {
|
||||
for ch in txt.chars() {
|
||||
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyboardEvent::Repeat {
|
||||
rawkey,
|
||||
keysym,
|
||||
utf8,
|
||||
..
|
||||
} => {
|
||||
let window_id = match inner.target_window_id {
|
||||
Some(window_id) => window_id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let virtual_keycode = keymap::keysym_to_vkey(keysym);
|
||||
|
||||
event_sink.push_window_event(
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
input: KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
scancode: rawkey,
|
||||
virtual_keycode,
|
||||
modifiers: *inner.modifiers_state.borrow(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
|
||||
if let Some(txt) = utf8 {
|
||||
for ch in txt.chars() {
|
||||
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyboardEvent::Modifiers { modifiers } => {
|
||||
let modifiers = ModifiersState::from(modifiers);
|
||||
if let Some(window_id) = inner.target_window_id {
|
||||
*inner.modifiers_state.borrow_mut() = modifiers;
|
||||
|
||||
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
|
||||
} else {
|
||||
// Compositor must send modifiers after wl_keyboard::enter, however certain
|
||||
// compositors are still sending it before, so stash such events and send
|
||||
// them on wl_keyboard::enter.
|
||||
inner.pending_modifers_state = Some(modifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
192
src/platform_impl/linux/wayland/seat/keyboard/keymap.rs
Normal file
192
src/platform_impl/linux/wayland/seat/keyboard/keymap.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
//! Convert Wayland keys to winit keys.
|
||||
|
||||
use crate::event::VirtualKeyCode;
|
||||
|
||||
pub fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
use sctk::seat::keyboard::keysyms;
|
||||
match keysym {
|
||||
// Numbers.
|
||||
keysyms::XKB_KEY_1 => Some(VirtualKeyCode::Key1),
|
||||
keysyms::XKB_KEY_2 => Some(VirtualKeyCode::Key2),
|
||||
keysyms::XKB_KEY_3 => Some(VirtualKeyCode::Key3),
|
||||
keysyms::XKB_KEY_4 => Some(VirtualKeyCode::Key4),
|
||||
keysyms::XKB_KEY_5 => Some(VirtualKeyCode::Key5),
|
||||
keysyms::XKB_KEY_6 => Some(VirtualKeyCode::Key6),
|
||||
keysyms::XKB_KEY_7 => Some(VirtualKeyCode::Key7),
|
||||
keysyms::XKB_KEY_8 => Some(VirtualKeyCode::Key8),
|
||||
keysyms::XKB_KEY_9 => Some(VirtualKeyCode::Key9),
|
||||
keysyms::XKB_KEY_0 => Some(VirtualKeyCode::Key0),
|
||||
// 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),
|
||||
// Escape.
|
||||
keysyms::XKB_KEY_Escape => Some(VirtualKeyCode::Escape),
|
||||
// Function keys.
|
||||
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),
|
||||
keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16),
|
||||
keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17),
|
||||
keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18),
|
||||
keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19),
|
||||
keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20),
|
||||
keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21),
|
||||
keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22),
|
||||
keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23),
|
||||
keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24),
|
||||
// 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),
|
||||
|
||||
keysyms::XKB_KEY_Multi_key => Some(VirtualKeyCode::Compose),
|
||||
keysyms::XKB_KEY_caret => Some(VirtualKeyCode::Caret),
|
||||
|
||||
// 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::Plus),
|
||||
keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe),
|
||||
// => Some(VirtualKeyCode::Apps),
|
||||
keysyms::XKB_KEY_at => Some(VirtualKeyCode::At),
|
||||
// => Some(VirtualKeyCode::Ax),
|
||||
keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash),
|
||||
keysyms::XKB_KEY_XF86Calculator => Some(VirtualKeyCode::Calculator),
|
||||
// => Some(VirtualKeyCode::Capital),
|
||||
keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon),
|
||||
keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma),
|
||||
// => Some(VirtualKeyCode::Convert),
|
||||
keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals),
|
||||
keysyms::XKB_KEY_grave => Some(VirtualKeyCode::Grave),
|
||||
// => Some(VirtualKeyCode::Kana),
|
||||
keysyms::XKB_KEY_Kanji => Some(VirtualKeyCode::Kanji),
|
||||
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
|
||||
keysyms::XKB_KEY_bracketleft => Some(VirtualKeyCode::LBracket),
|
||||
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
|
||||
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
|
||||
keysyms::XKB_KEY_Super_L => Some(VirtualKeyCode::LWin),
|
||||
keysyms::XKB_KEY_XF86Mail => Some(VirtualKeyCode::Mail),
|
||||
// => Some(VirtualKeyCode::MediaSelect),
|
||||
// => Some(VirtualKeyCode::MediaStop),
|
||||
keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus),
|
||||
keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk),
|
||||
keysyms::XKB_KEY_XF86AudioMute => Some(VirtualKeyCode::Mute),
|
||||
// => Some(VirtualKeyCode::MyComputer),
|
||||
keysyms::XKB_KEY_XF86AudioNext => 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),
|
||||
keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd),
|
||||
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract),
|
||||
keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply),
|
||||
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide),
|
||||
keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal),
|
||||
keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp),
|
||||
keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown),
|
||||
keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home),
|
||||
keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End),
|
||||
keysyms::XKB_KEY_KP_Left => Some(VirtualKeyCode::Left),
|
||||
keysyms::XKB_KEY_KP_Up => Some(VirtualKeyCode::Up),
|
||||
keysyms::XKB_KEY_KP_Right => Some(VirtualKeyCode::Right),
|
||||
keysyms::XKB_KEY_KP_Down => Some(VirtualKeyCode::Down),
|
||||
// => Some(VirtualKeyCode::OEM102),
|
||||
keysyms::XKB_KEY_period => Some(VirtualKeyCode::Period),
|
||||
// => Some(VirtualKeyCode::Playpause),
|
||||
keysyms::XKB_KEY_XF86PowerOff => Some(VirtualKeyCode::Power),
|
||||
keysyms::XKB_KEY_XF86AudioPrev => Some(VirtualKeyCode::PrevTrack),
|
||||
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
|
||||
keysyms::XKB_KEY_bracketright => Some(VirtualKeyCode::RBracket),
|
||||
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
|
||||
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
|
||||
keysyms::XKB_KEY_Super_R => Some(VirtualKeyCode::RWin),
|
||||
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
|
||||
keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash),
|
||||
keysyms::XKB_KEY_XF86Sleep => Some(VirtualKeyCode::Sleep),
|
||||
// => Some(VirtualKeyCode::Stop),
|
||||
// => Some(VirtualKeyCode::Sysrq),
|
||||
keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab),
|
||||
keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab),
|
||||
keysyms::XKB_KEY_underscore => 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),
|
||||
keysyms::XKB_KEY_yen => Some(VirtualKeyCode::Yen),
|
||||
keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy),
|
||||
keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste),
|
||||
keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut),
|
||||
// Fallback.
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
90
src/platform_impl/linux/wayland/seat/keyboard/mod.rs
Normal file
90
src/platform_impl/linux/wayland/seat/keyboard/mod.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
//! Wayland keyboard handling.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::Attached;
|
||||
|
||||
use sctk::reexports::calloop::LoopHandle;
|
||||
|
||||
use sctk::seat::keyboard;
|
||||
|
||||
use crate::event::ModifiersState;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
|
||||
mod handlers;
|
||||
mod keymap;
|
||||
|
||||
pub(crate) struct Keyboard {
|
||||
pub keyboard: WlKeyboard,
|
||||
}
|
||||
|
||||
impl Keyboard {
|
||||
pub fn new(
|
||||
seat: &Attached<WlSeat>,
|
||||
loop_handle: LoopHandle<'static, WinitState>,
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Option<Self> {
|
||||
let mut inner = KeyboardInner::new(modifiers_state);
|
||||
let keyboard = keyboard::map_keyboard_repeat(
|
||||
loop_handle.clone(),
|
||||
seat,
|
||||
None,
|
||||
keyboard::RepeatKind::System,
|
||||
move |event, _, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_keyboard(event, &mut inner, winit_state);
|
||||
},
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
Some(Self { keyboard })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Keyboard {
|
||||
fn drop(&mut self) {
|
||||
if self.keyboard.as_ref().version() >= 3 {
|
||||
self.keyboard.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyboardInner {
|
||||
/// Currently focused surface.
|
||||
target_window_id: Option<WindowId>,
|
||||
|
||||
/// A pending state of modifiers.
|
||||
///
|
||||
/// This state is getting set if we've got a modifiers update
|
||||
/// before `Enter` event, which shouldn't happen in general, however
|
||||
/// some compositors are still doing so.
|
||||
pending_modifers_state: Option<ModifiersState>,
|
||||
|
||||
/// Current state of modifiers keys.
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
}
|
||||
|
||||
impl KeyboardInner {
|
||||
fn new(modifiers_state: Rc<RefCell<ModifiersState>>) -> Self {
|
||||
Self {
|
||||
target_window_id: None,
|
||||
pending_modifers_state: None,
|
||||
modifiers_state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<keyboard::ModifiersState> for ModifiersState {
|
||||
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
|
||||
let mut wl_mods = ModifiersState::empty();
|
||||
wl_mods.set(ModifiersState::SHIFT, mods.shift);
|
||||
wl_mods.set(ModifiersState::CTRL, mods.ctrl);
|
||||
wl_mods.set(ModifiersState::ALT, mods.alt);
|
||||
wl_mods.set(ModifiersState::LOGO, mods.logo);
|
||||
wl_mods
|
||||
}
|
||||
}
|
||||
208
src/platform_impl/linux/wayland/seat/mod.rs
Normal file
208
src/platform_impl/linux/wayland/seat/mod.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
//! Seat handling and managing.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::Attached;
|
||||
|
||||
use sctk::environment::Environment;
|
||||
use sctk::reexports::calloop::LoopHandle;
|
||||
use sctk::seat::pointer::ThemeManager;
|
||||
use sctk::seat::{SeatData, SeatListener};
|
||||
|
||||
use super::env::WinitEnv;
|
||||
use super::event_loop::WinitState;
|
||||
use crate::event::ModifiersState;
|
||||
|
||||
mod keyboard;
|
||||
pub mod pointer;
|
||||
pub mod text_input;
|
||||
mod touch;
|
||||
|
||||
use keyboard::Keyboard;
|
||||
use pointer::Pointers;
|
||||
use text_input::TextInput;
|
||||
use touch::Touch;
|
||||
|
||||
pub struct SeatManager {
|
||||
/// Listener for seats.
|
||||
_seat_listener: SeatListener,
|
||||
}
|
||||
|
||||
impl SeatManager {
|
||||
pub fn new(
|
||||
env: &Environment<WinitEnv>,
|
||||
loop_handle: LoopHandle<'static, WinitState>,
|
||||
theme_manager: ThemeManager,
|
||||
) -> Self {
|
||||
let relative_pointer_manager = env.get_global::<ZwpRelativePointerManagerV1>();
|
||||
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>();
|
||||
let text_input_manager = env.get_global::<ZwpTextInputManagerV3>();
|
||||
|
||||
let mut inner = SeatManagerInner::new(
|
||||
theme_manager,
|
||||
relative_pointer_manager,
|
||||
pointer_constraints,
|
||||
text_input_manager,
|
||||
loop_handle,
|
||||
);
|
||||
|
||||
// Handle existing seats.
|
||||
for seat in env.get_all_seats() {
|
||||
let seat_data = match sctk::seat::clone_seat_data(&seat) {
|
||||
Some(seat_data) => seat_data,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
inner.process_seat_update(&seat, &seat_data);
|
||||
}
|
||||
|
||||
let seat_listener = env.listen_for_seats(move |seat, seat_data, _| {
|
||||
inner.process_seat_update(&seat, seat_data);
|
||||
});
|
||||
|
||||
Self {
|
||||
_seat_listener: seat_listener,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inner state of the seat manager.
|
||||
struct SeatManagerInner {
|
||||
/// Currently observed seats.
|
||||
seats: Vec<SeatInfo>,
|
||||
|
||||
/// Loop handle.
|
||||
loop_handle: LoopHandle<'static, WinitState>,
|
||||
|
||||
/// Relative pointer manager.
|
||||
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
|
||||
|
||||
/// Pointer constraints.
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
|
||||
/// Text input manager.
|
||||
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
|
||||
|
||||
/// A theme manager.
|
||||
theme_manager: ThemeManager,
|
||||
}
|
||||
|
||||
impl SeatManagerInner {
|
||||
fn new(
|
||||
theme_manager: ThemeManager,
|
||||
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
|
||||
loop_handle: LoopHandle<'static, WinitState>,
|
||||
) -> Self {
|
||||
Self {
|
||||
seats: Vec::new(),
|
||||
loop_handle,
|
||||
relative_pointer_manager,
|
||||
pointer_constraints,
|
||||
text_input_manager,
|
||||
theme_manager,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle seats update from the `SeatListener`.
|
||||
pub fn process_seat_update(&mut self, seat: &Attached<WlSeat>, seat_data: &SeatData) {
|
||||
let detached_seat = seat.detach();
|
||||
|
||||
let position = self.seats.iter().position(|si| si.seat == detached_seat);
|
||||
let index = position.unwrap_or_else(|| {
|
||||
self.seats.push(SeatInfo::new(detached_seat));
|
||||
self.seats.len() - 1
|
||||
});
|
||||
|
||||
let seat_info = &mut self.seats[index];
|
||||
|
||||
// Pointer handling.
|
||||
if seat_data.has_pointer && !seat_data.defunct {
|
||||
if seat_info.pointer.is_none() {
|
||||
seat_info.pointer = Some(Pointers::new(
|
||||
seat,
|
||||
&self.theme_manager,
|
||||
&self.relative_pointer_manager,
|
||||
&self.pointer_constraints,
|
||||
seat_info.modifiers_state.clone(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
seat_info.pointer = None;
|
||||
}
|
||||
|
||||
// Handle keyboard.
|
||||
if seat_data.has_keyboard && !seat_data.defunct {
|
||||
if seat_info.keyboard.is_none() {
|
||||
seat_info.keyboard = Keyboard::new(
|
||||
seat,
|
||||
self.loop_handle.clone(),
|
||||
seat_info.modifiers_state.clone(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
seat_info.keyboard = None;
|
||||
}
|
||||
|
||||
// Handle touch.
|
||||
if seat_data.has_touch && !seat_data.defunct {
|
||||
if seat_info.touch.is_none() {
|
||||
seat_info.touch = Some(Touch::new(seat));
|
||||
}
|
||||
} else {
|
||||
seat_info.touch = None;
|
||||
}
|
||||
|
||||
// Handle text input.
|
||||
if let Some(text_input_manager) = self.text_input_manager.as_ref() {
|
||||
if seat_data.defunct {
|
||||
seat_info.text_input = None;
|
||||
} else if seat_info.text_input.is_none() {
|
||||
seat_info.text_input = Some(TextInput::new(seat, text_input_manager));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resources associtated with a given seat.
|
||||
struct SeatInfo {
|
||||
/// Seat to which this `SeatInfo` belongs.
|
||||
seat: WlSeat,
|
||||
|
||||
/// A keyboard handle with its repeat rate handling.
|
||||
keyboard: Option<Keyboard>,
|
||||
|
||||
/// All pointers we're using on a seat.
|
||||
pointer: Option<Pointers>,
|
||||
|
||||
/// Touch handling.
|
||||
touch: Option<Touch>,
|
||||
|
||||
/// Text input handling aka IME.
|
||||
text_input: Option<TextInput>,
|
||||
|
||||
/// The current state of modifiers observed in keyboard handler.
|
||||
///
|
||||
/// We keep modifiers state on a seat, since it's being used by pointer events as well.
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
}
|
||||
|
||||
impl SeatInfo {
|
||||
pub fn new(seat: WlSeat) -> Self {
|
||||
Self {
|
||||
seat,
|
||||
keyboard: None,
|
||||
pointer: None,
|
||||
touch: None,
|
||||
text_input: None,
|
||||
modifiers_state: Rc::new(RefCell::new(ModifiersState::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/platform_impl/linux/wayland/seat/pointer/data.rs
Normal file
82
src/platform_impl/linux/wayland/seat/pointer/data.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
//! Data which is used in pointer callbacks.
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
||||
|
||||
use crate::event::{ModifiersState, TouchPhase};
|
||||
|
||||
/// A data being used by pointer handlers.
|
||||
pub(super) struct PointerData {
|
||||
/// Winit's surface the pointer is currently over.
|
||||
pub surface: Option<WlSurface>,
|
||||
|
||||
/// Current modifiers state.
|
||||
///
|
||||
/// This refers a state of modifiers from `WlKeyboard` on
|
||||
/// the given seat.
|
||||
pub modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
|
||||
/// Pointer constraints.
|
||||
pub pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
|
||||
pub confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
pub locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
|
||||
/// Latest observed serial in pointer events.
|
||||
pub latest_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// Latest observed serial in pointer enter events.
|
||||
pub latest_enter_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// The currently accumulated axis data on a pointer.
|
||||
pub axis_data: AxisData,
|
||||
}
|
||||
|
||||
impl PointerData {
|
||||
pub fn new(
|
||||
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
surface: None,
|
||||
latest_serial: Rc::new(Cell::new(0)),
|
||||
latest_enter_serial: Rc::new(Cell::new(0)),
|
||||
confined_pointer,
|
||||
locked_pointer,
|
||||
modifiers_state,
|
||||
pointer_constraints,
|
||||
axis_data: AxisData::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Axis data.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct AxisData {
|
||||
/// Current state of the axis.
|
||||
pub axis_state: TouchPhase,
|
||||
|
||||
/// A buffer for `PixelDelta` event.
|
||||
pub axis_buffer: Option<(f32, f32)>,
|
||||
|
||||
/// A buffer for `LineDelta` event.
|
||||
pub axis_discrete_buffer: Option<(f32, f32)>,
|
||||
}
|
||||
|
||||
impl AxisData {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
axis_state: TouchPhase::Ended,
|
||||
axis_buffer: None,
|
||||
axis_discrete_buffer: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
318
src/platform_impl/linux/wayland/seat/pointer/handlers.rs
Normal file
318
src/platform_impl/linux/wayland/seat/pointer/handlers.rs
Normal file
@@ -0,0 +1,318 @@
|
||||
//! Handlers for the pointers we're using.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent};
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent;
|
||||
|
||||
use sctk::seat::pointer::ThemedPointer;
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
|
||||
};
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId};
|
||||
|
||||
use super::{PointerData, WinitPointer};
|
||||
|
||||
// These values are comming from <linux/input-event-codes.h>.
|
||||
const BTN_LEFT: u32 = 0x110;
|
||||
const BTN_RIGHT: u32 = 0x111;
|
||||
const BTN_MIDDLE: u32 = 0x112;
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_pointer(
|
||||
pointer: ThemedPointer,
|
||||
event: PointerEvent,
|
||||
pointer_data: &Rc<RefCell<PointerData>>,
|
||||
winit_state: &mut WinitState,
|
||||
seat: WlSeat,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
let mut pointer_data = pointer_data.borrow_mut();
|
||||
match event {
|
||||
PointerEvent::Enter {
|
||||
surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
serial,
|
||||
..
|
||||
} => {
|
||||
pointer_data.latest_serial.replace(serial);
|
||||
pointer_data.latest_enter_serial.replace(serial);
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
if !winit_state.window_map.contains_key(&window_id) {
|
||||
return;
|
||||
}
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
|
||||
pointer_data.surface = Some(surface);
|
||||
|
||||
// Notify window that pointer entered the surface.
|
||||
let winit_pointer = WinitPointer {
|
||||
pointer,
|
||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
||||
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
|
||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
||||
latest_serial: pointer_data.latest_serial.clone(),
|
||||
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
|
||||
seat,
|
||||
};
|
||||
window_handle.pointer_entered(winit_pointer);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorEntered {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
|
||||
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
position,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Leave { surface, serial } => {
|
||||
pointer_data.surface = None;
|
||||
pointer_data.latest_serial.replace(serial);
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Notify a window that pointer is no longer observing it.
|
||||
let winit_pointer = WinitPointer {
|
||||
pointer,
|
||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
||||
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
|
||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
||||
latest_serial: pointer_data.latest_serial.clone(),
|
||||
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
|
||||
seat,
|
||||
};
|
||||
window_handle.pointer_left(winit_pointer);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorLeft {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Motion {
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
let surface = match pointer_data.surface.as_ref() {
|
||||
Some(surface) => surface,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let window_id = wayland::make_wid(surface);
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
|
||||
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
position,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Button {
|
||||
button,
|
||||
state,
|
||||
serial,
|
||||
..
|
||||
} => {
|
||||
pointer_data.latest_serial.replace(serial);
|
||||
let window_id = match pointer_data.surface.as_ref().map(wayland::make_wid) {
|
||||
Some(window_id) => window_id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let state = match state {
|
||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
||||
wl_pointer::ButtonState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let button = match button {
|
||||
BTN_LEFT => MouseButton::Left,
|
||||
BTN_RIGHT => MouseButton::Right,
|
||||
BTN_MIDDLE => MouseButton::Middle,
|
||||
button => MouseButton::Other(button as u16),
|
||||
};
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::MouseInput {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
state,
|
||||
button,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Axis { axis, value, .. } => {
|
||||
let surface = match pointer_data.surface.as_ref() {
|
||||
Some(surface) => surface,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let window_id = wayland::make_wid(surface);
|
||||
|
||||
if pointer.as_ref().version() < 5 {
|
||||
let (mut x, mut y) = (0.0, 0.0);
|
||||
|
||||
// Old seat compatibility.
|
||||
match axis {
|
||||
// Wayland sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
|
||||
let delta = LogicalPosition::new(x as f64, y as f64).to_physical(scale_factor);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
delta: MouseScrollDelta::PixelDelta(delta),
|
||||
phase: TouchPhase::Moved,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
} else {
|
||||
let (mut x, mut y) = pointer_data.axis_data.axis_buffer.unwrap_or((0.0, 0.0));
|
||||
match axis {
|
||||
// Wayland sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
pointer_data.axis_data.axis_buffer = Some((x, y));
|
||||
|
||||
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
}
|
||||
PointerEvent::AxisDiscrete { axis, discrete } => {
|
||||
let (mut x, mut y) = pointer_data
|
||||
.axis_data
|
||||
.axis_discrete_buffer
|
||||
.unwrap_or((0., 0.));
|
||||
|
||||
match axis {
|
||||
// Wayland sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= discrete as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x -= discrete as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
pointer_data.axis_data.axis_discrete_buffer = Some((x, y));
|
||||
|
||||
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
PointerEvent::AxisSource { .. } => (),
|
||||
PointerEvent::AxisStop { .. } => {
|
||||
pointer_data.axis_data.axis_state = TouchPhase::Ended;
|
||||
}
|
||||
PointerEvent::Frame => {
|
||||
let axis_buffer = pointer_data.axis_data.axis_buffer.take();
|
||||
let axis_discrete_buffer = pointer_data.axis_data.axis_discrete_buffer.take();
|
||||
|
||||
let surface = match pointer_data.surface.as_ref() {
|
||||
Some(surface) => surface,
|
||||
None => return,
|
||||
};
|
||||
let window_id = wayland::make_wid(surface);
|
||||
|
||||
let window_event = if let Some((x, y)) = axis_discrete_buffer {
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
delta: MouseScrollDelta::LineDelta(x, y),
|
||||
phase: pointer_data.axis_data.axis_state,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
}
|
||||
} else if let Some((x, y)) = axis_buffer {
|
||||
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
|
||||
let delta = LogicalPosition::new(x, y).to_physical(scale_factor);
|
||||
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
delta: MouseScrollDelta::PixelDelta(delta),
|
||||
phase: pointer_data.axis_data.axis_state,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
event_sink.push_window_event(window_event, window_id);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_relative_pointer(event: RelativePointerEvent, winit_state: &mut WinitState) {
|
||||
if let RelativePointerEvent::RelativeMotion {
|
||||
dx_unaccel,
|
||||
dy_unaccel,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
winit_state.event_sink.push_device_event(
|
||||
DeviceEvent::MouseMotion {
|
||||
delta: (dx_unaccel, dy_unaccel),
|
||||
},
|
||||
DeviceId,
|
||||
)
|
||||
}
|
||||
}
|
||||
343
src/platform_impl/linux/wayland/seat/pointer/mod.rs
Normal file
343
src/platform_impl/linux/wayland/seat/pointer/mod.rs
Normal file
@@ -0,0 +1,343 @@
|
||||
//! All pointer related handling.
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime};
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
||||
|
||||
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
|
||||
use sctk::window::Window;
|
||||
|
||||
use crate::event::ModifiersState;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::window::WinitFrame;
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
mod data;
|
||||
mod handlers;
|
||||
|
||||
use data::PointerData;
|
||||
|
||||
/// A proxy to Wayland pointer, which serves requests from a `WindowHandle`.
|
||||
pub struct WinitPointer {
|
||||
pointer: ThemedPointer,
|
||||
|
||||
/// Create confined pointers.
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
|
||||
/// Cursor to handle confine requests.
|
||||
confined_pointer: Weak<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
|
||||
/// Cursor to handle locked requests.
|
||||
locked_pointer: Weak<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
|
||||
/// Latest observed serial in pointer events.
|
||||
/// used by Window::start_interactive_move()
|
||||
latest_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// Latest observed serial in pointer enter events.
|
||||
/// used by Window::set_cursor()
|
||||
latest_enter_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// Seat.
|
||||
seat: WlSeat,
|
||||
}
|
||||
|
||||
impl PartialEq for WinitPointer {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
*self.pointer == *other.pointer
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for WinitPointer {}
|
||||
|
||||
impl WinitPointer {
|
||||
/// Set the cursor icon.
|
||||
///
|
||||
/// Providing `None` will hide the cursor.
|
||||
pub fn set_cursor(&self, cursor_icon: Option<CursorIcon>) {
|
||||
let cursor_icon = match cursor_icon {
|
||||
Some(cursor_icon) => cursor_icon,
|
||||
None => {
|
||||
// Hide the cursor.
|
||||
// WlPointer::set_cursor() expects the serial of the last *enter*
|
||||
// event (compare to to start_interactive_move()).
|
||||
(*self.pointer).set_cursor(self.latest_enter_serial.get(), None, 0, 0);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let cursors: &[&str] = match cursor_icon {
|
||||
CursorIcon::Alias => &["link"],
|
||||
CursorIcon::Arrow => &["arrow"],
|
||||
CursorIcon::Cell => &["plus"],
|
||||
CursorIcon::Copy => &["copy"],
|
||||
CursorIcon::Crosshair => &["crosshair"],
|
||||
CursorIcon::Default => &["left_ptr"],
|
||||
CursorIcon::Hand => &["hand2", "hand1"],
|
||||
CursorIcon::Help => &["question_arrow"],
|
||||
CursorIcon::Move => &["move"],
|
||||
CursorIcon::Grab => &["openhand", "grab"],
|
||||
CursorIcon::Grabbing => &["closedhand", "grabbing"],
|
||||
CursorIcon::Progress => &["progress"],
|
||||
CursorIcon::AllScroll => &["all-scroll"],
|
||||
CursorIcon::ContextMenu => &["context-menu"],
|
||||
|
||||
CursorIcon::NoDrop => &["no-drop", "circle"],
|
||||
CursorIcon::NotAllowed => &["crossed_circle"],
|
||||
|
||||
// Resize cursors
|
||||
CursorIcon::EResize => &["right_side"],
|
||||
CursorIcon::NResize => &["top_side"],
|
||||
CursorIcon::NeResize => &["top_right_corner"],
|
||||
CursorIcon::NwResize => &["top_left_corner"],
|
||||
CursorIcon::SResize => &["bottom_side"],
|
||||
CursorIcon::SeResize => &["bottom_right_corner"],
|
||||
CursorIcon::SwResize => &["bottom_left_corner"],
|
||||
CursorIcon::WResize => &["left_side"],
|
||||
CursorIcon::EwResize => &["h_double_arrow"],
|
||||
CursorIcon::NsResize => &["v_double_arrow"],
|
||||
CursorIcon::NwseResize => &["bd_double_arrow", "size_fdiag"],
|
||||
CursorIcon::NeswResize => &["fd_double_arrow", "size_bdiag"],
|
||||
CursorIcon::ColResize => &["split_h", "h_double_arrow"],
|
||||
CursorIcon::RowResize => &["split_v", "v_double_arrow"],
|
||||
CursorIcon::Text => &["text", "xterm"],
|
||||
CursorIcon::VerticalText => &["vertical-text"],
|
||||
|
||||
CursorIcon::Wait => &["watch"],
|
||||
|
||||
CursorIcon::ZoomIn => &["zoom-in"],
|
||||
CursorIcon::ZoomOut => &["zoom-out"],
|
||||
};
|
||||
|
||||
let serial = Some(self.latest_enter_serial.get());
|
||||
for cursor in cursors {
|
||||
if self.pointer.set_cursor(cursor, serial).is_ok() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
warn!("Failed to set cursor to {:?}", cursor_icon);
|
||||
}
|
||||
|
||||
/// Confine the pointer to a surface.
|
||||
pub fn confine(&self, surface: &WlSurface) {
|
||||
let pointer_constraints = match &self.pointer_constraints {
|
||||
Some(pointer_constraints) => pointer_constraints,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let confined_pointer = match self.confined_pointer.upgrade() {
|
||||
Some(confined_pointer) => confined_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
*confined_pointer.borrow_mut() = Some(init_confined_pointer(
|
||||
pointer_constraints,
|
||||
surface,
|
||||
&*self.pointer,
|
||||
));
|
||||
}
|
||||
|
||||
/// Tries to unconfine the pointer if the current pointer is confined.
|
||||
pub fn unconfine(&self) {
|
||||
let confined_pointer = match self.confined_pointer.upgrade() {
|
||||
Some(confined_pointer) => confined_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut confined_pointer = confined_pointer.borrow_mut();
|
||||
|
||||
if let Some(confined_pointer) = confined_pointer.take() {
|
||||
confined_pointer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lock(&self, surface: &WlSurface) {
|
||||
let pointer_constraints = match &self.pointer_constraints {
|
||||
Some(pointer_constraints) => pointer_constraints,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
||||
Some(locked_pointer) => locked_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
*locked_pointer.borrow_mut() = Some(init_locked_pointer(
|
||||
pointer_constraints,
|
||||
surface,
|
||||
&*self.pointer,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn unlock(&self) {
|
||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
||||
Some(locked_pointer) => locked_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut locked_pointer = locked_pointer.borrow_mut();
|
||||
|
||||
if let Some(locked_pointer) = locked_pointer.take() {
|
||||
locked_pointer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_position(&self, surface_x: u32, surface_y: u32) {
|
||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
||||
Some(locked_pointer) => locked_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
let locked_pointer = locked_pointer.borrow_mut();
|
||||
if let Some(locked_pointer) = locked_pointer.as_ref() {
|
||||
locked_pointer.set_cursor_position_hint(surface_x.into(), surface_y.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drag_window(&self, window: &Window<WinitFrame>) {
|
||||
// WlPointer::setart_interactive_move() expects the last serial of *any*
|
||||
// pointer event (compare to set_cursor()).
|
||||
window.start_interactive_move(&self.seat, self.latest_serial.get());
|
||||
}
|
||||
}
|
||||
|
||||
/// A pointer wrapper for easy releasing and managing pointers.
|
||||
pub(super) struct Pointers {
|
||||
/// A pointer itself.
|
||||
pointer: ThemedPointer,
|
||||
|
||||
/// A relative pointer handler.
|
||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
||||
|
||||
/// Confined pointer.
|
||||
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
|
||||
/// Locked pointer.
|
||||
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
}
|
||||
|
||||
impl Pointers {
|
||||
pub(super) fn new(
|
||||
seat: &Attached<WlSeat>,
|
||||
theme_manager: &ThemeManager,
|
||||
relative_pointer_manager: &Option<Attached<ZwpRelativePointerManagerV1>>,
|
||||
pointer_constraints: &Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Self {
|
||||
let confined_pointer = Rc::new(RefCell::new(None));
|
||||
let locked_pointer = Rc::new(RefCell::new(None));
|
||||
|
||||
let pointer_data = Rc::new(RefCell::new(PointerData::new(
|
||||
confined_pointer.clone(),
|
||||
locked_pointer.clone(),
|
||||
pointer_constraints.clone(),
|
||||
modifiers_state,
|
||||
)));
|
||||
|
||||
let pointer_seat = seat.detach();
|
||||
let pointer = theme_manager.theme_pointer_with_impl(
|
||||
seat,
|
||||
move |event, pointer, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_pointer(
|
||||
pointer,
|
||||
event,
|
||||
&pointer_data,
|
||||
winit_state,
|
||||
pointer_seat.clone(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Setup relative_pointer if it's available.
|
||||
let relative_pointer = relative_pointer_manager
|
||||
.as_ref()
|
||||
.map(|relative_pointer_manager| {
|
||||
init_relative_pointer(relative_pointer_manager, &*pointer)
|
||||
});
|
||||
|
||||
Self {
|
||||
pointer,
|
||||
relative_pointer,
|
||||
confined_pointer,
|
||||
locked_pointer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Pointers {
|
||||
fn drop(&mut self) {
|
||||
// Drop relative pointer.
|
||||
if let Some(relative_pointer) = self.relative_pointer.take() {
|
||||
relative_pointer.destroy();
|
||||
}
|
||||
|
||||
// Drop confined pointer.
|
||||
if let Some(confined_pointer) = self.confined_pointer.borrow_mut().take() {
|
||||
confined_pointer.destroy();
|
||||
}
|
||||
|
||||
// Drop lock ponter.
|
||||
if let Some(locked_pointer) = self.locked_pointer.borrow_mut().take() {
|
||||
locked_pointer.destroy();
|
||||
}
|
||||
|
||||
// Drop the pointer itself in case it's possible.
|
||||
if self.pointer.as_ref().version() >= 3 {
|
||||
self.pointer.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn init_relative_pointer(
|
||||
relative_pointer_manager: &ZwpRelativePointerManagerV1,
|
||||
pointer: &WlPointer,
|
||||
) -> ZwpRelativePointerV1 {
|
||||
let relative_pointer = relative_pointer_manager.get_relative_pointer(pointer);
|
||||
relative_pointer.quick_assign(move |_, event, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_relative_pointer(event, winit_state);
|
||||
});
|
||||
|
||||
relative_pointer.detach()
|
||||
}
|
||||
|
||||
pub(super) fn init_confined_pointer(
|
||||
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
) -> ZwpConfinedPointerV1 {
|
||||
let confined_pointer =
|
||||
pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent);
|
||||
|
||||
confined_pointer.quick_assign(move |_, _, _| {});
|
||||
|
||||
confined_pointer.detach()
|
||||
}
|
||||
|
||||
pub(super) fn init_locked_pointer(
|
||||
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
) -> ZwpLockedPointerV1 {
|
||||
let locked_pointer =
|
||||
pointer_constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent);
|
||||
|
||||
locked_pointer.quick_assign(move |_, _, _| {});
|
||||
|
||||
locked_pointer.detach()
|
||||
}
|
||||
121
src/platform_impl/linux/wayland/seat/text_input/handlers.rs
Normal file
121
src/platform_impl/linux/wayland/seat/text_input/handlers.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
//! Handling of IME events.
|
||||
|
||||
use sctk::reexports::client::Main;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::{
|
||||
Event as TextInputEvent, ZwpTextInputV3,
|
||||
};
|
||||
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
use crate::platform_impl::wayland;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
|
||||
use super::{Preedit, TextInputHandler, TextInputInner};
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_text_input(
|
||||
text_input: Main<ZwpTextInputV3>,
|
||||
inner: &mut TextInputInner,
|
||||
event: TextInputEvent,
|
||||
winit_state: &mut WinitState,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
match event {
|
||||
TextInputEvent::Enter { surface } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
inner.target_window_id = Some(window_id);
|
||||
|
||||
// Enable text input on that surface.
|
||||
if window_handle.ime_allowed.get() {
|
||||
text_input.enable();
|
||||
text_input.commit();
|
||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
|
||||
}
|
||||
|
||||
// Notify a window we're currently over about text input handler.
|
||||
let text_input_handler = TextInputHandler {
|
||||
text_input: text_input.detach(),
|
||||
};
|
||||
window_handle.text_input_entered(text_input_handler);
|
||||
}
|
||||
TextInputEvent::Leave { surface } => {
|
||||
// Always issue a disable.
|
||||
text_input.disable();
|
||||
text_input.commit();
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
inner.target_window_id = None;
|
||||
|
||||
// Remove text input handler from the window we're leaving.
|
||||
let text_input_handler = TextInputHandler {
|
||||
text_input: text_input.detach(),
|
||||
};
|
||||
window_handle.text_input_left(text_input_handler);
|
||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
|
||||
}
|
||||
TextInputEvent::PreeditString {
|
||||
text,
|
||||
cursor_begin,
|
||||
cursor_end,
|
||||
} => {
|
||||
let text = text.unwrap_or_default();
|
||||
let cursor_begin = usize::try_from(cursor_begin)
|
||||
.ok()
|
||||
.and_then(|idx| text.is_char_boundary(idx).then(|| idx));
|
||||
let cursor_end = usize::try_from(cursor_end)
|
||||
.ok()
|
||||
.and_then(|idx| text.is_char_boundary(idx).then(|| idx));
|
||||
|
||||
inner.pending_preedit = Some(Preedit {
|
||||
text,
|
||||
cursor_begin,
|
||||
cursor_end,
|
||||
});
|
||||
}
|
||||
TextInputEvent::CommitString { text } => {
|
||||
// Update currenly commited string and reset previous preedit.
|
||||
inner.pending_preedit = None;
|
||||
inner.pending_commit = Some(text.unwrap_or_default());
|
||||
}
|
||||
TextInputEvent::Done { .. } => {
|
||||
let window_id = match inner.target_window_id {
|
||||
Some(window_id) => window_id,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// Clear preedit at the start of `Done`.
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
window_id,
|
||||
);
|
||||
|
||||
// Send `Commit`.
|
||||
if let Some(text) = inner.pending_commit.take() {
|
||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
|
||||
}
|
||||
|
||||
// Send preedit.
|
||||
if let Some(preedit) = inner.pending_preedit.take() {
|
||||
let cursor_range = preedit
|
||||
.cursor_begin
|
||||
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
87
src/platform_impl/linux/wayland/seat/text_input/mod.rs
Normal file
87
src/platform_impl/linux/wayland/seat/text_input/mod.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::ZwpTextInputV3;
|
||||
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
|
||||
mod handlers;
|
||||
|
||||
/// A handler for text input that we're advertising for `WindowHandle`.
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct TextInputHandler {
|
||||
text_input: ZwpTextInputV3,
|
||||
}
|
||||
|
||||
impl TextInputHandler {
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, x: i32, y: i32) {
|
||||
self.text_input.set_cursor_rectangle(x, y, 0, 0);
|
||||
self.text_input.commit();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_input_allowed(&self, allowed: bool) {
|
||||
if allowed {
|
||||
self.text_input.enable();
|
||||
} else {
|
||||
self.text_input.disable();
|
||||
}
|
||||
|
||||
self.text_input.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around text input to automatically destroy the object on `Drop`.
|
||||
pub struct TextInput {
|
||||
text_input: Attached<ZwpTextInputV3>,
|
||||
}
|
||||
|
||||
impl TextInput {
|
||||
pub fn new(seat: &Attached<WlSeat>, text_input_manager: &ZwpTextInputManagerV3) -> Self {
|
||||
let text_input = text_input_manager.get_text_input(seat);
|
||||
let mut text_input_inner = TextInputInner::new();
|
||||
text_input.quick_assign(move |text_input, event, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_text_input(text_input, &mut text_input_inner, event, winit_state);
|
||||
});
|
||||
|
||||
let text_input: Attached<ZwpTextInputV3> = text_input.into();
|
||||
|
||||
Self { text_input }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TextInput {
|
||||
fn drop(&mut self) {
|
||||
self.text_input.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
struct TextInputInner {
|
||||
/// Currently focused surface.
|
||||
target_window_id: Option<WindowId>,
|
||||
|
||||
/// Pending commit event which will be dispatched on `text_input_v3::Done`.
|
||||
pending_commit: Option<String>,
|
||||
|
||||
/// Pending preedit event which will be dispatched on `text_input_v3::Done`.
|
||||
pending_preedit: Option<Preedit>,
|
||||
}
|
||||
|
||||
struct Preedit {
|
||||
text: String,
|
||||
cursor_begin: Option<usize>,
|
||||
cursor_end: Option<usize>,
|
||||
}
|
||||
|
||||
impl TextInputInner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
target_window_id: None,
|
||||
pending_commit: None,
|
||||
pending_preedit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/platform_impl/linux/wayland/seat/touch/handlers.rs
Normal file
128
src/platform_impl/linux/wayland/seat/touch/handlers.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
//! Various handlers for touch events.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_touch::Event as TouchEvent;
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{TouchPhase, WindowEvent};
|
||||
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId};
|
||||
|
||||
use super::{TouchInner, TouchPoint};
|
||||
|
||||
/// Handle WlTouch events.
|
||||
#[inline]
|
||||
pub(super) fn handle_touch(
|
||||
event: TouchEvent,
|
||||
inner: &mut TouchInner,
|
||||
winit_state: &mut WinitState,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
|
||||
match event {
|
||||
TouchEvent::Down {
|
||||
surface, id, x, y, ..
|
||||
} => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
if !winit_state.window_map.contains_key(&window_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
|
||||
let position = LogicalPosition::new(x, y);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Started,
|
||||
location: position.to_physical(scale_factor),
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
|
||||
// For `TouchEvent::Up` we don't receive a position, so we're tracking active
|
||||
// touch points. Update either a known touch id or register a new one.
|
||||
if let Some(i) = inner.touch_points.iter().position(|p| p.id == id) {
|
||||
inner.touch_points[i].position = position;
|
||||
} else {
|
||||
inner
|
||||
.touch_points
|
||||
.push(TouchPoint::new(surface, position, id));
|
||||
}
|
||||
}
|
||||
TouchEvent::Up { id, .. } => {
|
||||
let touch_point = match inner.touch_points.iter().find(|p| p.id == id) {
|
||||
Some(touch_point) => touch_point,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
|
||||
let location = touch_point.position.to_physical(scale_factor);
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Ended,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
TouchEvent::Motion { id, x, y, .. } => {
|
||||
let touch_point = match inner.touch_points.iter_mut().find(|p| p.id == id) {
|
||||
Some(touch_point) => touch_point,
|
||||
None => return,
|
||||
};
|
||||
|
||||
touch_point.position = LogicalPosition::new(x, y);
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
|
||||
let location = touch_point.position.to_physical(scale_factor);
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Moved,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
TouchEvent::Frame => (),
|
||||
TouchEvent::Cancel => {
|
||||
for touch_point in inner.touch_points.drain(..) {
|
||||
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
|
||||
let location = touch_point.position.to_physical(scale_factor);
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: touch_point.id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
78
src/platform_impl/linux/wayland/seat/touch/mod.rs
Normal file
78
src/platform_impl/linux/wayland/seat/touch/mod.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
//! Touch handling.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::protocol::wl_touch::WlTouch;
|
||||
use sctk::reexports::client::Attached;
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
|
||||
mod handlers;
|
||||
|
||||
/// Wrapper around touch to handle release.
|
||||
pub struct Touch {
|
||||
/// Proxy to touch.
|
||||
touch: WlTouch,
|
||||
}
|
||||
|
||||
impl Touch {
|
||||
pub fn new(seat: &Attached<WlSeat>) -> Self {
|
||||
let touch = seat.get_touch();
|
||||
let mut inner = TouchInner::new();
|
||||
|
||||
touch.quick_assign(move |_, event, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_touch(event, &mut inner, winit_state);
|
||||
});
|
||||
|
||||
Self {
|
||||
touch: touch.detach(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Touch {
|
||||
fn drop(&mut self) {
|
||||
if self.touch.as_ref().version() >= 3 {
|
||||
self.touch.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The data used by touch handlers.
|
||||
pub(super) struct TouchInner {
|
||||
/// Current touch points.
|
||||
touch_points: Vec<TouchPoint>,
|
||||
}
|
||||
|
||||
impl TouchInner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
touch_points: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Location of touch press.
|
||||
pub(super) struct TouchPoint {
|
||||
/// A surface where the touch point is located.
|
||||
surface: WlSurface,
|
||||
|
||||
/// Location of the touch point.
|
||||
position: LogicalPosition<f64>,
|
||||
|
||||
/// Id.
|
||||
id: i32,
|
||||
}
|
||||
|
||||
impl TouchPoint {
|
||||
pub fn new(surface: WlSurface, position: LogicalPosition<f64>, id: i32) -> Self {
|
||||
Self {
|
||||
surface,
|
||||
position,
|
||||
id,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::event::{TouchPhase, WindowEvent};
|
||||
|
||||
use super::{event_loop::WindowEventsSink, window::WindowStore, DeviceId, WindowId};
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::{
|
||||
wl_seat,
|
||||
wl_touch::{Event as TouchEvent, WlTouch},
|
||||
};
|
||||
|
||||
struct TouchPoint {
|
||||
wid: WindowId,
|
||||
location: (f64, f64),
|
||||
id: i32,
|
||||
}
|
||||
|
||||
pub(crate) fn implement_touch<T: 'static>(
|
||||
seat: &wl_seat::WlSeat,
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
) -> WlTouch {
|
||||
let mut pending_ids = Vec::new();
|
||||
seat.get_touch(|touch| {
|
||||
touch.implement_closure(
|
||||
move |evt, _| {
|
||||
let mut sink = sink.lock().unwrap();
|
||||
let store = store.lock().unwrap();
|
||||
match evt {
|
||||
TouchEvent::Down {
|
||||
surface, id, x, y, ..
|
||||
} => {
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Started,
|
||||
location: (x, y).into(),
|
||||
id: id as u64,
|
||||
}),
|
||||
wid,
|
||||
);
|
||||
pending_ids.push(TouchPoint {
|
||||
wid,
|
||||
location: (x, y),
|
||||
id,
|
||||
});
|
||||
}
|
||||
}
|
||||
TouchEvent::Up { id, .. } => {
|
||||
let idx = pending_ids.iter().position(|p| p.id == id);
|
||||
if let Some(idx) = idx {
|
||||
let pt = pending_ids.remove(idx);
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Ended,
|
||||
location: pt.location.into(),
|
||||
id: id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
TouchEvent::Motion { id, x, y, .. } => {
|
||||
let pt = pending_ids.iter_mut().find(|p| p.id == id);
|
||||
if let Some(pt) = pt {
|
||||
pt.location = (x, y);
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Moved,
|
||||
location: (x, y).into(),
|
||||
id: id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
TouchEvent::Frame => (),
|
||||
TouchEvent::Cancel => {
|
||||
for pt in pending_ids.drain(..) {
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location: pt.location.into(),
|
||||
id: pt.id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, Mutex, Weak},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::{LogicalPosition, LogicalSize},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
monitor::MonitorHandle as RootMonitorHandle,
|
||||
platform_impl::{
|
||||
MonitorHandle as PlatformMonitorHandle,
|
||||
PlatformSpecificWindowBuilderAttributes as PlAttributes,
|
||||
},
|
||||
window::{CursorIcon, WindowAttributes},
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::{
|
||||
output::OutputMgr,
|
||||
reexports::client::{
|
||||
protocol::{wl_seat, wl_surface},
|
||||
Display,
|
||||
},
|
||||
surface::{get_dpi_factor, get_outputs},
|
||||
window::{ConceptFrame, Event as WEvent, State as WState, Theme, Window as SWindow},
|
||||
};
|
||||
|
||||
use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
|
||||
use crate::platform_impl::platform::wayland::event_loop::{available_monitors, primary_monitor};
|
||||
|
||||
pub struct Window {
|
||||
surface: wl_surface::WlSurface,
|
||||
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
|
||||
outputs: OutputMgr, // Access to info for all monitors
|
||||
size: Arc<Mutex<(u32, u32)>>,
|
||||
kill_switch: (Arc<Mutex<bool>>, Arc<Mutex<bool>>),
|
||||
display: Arc<Display>,
|
||||
need_frame_refresh: Arc<Mutex<bool>>,
|
||||
need_refresh: Arc<Mutex<bool>>,
|
||||
fullscreen: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new<T>(
|
||||
evlp: &EventLoopWindowTarget<T>,
|
||||
attributes: WindowAttributes,
|
||||
pl_attribs: PlAttributes,
|
||||
) -> Result<Window, RootOsError> {
|
||||
let (width, height) = attributes.inner_size.map(Into::into).unwrap_or((800, 600));
|
||||
// Create the window
|
||||
let size = Arc::new(Mutex::new((width, height)));
|
||||
let fullscreen = Arc::new(Mutex::new(false));
|
||||
|
||||
let window_store = evlp.store.clone();
|
||||
let surface = evlp.env.create_surface(move |dpi, surface| {
|
||||
window_store.lock().unwrap().dpi_change(&surface, dpi);
|
||||
surface.set_buffer_scale(dpi);
|
||||
});
|
||||
|
||||
let window_store = evlp.store.clone();
|
||||
let my_surface = surface.clone();
|
||||
let mut frame = SWindow::<ConceptFrame>::init_from_env(
|
||||
&evlp.env,
|
||||
surface.clone(),
|
||||
(width, height),
|
||||
move |event| match event {
|
||||
WEvent::Configure { new_size, states } => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
let is_fullscreen = states.contains(&WState::Fullscreen);
|
||||
|
||||
for window in &mut store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.newsize = new_size;
|
||||
*(window.need_refresh.lock().unwrap()) = true;
|
||||
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
WEvent::Refresh => {
|
||||
let store = window_store.lock().unwrap();
|
||||
for window in &store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
WEvent::Close => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
for window in &mut store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.closed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(app_id) = pl_attribs.app_id {
|
||||
frame.set_app_id(app_id);
|
||||
}
|
||||
|
||||
for &(_, ref seat) in evlp.seats.lock().unwrap().iter() {
|
||||
frame.new_seat(seat);
|
||||
}
|
||||
|
||||
// Check for fullscreen requirements
|
||||
if let Some(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||
}) = attributes.fullscreen
|
||||
{
|
||||
frame.set_fullscreen(Some(&monitor_id.proxy));
|
||||
} else if attributes.maximized {
|
||||
frame.set_maximized();
|
||||
}
|
||||
|
||||
frame.set_resizable(attributes.resizable);
|
||||
|
||||
// set decorations
|
||||
frame.set_decorate(attributes.decorations);
|
||||
|
||||
// set title
|
||||
frame.set_title(attributes.title);
|
||||
|
||||
// min-max dimensions
|
||||
frame.set_min_size(attributes.min_inner_size.map(Into::into));
|
||||
frame.set_max_size(attributes.max_inner_size.map(Into::into));
|
||||
|
||||
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 need_refresh = Arc::new(Mutex::new(true));
|
||||
|
||||
evlp.store.lock().unwrap().windows.push(InternalWindow {
|
||||
closed: false,
|
||||
newsize: None,
|
||||
size: size.clone(),
|
||||
need_refresh: need_refresh.clone(),
|
||||
fullscreen: fullscreen.clone(),
|
||||
need_frame_refresh: need_frame_refresh.clone(),
|
||||
surface: surface.clone(),
|
||||
kill_switch: kill_switch.clone(),
|
||||
frame: Arc::downgrade(&frame),
|
||||
current_dpi: 1,
|
||||
new_dpi: None,
|
||||
});
|
||||
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
|
||||
|
||||
Ok(Window {
|
||||
display: evlp.display.clone(),
|
||||
surface,
|
||||
frame,
|
||||
outputs: evlp.env.outputs.clone(),
|
||||
size,
|
||||
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
|
||||
need_frame_refresh,
|
||||
need_refresh,
|
||||
fullscreen,
|
||||
})
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, _visible: bool) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_outer_position(&self, _pos: LogicalPosition) {
|
||||
// Not possible with wayland
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> LogicalSize {
|
||||
self.size.lock().unwrap().clone().into()
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
*self.need_refresh.lock().unwrap() = true;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> LogicalSize {
|
||||
let (w, h) = self.size.lock().unwrap().clone();
|
||||
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
|
||||
(w, h).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
// NOTE: This will only resize the borders, the contents must be updated by the user
|
||||
pub fn set_inner_size(&self, size: LogicalSize) {
|
||||
let (w, h) = size.into();
|
||||
self.frame.lock().unwrap().resize(w, h);
|
||||
*(self.size.lock().unwrap()) = (w, h);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_inner_size(&self, dimensions: Option<LogicalSize>) {
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_min_size(dimensions.map(Into::into));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_inner_size(&self, dimensions: Option<LogicalSize>) {
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_max_size(dimensions.map(Into::into));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
self.frame.lock().unwrap().set_resizable(resizable);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> i32 {
|
||||
get_dpi_factor(&self.surface)
|
||||
}
|
||||
|
||||
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_maximized();
|
||||
} else {
|
||||
self.frame.lock().unwrap().unset_maximized();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fullscreen(&self) -> Option<MonitorHandle> {
|
||||
if *(self.fullscreen.lock().unwrap()) {
|
||||
Some(self.current_monitor())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
||||
if let Some(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||
}) = monitor
|
||||
{
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_fullscreen(Some(&monitor_id.proxy));
|
||||
} else {
|
||||
self.frame.lock().unwrap().unset_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_theme<T: Theme>(&self, theme: T) {
|
||||
self.frame.lock().unwrap().set_theme(theme)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, _visible: bool) {
|
||||
// TODO: This isn't possible on Wayland yet
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn display(&self) -> &Display {
|
||||
&*self.display
|
||||
}
|
||||
|
||||
pub fn surface(&self) -> &wl_surface::WlSurface {
|
||||
&self.surface
|
||||
}
|
||||
|
||||
pub fn current_monitor(&self) -> MonitorHandle {
|
||||
let output = get_outputs(&self.surface).last().unwrap().clone();
|
||||
MonitorHandle {
|
||||
proxy: output,
|
||||
mgr: self.outputs.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
available_monitors(&self.outputs)
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
primary_monitor(&self.outputs)
|
||||
}
|
||||
}
|
||||
|
||||
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<(u32, u32)>,
|
||||
size: Arc<Mutex<(u32, u32)>>,
|
||||
need_refresh: Arc<Mutex<bool>>,
|
||||
fullscreen: Arc<Mutex<bool>>,
|
||||
need_frame_refresh: Arc<Mutex<bool>>,
|
||||
closed: bool,
|
||||
kill_switch: Arc<Mutex<bool>>,
|
||||
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
|
||||
current_dpi: i32,
|
||||
new_dpi: Option<i32>,
|
||||
}
|
||||
|
||||
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.as_ref().equals(&window.surface.as_ref()) {
|
||||
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 new_seat(&self, seat: &wl_seat::WlSeat) {
|
||||
for window in &self.windows {
|
||||
if let Some(w) = window.frame.upgrade() {
|
||||
w.lock().unwrap().new_seat(seat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dpi_change(&mut self, surface: &wl_surface::WlSurface, new: i32) {
|
||||
for window in &mut self.windows {
|
||||
if surface.as_ref().equals(&window.surface.as_ref()) {
|
||||
window.new_dpi = Some(new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_each<F>(&mut self, mut f: F)
|
||||
where
|
||||
F: FnMut(
|
||||
Option<(u32, u32)>,
|
||||
&mut (u32, u32),
|
||||
Option<i32>,
|
||||
bool,
|
||||
bool,
|
||||
bool,
|
||||
WindowId,
|
||||
Option<&mut SWindow<ConceptFrame>>,
|
||||
),
|
||||
{
|
||||
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(),
|
||||
&mut *(window.size.lock().unwrap()),
|
||||
window.new_dpi,
|
||||
::std::mem::replace(&mut *window.need_refresh.lock().unwrap(), false),
|
||||
::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),
|
||||
);
|
||||
if let Some(dpi) = window.new_dpi.take() {
|
||||
window.current_dpi = dpi;
|
||||
}
|
||||
// avoid re-spamming the event
|
||||
window.closed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
638
src/platform_impl/linux/wayland/window/mod.rs
Normal file
638
src/platform_impl/linux/wayland/window/mod.rs
Normal file
@@ -0,0 +1,638 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Display;
|
||||
|
||||
use sctk::reexports::calloop;
|
||||
|
||||
use raw_window_handle::{
|
||||
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
|
||||
};
|
||||
use sctk::window::Decorations;
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
||||
use crate::monitor::MonitorHandle as RootMonitorHandle;
|
||||
use crate::platform_impl::{
|
||||
MonitorHandle as PlatformMonitorHandle, OsError,
|
||||
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
|
||||
};
|
||||
use crate::window::{
|
||||
CursorGrabMode, CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes,
|
||||
};
|
||||
|
||||
use super::env::WindowingFeatures;
|
||||
use super::event_loop::WinitState;
|
||||
use super::output::{MonitorHandle, OutputManagerHandle};
|
||||
use super::{EventLoopWindowTarget, WindowId};
|
||||
|
||||
pub mod shim;
|
||||
|
||||
use shim::{WindowCompositorUpdate, WindowHandle, WindowRequest, WindowUserRequest};
|
||||
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
pub type WinitFrame = sctk_adwaita::AdwaitaFrame;
|
||||
#[cfg(not(feature = "sctk-adwaita"))]
|
||||
pub type WinitFrame = sctk::window::FallbackFrame;
|
||||
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
const WAYLAND_CSD_THEME_ENV_VAR: &str = "WINIT_WAYLAND_CSD_THEME";
|
||||
|
||||
pub struct Window {
|
||||
/// Window id.
|
||||
window_id: WindowId,
|
||||
|
||||
/// The Wayland display.
|
||||
display: Display,
|
||||
|
||||
/// The underlying wl_surface.
|
||||
surface: WlSurface,
|
||||
|
||||
/// The current window size.
|
||||
size: Arc<Mutex<LogicalSize<u32>>>,
|
||||
|
||||
/// A handle to output manager.
|
||||
output_manager_handle: OutputManagerHandle,
|
||||
|
||||
/// Event loop proxy to wake it up.
|
||||
event_loop_awakener: calloop::ping::Ping,
|
||||
|
||||
/// Fullscreen state.
|
||||
fullscreen: Arc<AtomicBool>,
|
||||
|
||||
/// Maximized state.
|
||||
maximized: Arc<AtomicBool>,
|
||||
|
||||
/// Available windowing features.
|
||||
windowing_features: WindowingFeatures,
|
||||
|
||||
/// Requests that SCTK window should perform.
|
||||
window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
||||
|
||||
/// Whether the window is resizeable.
|
||||
resizeable: AtomicBool,
|
||||
|
||||
/// Whether the window is decorated.
|
||||
decorated: AtomicBool,
|
||||
|
||||
/// Grabbing mode.
|
||||
cursor_grab_mode: Mutex<CursorGrabMode>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub(crate) fn new<T>(
|
||||
event_loop_window_target: &EventLoopWindowTarget<T>,
|
||||
attributes: WindowAttributes,
|
||||
platform_attributes: PlatformAttributes,
|
||||
) -> Result<Self, RootOsError> {
|
||||
let surface = event_loop_window_target
|
||||
.env
|
||||
.create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
|
||||
// Get the window that received the event.
|
||||
let window_id = super::make_wid(&surface);
|
||||
let mut window_compositor_update = winit_state
|
||||
.window_compositor_updates
|
||||
.get_mut(&window_id)
|
||||
.unwrap();
|
||||
|
||||
// Mark that we need a frame refresh on the DPI change.
|
||||
winit_state
|
||||
.window_user_requests
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.refresh_frame = true;
|
||||
|
||||
// Set pending scale factor.
|
||||
window_compositor_update.scale_factor = Some(scale);
|
||||
|
||||
surface.set_buffer_scale(scale);
|
||||
})
|
||||
.detach();
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&surface);
|
||||
|
||||
let window_id = super::make_wid(&surface);
|
||||
let maximized = Arc::new(AtomicBool::new(false));
|
||||
let maximized_clone = maximized.clone();
|
||||
let fullscreen = Arc::new(AtomicBool::new(false));
|
||||
let fullscreen_clone = fullscreen.clone();
|
||||
|
||||
let (width, height) = attributes
|
||||
.inner_size
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into())
|
||||
.unwrap_or((800, 600));
|
||||
|
||||
let theme_manager = event_loop_window_target.theme_manager.clone();
|
||||
let mut window = event_loop_window_target
|
||||
.env
|
||||
.create_window::<WinitFrame, _>(
|
||||
surface.clone(),
|
||||
Some(theme_manager),
|
||||
(width, height),
|
||||
move |event, mut dispatch_data| {
|
||||
use sctk::window::{Event, State};
|
||||
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
let mut window_compositor_update = winit_state
|
||||
.window_compositor_updates
|
||||
.get_mut(&window_id)
|
||||
.unwrap();
|
||||
|
||||
let mut window_user_requests = winit_state
|
||||
.window_user_requests
|
||||
.get_mut(&window_id)
|
||||
.unwrap();
|
||||
|
||||
match event {
|
||||
Event::Refresh => {
|
||||
window_user_requests.refresh_frame = true;
|
||||
}
|
||||
Event::Configure { new_size, states } => {
|
||||
let is_maximized = states.contains(&State::Maximized);
|
||||
maximized_clone.store(is_maximized, Ordering::Relaxed);
|
||||
let is_fullscreen = states.contains(&State::Fullscreen);
|
||||
fullscreen_clone.store(is_fullscreen, Ordering::Relaxed);
|
||||
|
||||
window_user_requests.refresh_frame = true;
|
||||
if let Some((w, h)) = new_size {
|
||||
window_compositor_update.size = Some(LogicalSize::new(w, h));
|
||||
}
|
||||
}
|
||||
Event::Close => {
|
||||
window_compositor_update.close_window = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?;
|
||||
|
||||
// Set CSD frame config
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
{
|
||||
let theme = platform_attributes.csd_theme.unwrap_or_else(|| {
|
||||
let env = std::env::var(WAYLAND_CSD_THEME_ENV_VAR).unwrap_or_default();
|
||||
match env.to_lowercase().as_str() {
|
||||
"dark" => Theme::Dark,
|
||||
_ => Theme::Light,
|
||||
}
|
||||
});
|
||||
|
||||
window.set_frame_config(theme.into());
|
||||
}
|
||||
|
||||
// Set decorations.
|
||||
if attributes.decorations {
|
||||
window.set_decorate(Decorations::FollowServer);
|
||||
} else {
|
||||
window.set_decorate(Decorations::None);
|
||||
}
|
||||
|
||||
// Min dimensions.
|
||||
let min_size = attributes
|
||||
.min_inner_size
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
|
||||
window.set_min_size(min_size);
|
||||
|
||||
// Max dimensions.
|
||||
let max_size = attributes
|
||||
.max_inner_size
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
|
||||
window.set_max_size(max_size);
|
||||
|
||||
// Set Wayland specific window attributes.
|
||||
if let Some(name) = platform_attributes.name {
|
||||
window.set_app_id(name.general);
|
||||
}
|
||||
|
||||
// Set common window attributes.
|
||||
//
|
||||
// We set resizable after other attributes, since it touches min and max size under
|
||||
// the hood.
|
||||
window.set_resizable(attributes.resizable);
|
||||
window.set_title(attributes.title);
|
||||
|
||||
// Set fullscreen/maximized if so was requested.
|
||||
match attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland")
|
||||
}
|
||||
Some(Fullscreen::Borderless(monitor)) => {
|
||||
let monitor =
|
||||
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
|
||||
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
|
||||
#[cfg(feature = "x11")]
|
||||
PlatformMonitorHandle::X(_) => None,
|
||||
});
|
||||
|
||||
window.set_fullscreen(monitor.as_ref());
|
||||
}
|
||||
None => {
|
||||
if attributes.maximized {
|
||||
window.set_maximized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Without this commit here at least on kwin 5.23.3 the initial configure
|
||||
// will have a size (1,1), the second configure including the decoration
|
||||
// mode will have the min_size as its size. With this commit the initial
|
||||
// configure will have no size, the application will draw it's content
|
||||
// with the initial size and everything works as expected afterwards.
|
||||
//
|
||||
// The window commit must be after setting on top level properties, but right before any
|
||||
// buffer attachments commits.
|
||||
window.surface().commit();
|
||||
|
||||
let size = Arc::new(Mutex::new(LogicalSize::new(width, height)));
|
||||
|
||||
// We should trigger redraw and commit the surface for the newly created window.
|
||||
let mut window_user_request = WindowUserRequest::new();
|
||||
window_user_request.refresh_frame = true;
|
||||
window_user_request.redraw_requested = true;
|
||||
|
||||
let window_id = super::make_wid(&surface);
|
||||
let window_requests = Arc::new(Mutex::new(Vec::with_capacity(64)));
|
||||
|
||||
// Create a handle that performs all the requests on underlying sctk a window.
|
||||
let window_handle = WindowHandle::new(
|
||||
&event_loop_window_target.env,
|
||||
window,
|
||||
size.clone(),
|
||||
window_requests.clone(),
|
||||
);
|
||||
|
||||
// Set resizable state, so we can determine how to handle `Window::set_inner_size`.
|
||||
window_handle.is_resizable.set(attributes.resizable);
|
||||
|
||||
let mut winit_state = event_loop_window_target.state.borrow_mut();
|
||||
|
||||
winit_state.window_map.insert(window_id, window_handle);
|
||||
|
||||
// On Wayland window doesn't have Focus by default and it'll get it later on. So be
|
||||
// explicit here.
|
||||
winit_state
|
||||
.event_sink
|
||||
.push_window_event(crate::event::WindowEvent::Focused(false), window_id);
|
||||
|
||||
// Add state for the window.
|
||||
winit_state
|
||||
.window_user_requests
|
||||
.insert(window_id, window_user_request);
|
||||
winit_state
|
||||
.window_compositor_updates
|
||||
.insert(window_id, WindowCompositorUpdate::new());
|
||||
|
||||
let windowing_features = event_loop_window_target.windowing_features;
|
||||
|
||||
// To make our window usable for drawing right away we must `ack` a `configure`
|
||||
// from the server, the acking part here is done by SCTK window frame, so we just
|
||||
// need to sync with server so it'll be done automatically for us.
|
||||
{
|
||||
let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut();
|
||||
let event_queue = wayland_source.queue();
|
||||
let _ = event_queue.sync_roundtrip(&mut *winit_state, |_, _, _| unreachable!());
|
||||
}
|
||||
|
||||
// We all praise GNOME for these 3 lines of pure magic. If we don't do that,
|
||||
// GNOME will shrink our window a bit for the size of the decorations. I guess it
|
||||
// happens because we haven't committed them with buffers to the server.
|
||||
let window_handle = winit_state.window_map.get_mut(&window_id).unwrap();
|
||||
window_handle.window.refresh();
|
||||
|
||||
let output_manager_handle = event_loop_window_target.output_manager.handle();
|
||||
|
||||
let window = Self {
|
||||
window_id,
|
||||
surface,
|
||||
display: event_loop_window_target.display.clone(),
|
||||
output_manager_handle,
|
||||
size,
|
||||
window_requests,
|
||||
event_loop_awakener: event_loop_window_target.event_loop_awakener.clone(),
|
||||
fullscreen,
|
||||
maximized,
|
||||
windowing_features,
|
||||
resizeable: AtomicBool::new(attributes.resizable),
|
||||
decorated: AtomicBool::new(attributes.decorations),
|
||||
cursor_grab_mode: Mutex::new(CursorGrabMode::None),
|
||||
};
|
||||
|
||||
Ok(window)
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
self.window_id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, title: &str) {
|
||||
self.send_request(WindowRequest::Title(title.to_owned()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, _visible: bool) {
|
||||
// Not possible on Wayland.
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_outer_position(&self, _: Position) {
|
||||
// Not possible on Wayland.
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
self.size
|
||||
.lock()
|
||||
.unwrap()
|
||||
.to_physical(self.scale_factor() as f64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_redraw(&self) {
|
||||
self.send_request(WindowRequest::Redraw);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
self.size
|
||||
.lock()
|
||||
.unwrap()
|
||||
.to_physical(self.scale_factor() as f64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
|
||||
let size = size.to_logical::<u32>(scale_factor);
|
||||
*self.size.lock().unwrap() = size;
|
||||
|
||||
self.send_request(WindowRequest::FrameSize(size));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
|
||||
|
||||
self.send_request(WindowRequest::MinSize(size));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
|
||||
|
||||
self.send_request(WindowRequest::MaxSize(size));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
self.resizeable.store(resizable, Ordering::Relaxed);
|
||||
self.send_request(WindowRequest::Resizeable(resizable));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_resizable(&self) -> bool {
|
||||
self.resizeable.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> u32 {
|
||||
// The scale factor from `get_surface_scale_factor` is always greater than zero, so
|
||||
// u32 conversion is safe.
|
||||
sctk::get_surface_scale_factor(&self.surface) as u32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorate: bool) {
|
||||
self.decorated.store(decorate, Ordering::Relaxed);
|
||||
self.send_request(WindowRequest::Decorate(decorate));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
self.decorated.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_csd_theme(&self, theme: Theme) {
|
||||
self.send_request(WindowRequest::CsdThemeVariant(theme));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
// You can't unminimize the window on Wayland.
|
||||
if !minimized {
|
||||
return;
|
||||
}
|
||||
|
||||
self.send_request(WindowRequest::Minimize);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
self.maximized.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
self.send_request(WindowRequest::Maximize(maximized));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
if self.fullscreen.load(Ordering::Relaxed) {
|
||||
let current_monitor = self.current_monitor().map(|monitor| RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(monitor),
|
||||
});
|
||||
|
||||
Some(Fullscreen::Borderless(current_monitor))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
let fullscreen_request = match fullscreen {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
|
||||
return;
|
||||
}
|
||||
Some(Fullscreen::Borderless(monitor)) => {
|
||||
let monitor =
|
||||
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
|
||||
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
|
||||
#[cfg(feature = "x11")]
|
||||
PlatformMonitorHandle::X(_) => None,
|
||||
});
|
||||
|
||||
WindowRequest::Fullscreen(monitor)
|
||||
}
|
||||
None => WindowRequest::UnsetFullscreen,
|
||||
};
|
||||
|
||||
self.send_request(fullscreen_request);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
self.send_request(WindowRequest::NewCursorIcon(cursor));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
self.send_request(WindowRequest::ShowCursor(visible));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
if !self.windowing_features.pointer_constraints() {
|
||||
if mode == CursorGrabMode::None {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Err(ExternalError::NotSupported(NotSupportedError::new()));
|
||||
}
|
||||
|
||||
*self.cursor_grab_mode.lock().unwrap() = mode;
|
||||
self.send_request(WindowRequest::SetCursorGrabMode(mode));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
if !self.windowing_features.xdg_activation() {
|
||||
warn!("`request_user_attention` isn't supported");
|
||||
return;
|
||||
}
|
||||
|
||||
self.send_request(WindowRequest::Attention(request_type));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
|
||||
// Positon can be set only for locked cursor.
|
||||
if *self.cursor_grab_mode.lock().unwrap() != CursorGrabMode::Locked {
|
||||
return Err(ExternalError::Os(os_error!(OsError::WaylandMisc(
|
||||
"cursor position can be set only for locked cursor."
|
||||
))));
|
||||
}
|
||||
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let position = position.to_logical(scale_factor);
|
||||
self.send_request(WindowRequest::SetLockedCursorPosition(position));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
self.send_request(WindowRequest::DragWindow);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
||||
self.send_request(WindowRequest::PassthroughMouseInput(!hittest));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, position: Position) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let position = position.to_logical(scale_factor);
|
||||
self.send_request(WindowRequest::ImePosition(position));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
||||
self.send_request(WindowRequest::AllowIme(allowed));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn display(&self) -> &Display {
|
||||
&self.display
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn surface(&self) -> &WlSurface {
|
||||
&self.surface
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||
let output = sctk::get_surface_outputs(&self.surface).last()?.clone();
|
||||
Some(MonitorHandle::new(output))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
self.output_manager_handle.available_outputs()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let mut window_handle = WaylandWindowHandle::empty();
|
||||
window_handle.surface = self.surface.as_ref().c_ptr() as *mut _;
|
||||
RawWindowHandle::Wayland(window_handle)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
let mut display_handle = WaylandDisplayHandle::empty();
|
||||
display_handle.display = self.display.get_display_ptr() as *mut _;
|
||||
RawDisplayHandle::Wayland(display_handle)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn send_request(&self, request: WindowRequest) {
|
||||
self.window_requests.lock().unwrap().push(request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
self.send_request(WindowRequest::Close);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
impl From<Theme> for sctk_adwaita::FrameConfig {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => sctk_adwaita::FrameConfig::light(),
|
||||
Theme::Dark => sctk_adwaita::FrameConfig::dark(),
|
||||
}
|
||||
}
|
||||
}
|
||||
561
src/platform_impl/linux/wayland/window/shim.rs
Normal file
561
src/platform_impl/linux/wayland/window/shim.rs
Normal file
@@ -0,0 +1,561 @@
|
||||
use std::cell::Cell;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1;
|
||||
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
|
||||
|
||||
use sctk::environment::Environment;
|
||||
use sctk::window::{Decorations, Window};
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
use crate::platform_impl::wayland;
|
||||
use crate::platform_impl::wayland::env::WinitEnv;
|
||||
use crate::platform_impl::wayland::event_loop::{EventSink, WinitState};
|
||||
use crate::platform_impl::wayland::seat::pointer::WinitPointer;
|
||||
use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
use crate::window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType};
|
||||
|
||||
use super::WinitFrame;
|
||||
|
||||
/// A request to SCTK window from Winit window.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum WindowRequest {
|
||||
/// Set fullscreen.
|
||||
///
|
||||
/// Passing `None` will set it on the current monitor.
|
||||
Fullscreen(Option<WlOutput>),
|
||||
|
||||
/// Unset fullscreen.
|
||||
UnsetFullscreen,
|
||||
|
||||
/// Show cursor for the certain window or not.
|
||||
ShowCursor(bool),
|
||||
|
||||
/// Change the cursor icon.
|
||||
NewCursorIcon(CursorIcon),
|
||||
|
||||
/// Change cursor grabbing mode.
|
||||
SetCursorGrabMode(CursorGrabMode),
|
||||
|
||||
/// Set cursor position.
|
||||
SetLockedCursorPosition(LogicalPosition<u32>),
|
||||
|
||||
/// Drag window.
|
||||
DragWindow,
|
||||
|
||||
/// Maximize the window.
|
||||
Maximize(bool),
|
||||
|
||||
/// Minimize the window.
|
||||
Minimize,
|
||||
|
||||
/// Request decorations change.
|
||||
Decorate(bool),
|
||||
|
||||
/// Request decorations change.
|
||||
CsdThemeVariant(Theme),
|
||||
|
||||
/// Make the window resizeable.
|
||||
Resizeable(bool),
|
||||
|
||||
/// Set the title for window.
|
||||
Title(String),
|
||||
|
||||
/// Min size.
|
||||
MinSize(Option<LogicalSize<u32>>),
|
||||
|
||||
/// Max size.
|
||||
MaxSize(Option<LogicalSize<u32>>),
|
||||
|
||||
/// New frame size.
|
||||
FrameSize(LogicalSize<u32>),
|
||||
|
||||
/// Set IME window position.
|
||||
ImePosition(LogicalPosition<u32>),
|
||||
|
||||
/// Enable IME on the given window.
|
||||
AllowIme(bool),
|
||||
|
||||
/// Request Attention.
|
||||
///
|
||||
/// `None` unsets the attention request.
|
||||
Attention(Option<UserAttentionType>),
|
||||
|
||||
/// Passthrough mouse input to underlying windows.
|
||||
PassthroughMouseInput(bool),
|
||||
|
||||
/// Redraw was requested.
|
||||
Redraw,
|
||||
|
||||
/// Window should be closed.
|
||||
Close,
|
||||
}
|
||||
|
||||
// The window update comming from the compositor.
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct WindowCompositorUpdate {
|
||||
/// New window size.
|
||||
pub size: Option<LogicalSize<u32>>,
|
||||
|
||||
/// New scale factor.
|
||||
pub scale_factor: Option<i32>,
|
||||
|
||||
/// Close the window.
|
||||
pub close_window: bool,
|
||||
}
|
||||
|
||||
impl WindowCompositorUpdate {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Pending update to a window requested by the user.
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct WindowUserRequest {
|
||||
/// Whether `redraw` was requested.
|
||||
pub redraw_requested: bool,
|
||||
|
||||
/// Wether the frame should be refreshed.
|
||||
pub refresh_frame: bool,
|
||||
}
|
||||
|
||||
impl WindowUserRequest {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to perform operations on SCTK window
|
||||
/// and react to events.
|
||||
pub struct WindowHandle {
|
||||
/// An actual window.
|
||||
pub window: ManuallyDrop<Window<WinitFrame>>,
|
||||
|
||||
/// The current size of the window.
|
||||
pub size: Arc<Mutex<LogicalSize<u32>>>,
|
||||
|
||||
/// A pending requests to SCTK window.
|
||||
pub pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
||||
|
||||
/// Current cursor icon.
|
||||
pub cursor_icon: Cell<CursorIcon>,
|
||||
|
||||
/// Whether the window is resizable.
|
||||
pub is_resizable: Cell<bool>,
|
||||
|
||||
/// Allow IME events for that window.
|
||||
pub ime_allowed: Cell<bool>,
|
||||
|
||||
/// Visible cursor or not.
|
||||
cursor_visible: Cell<bool>,
|
||||
|
||||
/// Cursor confined to the surface.
|
||||
cursor_grab_mode: Cell<CursorGrabMode>,
|
||||
|
||||
/// Pointers over the current surface.
|
||||
pointers: Vec<WinitPointer>,
|
||||
|
||||
/// Text inputs on the current surface.
|
||||
text_inputs: Vec<TextInputHandler>,
|
||||
|
||||
/// XdgActivation object.
|
||||
xdg_activation: Option<Attached<XdgActivationV1>>,
|
||||
|
||||
/// Indicator whether user attention is requested.
|
||||
attention_requested: Cell<bool>,
|
||||
|
||||
/// Compositor
|
||||
compositor: Attached<WlCompositor>,
|
||||
}
|
||||
|
||||
impl WindowHandle {
|
||||
pub fn new(
|
||||
env: &Environment<WinitEnv>,
|
||||
window: Window<WinitFrame>,
|
||||
size: Arc<Mutex<LogicalSize<u32>>>,
|
||||
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
||||
) -> Self {
|
||||
let xdg_activation = env.get_global::<XdgActivationV1>();
|
||||
// Unwrap is safe, since we can't create window without compositor anyway and won't be
|
||||
// here.
|
||||
let compositor = env.get_global::<WlCompositor>().unwrap();
|
||||
|
||||
Self {
|
||||
window: ManuallyDrop::new(window),
|
||||
size,
|
||||
pending_window_requests,
|
||||
cursor_icon: Cell::new(CursorIcon::Default),
|
||||
is_resizable: Cell::new(true),
|
||||
cursor_grab_mode: Cell::new(CursorGrabMode::None),
|
||||
cursor_visible: Cell::new(true),
|
||||
pointers: Vec::new(),
|
||||
text_inputs: Vec::new(),
|
||||
xdg_activation,
|
||||
attention_requested: Cell::new(false),
|
||||
compositor,
|
||||
ime_allowed: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) {
|
||||
// The new requested state matches the current confine status, return.
|
||||
let old_mode = self.cursor_grab_mode.replace(mode);
|
||||
if old_mode == mode {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear old pointer data.
|
||||
match old_mode {
|
||||
CursorGrabMode::None => (),
|
||||
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.unconfine()),
|
||||
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.unlock()),
|
||||
}
|
||||
|
||||
let surface = self.window.surface();
|
||||
match mode {
|
||||
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.lock(surface)),
|
||||
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.confine(surface)),
|
||||
CursorGrabMode::None => {
|
||||
// Current lock/confine was already removed.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_locked_cursor_position(&self, position: LogicalPosition<u32>) {
|
||||
// XXX the cursor locking is ensured inside `Window`.
|
||||
self.pointers
|
||||
.iter()
|
||||
.for_each(|p| p.set_cursor_position(position.x, position.y));
|
||||
}
|
||||
|
||||
pub fn set_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let xdg_activation = match self.xdg_activation.as_ref() {
|
||||
None => return,
|
||||
Some(xdg_activation) => xdg_activation,
|
||||
};
|
||||
|
||||
// Urgency is only removed by the compositor and there's no need to raise urgency when it
|
||||
// was already raised.
|
||||
if request_type.is_none() || self.attention_requested.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
let xdg_activation_token = xdg_activation.get_activation_token();
|
||||
let surface = self.window.surface();
|
||||
let window_id = wayland::make_wid(surface);
|
||||
let xdg_activation = xdg_activation.clone();
|
||||
|
||||
xdg_activation_token.quick_assign(move |xdg_token, event, mut dispatch_data| {
|
||||
let token = match event {
|
||||
xdg_activation_token_v1::Event::Done { token } => token,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let surface = window_handle.window.surface();
|
||||
xdg_activation.activate(token, surface);
|
||||
|
||||
// Mark that attention request was done and drop the token.
|
||||
window_handle.attention_requested.replace(false);
|
||||
xdg_token.destroy();
|
||||
});
|
||||
|
||||
xdg_activation_token.set_surface(surface);
|
||||
xdg_activation_token.commit();
|
||||
self.attention_requested.replace(true);
|
||||
}
|
||||
|
||||
/// Pointer appeared over the window.
|
||||
pub fn pointer_entered(&mut self, pointer: WinitPointer) {
|
||||
let position = self.pointers.iter().position(|p| *p == pointer);
|
||||
|
||||
if position.is_none() {
|
||||
let surface = self.window.surface();
|
||||
match self.cursor_grab_mode.get() {
|
||||
CursorGrabMode::None => (),
|
||||
CursorGrabMode::Locked => pointer.lock(surface),
|
||||
CursorGrabMode::Confined => pointer.confine(surface),
|
||||
}
|
||||
|
||||
self.pointers.push(pointer);
|
||||
}
|
||||
|
||||
// Apply the current cursor style.
|
||||
self.set_cursor_visible(self.cursor_visible.get());
|
||||
}
|
||||
|
||||
/// Pointer left the window.
|
||||
pub fn pointer_left(&mut self, pointer: WinitPointer) {
|
||||
let position = self.pointers.iter().position(|p| *p == pointer);
|
||||
|
||||
if let Some(position) = position {
|
||||
let pointer = self.pointers.remove(position);
|
||||
|
||||
// Drop the grabbing mode.
|
||||
match self.cursor_grab_mode.get() {
|
||||
CursorGrabMode::None => (),
|
||||
CursorGrabMode::Locked => pointer.unlock(),
|
||||
CursorGrabMode::Confined => pointer.unconfine(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_input_entered(&mut self, text_input: TextInputHandler) {
|
||||
if !self.text_inputs.iter().any(|t| *t == text_input) {
|
||||
self.text_inputs.push(text_input);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_input_left(&mut self, text_input: TextInputHandler) {
|
||||
if let Some(position) = self.text_inputs.iter().position(|t| *t == text_input) {
|
||||
self.text_inputs.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_position(&self, position: LogicalPosition<u32>) {
|
||||
// XXX This won't fly unless user will have a way to request IME window per seat, since
|
||||
// the ime windows will be overlapping, but winit doesn't expose API to specify for
|
||||
// which seat we're setting IME position.
|
||||
let (x, y) = (position.x as i32, position.y as i32);
|
||||
for text_input in self.text_inputs.iter() {
|
||||
text_input.set_ime_position(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn passthrough_mouse_input(&self, passthrough_mouse_input: bool) {
|
||||
if passthrough_mouse_input {
|
||||
let region = self.compositor.create_region();
|
||||
region.add(0, 0, 0, 0);
|
||||
self.window
|
||||
.surface()
|
||||
.set_input_region(Some(®ion.detach()));
|
||||
region.destroy();
|
||||
} else {
|
||||
// Using `None` results in the entire window being clickable.
|
||||
self.window.surface().set_input_region(None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&self, allowed: bool, event_sink: &mut EventSink) {
|
||||
if self.ime_allowed.get() == allowed {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ime_allowed.replace(allowed);
|
||||
let window_id = wayland::make_wid(self.window.surface());
|
||||
|
||||
for text_input in self.text_inputs.iter() {
|
||||
text_input.set_input_allowed(allowed);
|
||||
}
|
||||
|
||||
let event = if allowed {
|
||||
WindowEvent::Ime(Ime::Enabled)
|
||||
} else {
|
||||
WindowEvent::Ime(Ime::Disabled)
|
||||
};
|
||||
|
||||
event_sink.push_window_event(event, window_id);
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
self.cursor_visible.replace(visible);
|
||||
let cursor_icon = match visible {
|
||||
true => Some(self.cursor_icon.get()),
|
||||
false => None,
|
||||
};
|
||||
|
||||
for pointer in self.pointers.iter() {
|
||||
pointer.set_cursor(cursor_icon)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_icon(&self, cursor_icon: CursorIcon) {
|
||||
self.cursor_icon.replace(cursor_icon);
|
||||
|
||||
if !self.cursor_visible.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
for pointer in self.pointers.iter() {
|
||||
pointer.set_cursor(Some(cursor_icon));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drag_window(&self) {
|
||||
for pointer in self.pointers.iter() {
|
||||
pointer.drag_window(&self.window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn handle_window_requests(winit_state: &mut WinitState) {
|
||||
let window_map = &mut winit_state.window_map;
|
||||
let window_user_requests = &mut winit_state.window_user_requests;
|
||||
let window_compositor_updates = &mut winit_state.window_compositor_updates;
|
||||
let mut windows_to_close: Vec<WindowId> = Vec::new();
|
||||
|
||||
// Process the rest of the events.
|
||||
for (window_id, window_handle) in window_map.iter_mut() {
|
||||
let mut requests = window_handle.pending_window_requests.lock().unwrap();
|
||||
let requests = requests.drain(..);
|
||||
for request in requests {
|
||||
match request {
|
||||
WindowRequest::Fullscreen(fullscreen) => {
|
||||
window_handle.window.set_fullscreen(fullscreen.as_ref());
|
||||
}
|
||||
WindowRequest::UnsetFullscreen => {
|
||||
window_handle.window.unset_fullscreen();
|
||||
}
|
||||
WindowRequest::ShowCursor(show_cursor) => {
|
||||
window_handle.set_cursor_visible(show_cursor);
|
||||
}
|
||||
WindowRequest::NewCursorIcon(cursor_icon) => {
|
||||
window_handle.set_cursor_icon(cursor_icon);
|
||||
}
|
||||
WindowRequest::ImePosition(position) => {
|
||||
window_handle.set_ime_position(position);
|
||||
}
|
||||
WindowRequest::AllowIme(allow) => {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
window_handle.set_ime_allowed(allow, event_sink);
|
||||
}
|
||||
WindowRequest::SetCursorGrabMode(mode) => {
|
||||
window_handle.set_cursor_grab(mode);
|
||||
}
|
||||
WindowRequest::SetLockedCursorPosition(position) => {
|
||||
window_handle.set_locked_cursor_position(position);
|
||||
}
|
||||
WindowRequest::DragWindow => {
|
||||
window_handle.drag_window();
|
||||
}
|
||||
WindowRequest::Maximize(maximize) => {
|
||||
if maximize {
|
||||
window_handle.window.set_maximized();
|
||||
} else {
|
||||
window_handle.window.unset_maximized();
|
||||
}
|
||||
}
|
||||
WindowRequest::Minimize => {
|
||||
window_handle.window.set_minimized();
|
||||
}
|
||||
WindowRequest::Decorate(decorate) => {
|
||||
let decorations = match decorate {
|
||||
true => Decorations::FollowServer,
|
||||
false => Decorations::None,
|
||||
};
|
||||
|
||||
window_handle.window.set_decorate(decorations);
|
||||
|
||||
// We should refresh the frame to apply decorations change.
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
WindowRequest::CsdThemeVariant(theme) => {
|
||||
window_handle.window.set_frame_config(theme.into());
|
||||
|
||||
let window_requst = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_requst.refresh_frame = true;
|
||||
}
|
||||
#[cfg(not(feature = "sctk-adwaita"))]
|
||||
WindowRequest::CsdThemeVariant(_) => {}
|
||||
WindowRequest::Resizeable(resizeable) => {
|
||||
window_handle.window.set_resizable(resizeable);
|
||||
|
||||
// We should refresh the frame to update button state.
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::Title(title) => {
|
||||
window_handle.window.set_title(title);
|
||||
|
||||
// We should refresh the frame to draw new title.
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::MinSize(size) => {
|
||||
let size = size.map(|size| (size.width, size.height));
|
||||
window_handle.window.set_min_size(size);
|
||||
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::MaxSize(size) => {
|
||||
let size = size.map(|size| (size.width, size.height));
|
||||
window_handle.window.set_max_size(size);
|
||||
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::FrameSize(size) => {
|
||||
if !window_handle.is_resizable.get() {
|
||||
// On Wayland non-resizable window is achieved by setting both min and max
|
||||
// size of the window to the same value.
|
||||
let size = Some((size.width, size.height));
|
||||
window_handle.window.set_max_size(size);
|
||||
window_handle.window.set_min_size(size);
|
||||
}
|
||||
|
||||
window_handle.window.resize(size.width, size.height);
|
||||
|
||||
// We should refresh the frame after resize.
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::PassthroughMouseInput(passthrough) => {
|
||||
window_handle.passthrough_mouse_input(passthrough);
|
||||
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::Attention(request_type) => {
|
||||
window_handle.set_user_attention(request_type);
|
||||
}
|
||||
WindowRequest::Redraw => {
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.redraw_requested = true;
|
||||
}
|
||||
WindowRequest::Close => {
|
||||
// The window was requested to be closed.
|
||||
windows_to_close.push(*window_id);
|
||||
|
||||
// Send event that the window was destroyed.
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
event_sink.push_window_event(WindowEvent::Destroyed, *window_id);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Close the windows.
|
||||
for window in windows_to_close {
|
||||
let _ = window_map.remove(&window);
|
||||
let _ = window_user_requests.remove(&window);
|
||||
let _ = window_compositor_updates.remove(&window);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowHandle {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let surface = self.window.surface().clone();
|
||||
// The window must be destroyed before wl_surface.
|
||||
ManuallyDrop::drop(&mut self.window);
|
||||
surface.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
//ffi::XK_Linefeed => VirtualKeyCode::Linefeed,
|
||||
//ffi::XK_Clear => VirtualKeyCode::Clear,
|
||||
ffi::XK_Return => VirtualKeyCode::Return,
|
||||
//ffi::XK_Pause => VirtualKeyCode::Pause,
|
||||
ffi::XK_Pause => VirtualKeyCode::Pause,
|
||||
//ffi::XK_Scroll_Lock => VirtualKeyCode::Scroll_lock,
|
||||
//ffi::XK_Sys_Req => VirtualKeyCode::Sys_req,
|
||||
ffi::XK_Escape => VirtualKeyCode::Escape,
|
||||
@@ -59,10 +59,10 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
//ffi::XK_Break => VirtualKeyCode::Break,
|
||||
//ffi::XK_Mode_switch => VirtualKeyCode::Mode_switch,
|
||||
//ffi::XK_script_switch => VirtualKeyCode::Script_switch,
|
||||
//ffi::XK_Num_Lock => VirtualKeyCode::Num_lock,
|
||||
ffi::XK_Num_Lock => VirtualKeyCode::Numlock,
|
||||
//ffi::XK_KP_Space => VirtualKeyCode::Kp_space,
|
||||
//ffi::XK_KP_Tab => VirtualKeyCode::Kp_tab,
|
||||
//ffi::XK_KP_Enter => VirtualKeyCode::Kp_enter,
|
||||
ffi::XK_KP_Enter => VirtualKeyCode::NumpadEnter,
|
||||
//ffi::XK_KP_F1 => VirtualKeyCode::Kp_f1,
|
||||
//ffi::XK_KP_F2 => VirtualKeyCode::Kp_f2,
|
||||
//ffi::XK_KP_F3 => VirtualKeyCode::Kp_f3,
|
||||
@@ -81,12 +81,12 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
ffi::XK_KP_Insert => VirtualKeyCode::Insert,
|
||||
ffi::XK_KP_Delete => VirtualKeyCode::Delete,
|
||||
ffi::XK_KP_Equal => VirtualKeyCode::NumpadEquals,
|
||||
//ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply,
|
||||
ffi::XK_KP_Add => VirtualKeyCode::Add,
|
||||
//ffi::XK_KP_Separator => VirtualKeyCode::Kp_separator,
|
||||
ffi::XK_KP_Subtract => VirtualKeyCode::Subtract,
|
||||
//ffi::XK_KP_Decimal => VirtualKeyCode::Kp_decimal,
|
||||
ffi::XK_KP_Divide => VirtualKeyCode::Divide,
|
||||
ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply,
|
||||
ffi::XK_KP_Add => VirtualKeyCode::NumpadAdd,
|
||||
ffi::XK_KP_Separator => VirtualKeyCode::NumpadComma,
|
||||
ffi::XK_KP_Subtract => VirtualKeyCode::NumpadSubtract,
|
||||
ffi::XK_KP_Decimal => VirtualKeyCode::NumpadDecimal,
|
||||
ffi::XK_KP_Divide => VirtualKeyCode::NumpadDivide,
|
||||
ffi::XK_KP_0 => VirtualKeyCode::Numpad0,
|
||||
ffi::XK_KP_1 => VirtualKeyCode::Numpad1,
|
||||
ffi::XK_KP_2 => VirtualKeyCode::Numpad2,
|
||||
@@ -161,7 +161,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
ffi::XK_Shift_R => VirtualKeyCode::RShift,
|
||||
ffi::XK_Control_L => VirtualKeyCode::LControl,
|
||||
ffi::XK_Control_R => VirtualKeyCode::RControl,
|
||||
//ffi::XK_Caps_Lock => VirtualKeyCode::Caps_lock,
|
||||
ffi::XK_Caps_Lock => VirtualKeyCode::Capital,
|
||||
//ffi::XK_Shift_Lock => VirtualKeyCode::Shift_lock,
|
||||
//ffi::XK_Meta_L => VirtualKeyCode::Meta_l,
|
||||
//ffi::XK_Meta_R => VirtualKeyCode::Meta_r,
|
||||
@@ -183,10 +183,10 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
//ffi::XK_quoteright => VirtualKeyCode::Quoteright,
|
||||
//ffi::XK_parenleft => VirtualKeyCode::Parenleft,
|
||||
//ffi::XK_parenright => VirtualKeyCode::Parenright,
|
||||
//ffi::XK_asterisk => VirtualKeyCode::Asterisk,
|
||||
ffi::XK_plus => VirtualKeyCode::Add,
|
||||
ffi::XK_asterisk => VirtualKeyCode::Asterisk,
|
||||
ffi::XK_plus => VirtualKeyCode::Plus,
|
||||
ffi::XK_comma => VirtualKeyCode::Comma,
|
||||
ffi::XK_minus => VirtualKeyCode::Subtract,
|
||||
ffi::XK_minus => VirtualKeyCode::Minus,
|
||||
ffi::XK_period => VirtualKeyCode::Period,
|
||||
ffi::XK_slash => VirtualKeyCode::Slash,
|
||||
ffi::XK_0 => VirtualKeyCode::Key0,
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use x11_dl::xmd::CARD32;
|
||||
pub use x11_dl::{
|
||||
error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*,
|
||||
xrandr::*, xrender::*,
|
||||
};
|
||||
|
||||
// Isn't defined by x11_dl
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const IconicState: CARD32 = 3;
|
||||
|
||||
@@ -62,7 +62,7 @@ pub unsafe fn set_destroy_callback(
|
||||
inner: &ImeInner,
|
||||
) -> Result<(), XError> {
|
||||
xim_set_callback(
|
||||
&xconn,
|
||||
xconn,
|
||||
im,
|
||||
ffi::XNDestroyCallback_0.as_ptr() as *const _,
|
||||
&inner.destroy_callback as *const _ as *mut _,
|
||||
@@ -70,6 +70,7 @@ pub unsafe fn set_destroy_callback(
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum ReplaceImError {
|
||||
MethodOpenFailed(PotentialInputMethods),
|
||||
ContextCreationFailed(ImeContextCreationError),
|
||||
@@ -107,8 +108,30 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
||||
let mut new_contexts = HashMap::new();
|
||||
for (window, old_context) in (*inner).contexts.iter() {
|
||||
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
|
||||
|
||||
// Check if the IME was allowed on that context.
|
||||
let is_allowed = old_context
|
||||
.as_ref()
|
||||
.map(|old_context| old_context.is_allowed())
|
||||
.unwrap_or_default();
|
||||
|
||||
// We can't use the style from the old context here, since it may change on reload, so
|
||||
// pick style from the new XIM based on the old state.
|
||||
let style = if is_allowed {
|
||||
new_im.preedit_style
|
||||
} else {
|
||||
new_im.none_style
|
||||
};
|
||||
|
||||
let new_context = {
|
||||
let result = ImeContext::new(xconn, new_im.im, *window, spot);
|
||||
let result = ImeContext::new(
|
||||
xconn,
|
||||
new_im.im,
|
||||
style,
|
||||
*window,
|
||||
spot,
|
||||
(*inner).event_sender.clone(),
|
||||
);
|
||||
if result.is_err() {
|
||||
let _ = close_im(xconn, new_im.im);
|
||||
}
|
||||
@@ -120,7 +143,7 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
||||
// If we've made it this far, everything succeeded.
|
||||
let _ = (*inner).destroy_all_contexts_if_necessary();
|
||||
let _ = (*inner).close_im_if_necessary();
|
||||
(*inner).im = new_im.im;
|
||||
(*inner).im = Some(new_im);
|
||||
(*inner).contexts = new_contexts;
|
||||
(*inner).is_destroyed = false;
|
||||
(*inner).is_fallback = is_fallback;
|
||||
@@ -136,13 +159,17 @@ pub unsafe extern "C" fn xim_instantiate_callback(
|
||||
let inner: *mut ImeInner = client_data as _;
|
||||
if !inner.is_null() {
|
||||
let xconn = &(*inner).xconn;
|
||||
let result = replace_im(inner);
|
||||
if result.is_ok() {
|
||||
let _ = unset_instantiate_callback(xconn, client_data);
|
||||
(*inner).is_fallback = false;
|
||||
} else if result.is_err() && (*inner).is_destroyed {
|
||||
// We have no usable input methods!
|
||||
result.expect("Failed to reopen input method");
|
||||
match replace_im(inner) {
|
||||
Ok(()) => {
|
||||
let _ = unset_instantiate_callback(xconn, client_data);
|
||||
(*inner).is_fallback = false;
|
||||
}
|
||||
Err(err) => {
|
||||
if (*inner).is_destroyed {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to reopen input method: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,12 +190,12 @@ pub unsafe extern "C" fn xim_destroy_callback(
|
||||
if !(*inner).is_fallback {
|
||||
let _ = set_instantiate_callback(xconn, client_data);
|
||||
// Attempt to open fallback input method.
|
||||
let result = replace_im(inner);
|
||||
if result.is_ok() {
|
||||
(*inner).is_fallback = true;
|
||||
} else {
|
||||
// We have no usable input methods!
|
||||
result.expect("Failed to open fallback input method");
|
||||
match replace_im(inner) {
|
||||
Ok(()) => (*inner).is_fallback = true,
|
||||
Err(err) => {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to open fallback input method: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +1,326 @@
|
||||
use std::{
|
||||
os::raw::{c_short, c_void},
|
||||
ptr,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_short;
|
||||
use std::sync::Arc;
|
||||
use std::{mem, ptr};
|
||||
|
||||
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
|
||||
|
||||
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
/// IME creation error.
|
||||
#[derive(Debug)]
|
||||
pub enum ImeContextCreationError {
|
||||
/// Got the error from Xlib.
|
||||
XError(XError),
|
||||
|
||||
/// Got null pointer from Xlib but without exact reason.
|
||||
Null,
|
||||
}
|
||||
|
||||
unsafe fn create_pre_edit_attr<'a>(
|
||||
xconn: &'a Arc<XConnection>,
|
||||
ic_spot: &'a ffi::XPoint,
|
||||
) -> util::XSmartPointer<'a, c_void> {
|
||||
util::XSmartPointer::new(
|
||||
xconn,
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNSpotLocation_0.as_ptr() as *const _,
|
||||
ic_spot,
|
||||
ptr::null_mut::<()>(),
|
||||
),
|
||||
)
|
||||
.expect("XVaCreateNestedList returned NULL")
|
||||
/// The callback used by XIM preedit functions.
|
||||
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);
|
||||
|
||||
/// Wrapper for creating XIM callbacks.
|
||||
#[inline]
|
||||
fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
|
||||
XIMCallback {
|
||||
client_data,
|
||||
callback: Some(callback),
|
||||
}
|
||||
}
|
||||
|
||||
// WARNING: this struct doesn't destroy its XIC resource when dropped.
|
||||
/// The server started preedit.
|
||||
extern "C" fn preedit_start_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) -> i32 {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
client_data.text.clear();
|
||||
client_data.cursor_pos = 0;
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::Start))
|
||||
.expect("failed to send preedit start event");
|
||||
-1
|
||||
}
|
||||
|
||||
/// Done callback is used when the preedit should be hidden.
|
||||
extern "C" fn preedit_done_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
// Drop text buffer and reset cursor position on done.
|
||||
client_data.text = Vec::new();
|
||||
client_data.cursor_pos = 0;
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::End))
|
||||
.expect("failed to send preedit end event");
|
||||
}
|
||||
|
||||
fn calc_byte_position(text: &[char], pos: usize) -> usize {
|
||||
text.iter()
|
||||
.take(pos)
|
||||
.fold(0, |byte_pos, text| byte_pos + text.len_utf8())
|
||||
}
|
||||
|
||||
/// Preedit text information to be drawn inline by the client.
|
||||
extern "C" fn preedit_draw_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) };
|
||||
client_data.cursor_pos = call_data.caret as usize;
|
||||
|
||||
let chg_range =
|
||||
call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize;
|
||||
if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() {
|
||||
warn!(
|
||||
"invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}",
|
||||
client_data.text.len(),
|
||||
call_data.chg_first,
|
||||
call_data.chg_length
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// NULL indicate text deletion
|
||||
let mut new_chars = if call_data.text.is_null() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let xim_text = unsafe { &mut *(call_data.text) };
|
||||
if xim_text.encoding_is_wchar > 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { xim_text.string.multi_byte };
|
||||
|
||||
if new_text.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { CStr::from_ptr(new_text) };
|
||||
|
||||
String::from(new_text.to_str().expect("Invalid UTF-8 String from IME"))
|
||||
.chars()
|
||||
.collect()
|
||||
};
|
||||
let mut old_text_tail = client_data.text.split_off(chg_range.end);
|
||||
client_data.text.truncate(chg_range.start);
|
||||
client_data.text.append(&mut new_chars);
|
||||
client_data.text.append(&mut old_text_tail);
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
|
||||
/// Handling of cursor movements in preedit text.
|
||||
extern "C" fn preedit_caret_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) };
|
||||
|
||||
if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition {
|
||||
client_data.cursor_pos = call_data.position as usize;
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to simplify callback creation and latter passing into Xlib XIM.
|
||||
struct PreeditCallbacks {
|
||||
start_callback: ffi::XIMCallback,
|
||||
done_callback: ffi::XIMCallback,
|
||||
draw_callback: ffi::XIMCallback,
|
||||
caret_callback: ffi::XIMCallback,
|
||||
}
|
||||
|
||||
impl PreeditCallbacks {
|
||||
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
|
||||
let start_callback = create_xim_callback(client_data, unsafe {
|
||||
mem::transmute(preedit_start_callback as usize)
|
||||
});
|
||||
let done_callback = create_xim_callback(client_data, preedit_done_callback);
|
||||
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
|
||||
let draw_callback = create_xim_callback(client_data, preedit_draw_callback);
|
||||
|
||||
PreeditCallbacks {
|
||||
start_callback,
|
||||
done_callback,
|
||||
caret_callback,
|
||||
draw_callback,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ImeContextClientData {
|
||||
window: ffi::Window,
|
||||
event_sender: ImeEventSender,
|
||||
text: Vec<char>,
|
||||
cursor_pos: usize,
|
||||
}
|
||||
|
||||
// XXX: this struct doesn't destroy its XIC resource when dropped.
|
||||
// This is intentional, as it doesn't have enough information to know whether or not the context
|
||||
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
|
||||
// through `ImeInner`.
|
||||
#[derive(Debug)]
|
||||
pub struct ImeContext {
|
||||
pub ic: ffi::XIC,
|
||||
pub ic_spot: ffi::XPoint,
|
||||
pub(crate) ic: ffi::XIC,
|
||||
pub(crate) ic_spot: ffi::XPoint,
|
||||
pub(crate) style: Style,
|
||||
// Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from
|
||||
// there we keep the pointer to automatically deallocate it.
|
||||
_client_data: Box<ImeContextClientData>,
|
||||
}
|
||||
|
||||
impl ImeContext {
|
||||
pub unsafe fn new(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: Style,
|
||||
window: ffi::Window,
|
||||
ic_spot: Option<ffi::XPoint>,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Result<Self, ImeContextCreationError> {
|
||||
let ic = if let Some(ic_spot) = ic_spot {
|
||||
ImeContext::create_ic_with_spot(xconn, im, window, ic_spot)
|
||||
} else {
|
||||
ImeContext::create_ic(xconn, im, window)
|
||||
};
|
||||
let client_data = Box::into_raw(Box::new(ImeContextClientData {
|
||||
window,
|
||||
event_sender,
|
||||
text: Vec::new(),
|
||||
cursor_pos: 0,
|
||||
}));
|
||||
|
||||
let ic = match style as _ {
|
||||
Style::Preedit(style) => ImeContext::create_preedit_ic(
|
||||
xconn,
|
||||
im,
|
||||
style,
|
||||
window,
|
||||
client_data as ffi::XPointer,
|
||||
),
|
||||
Style::Nothing(style) => ImeContext::create_nothing_ic(xconn, im, style, window),
|
||||
Style::None(style) => ImeContext::create_none_ic(xconn, im, style, window),
|
||||
}
|
||||
.ok_or(ImeContextCreationError::Null)?;
|
||||
|
||||
let ic = ic.ok_or(ImeContextCreationError::Null)?;
|
||||
xconn
|
||||
.check_errors()
|
||||
.map_err(ImeContextCreationError::XError)?;
|
||||
|
||||
Ok(ImeContext {
|
||||
let mut context = ImeContext {
|
||||
ic,
|
||||
ic_spot: ic_spot.unwrap_or_else(|| ffi::XPoint { x: 0, y: 0 }),
|
||||
})
|
||||
ic_spot: ffi::XPoint { x: 0, y: 0 },
|
||||
style,
|
||||
_client_data: Box::from_raw(client_data),
|
||||
};
|
||||
|
||||
// Set the spot location, if it's present.
|
||||
if let Some(ic_spot) = ic_spot {
|
||||
context.set_spot(xconn, ic_spot.x, ic_spot.y)
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
unsafe fn create_ic(
|
||||
unsafe fn create_none_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = (xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
if ic.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(ic)
|
||||
}
|
||||
|
||||
(!ic.is_null()).then(|| ic)
|
||||
}
|
||||
|
||||
unsafe fn create_ic_with_spot(
|
||||
unsafe fn create_preedit_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
ic_spot: ffi::XPoint,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Option<ffi::XIC> {
|
||||
let pre_edit_attr = create_pre_edit_attr(xconn, &ic_spot);
|
||||
let preedit_callbacks = PreeditCallbacks::new(client_data);
|
||||
let preedit_attr = util::XSmartPointer::new(
|
||||
xconn,
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.start_callback) as *const _,
|
||||
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.done_callback) as *const _,
|
||||
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.caret_callback) as *const _,
|
||||
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.draw_callback) as *const _,
|
||||
ptr::null_mut::<()>(),
|
||||
),
|
||||
)
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
let ic = (xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
pre_edit_attr.ptr,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
if ic.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(ic)
|
||||
}
|
||||
|
||||
(!ic.is_null()).then(|| ic)
|
||||
}
|
||||
|
||||
unsafe fn create_nothing_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = (xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
|
||||
(!ic.is_null()).then(|| ic)
|
||||
}
|
||||
|
||||
pub fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||
@@ -120,18 +337,38 @@ impl ImeContext {
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub fn is_allowed(&self) -> bool {
|
||||
!matches!(self.style, Style::None(_))
|
||||
}
|
||||
|
||||
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
|
||||
// are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
|
||||
// window and couldn't be changed.
|
||||
//
|
||||
// For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
|
||||
pub fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
|
||||
if self.ic_spot.x == x && self.ic_spot.y == y {
|
||||
if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ic_spot = ffi::XPoint { x, y };
|
||||
|
||||
unsafe {
|
||||
let pre_edit_attr = create_pre_edit_attr(xconn, &self.ic_spot);
|
||||
let preedit_attr = util::XSmartPointer::new(
|
||||
xconn,
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNSpotLocation_0.as_ptr(),
|
||||
&self.ic_spot,
|
||||
ptr::null_mut::<()>(),
|
||||
),
|
||||
)
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
(xconn.xlib.XSetICValues)(
|
||||
self.ic,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
pre_edit_attr.ptr,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
use std::{collections::HashMap, mem, ptr, sync::Arc};
|
||||
use std::{collections::HashMap, mem, sync::Arc};
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
|
||||
use super::{context::ImeContext, input_method::PotentialInputMethods};
|
||||
use super::{
|
||||
context::ImeContext,
|
||||
input_method::{InputMethod, PotentialInputMethods},
|
||||
};
|
||||
use crate::platform_impl::platform::x11::ime::ImeEventSender;
|
||||
|
||||
pub unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
|
||||
(xconn.xlib.XCloseIM)(im);
|
||||
@@ -16,12 +20,12 @@ pub unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), X
|
||||
|
||||
pub struct ImeInner {
|
||||
pub xconn: Arc<XConnection>,
|
||||
// WARNING: this is initially null!
|
||||
pub im: ffi::XIM,
|
||||
pub im: Option<InputMethod>,
|
||||
pub potential_input_methods: PotentialInputMethods,
|
||||
pub contexts: HashMap<ffi::Window, Option<ImeContext>>,
|
||||
// WARNING: this is initially zeroed!
|
||||
pub destroy_callback: ffi::XIMCallback,
|
||||
pub event_sender: ImeEventSender,
|
||||
// Indicates whether or not the the input method was destroyed on the server end
|
||||
// (i.e. if ibus/fcitx/etc. was terminated/restarted)
|
||||
pub is_destroyed: bool,
|
||||
@@ -29,21 +33,26 @@ pub struct ImeInner {
|
||||
}
|
||||
|
||||
impl ImeInner {
|
||||
pub fn new(xconn: Arc<XConnection>, potential_input_methods: PotentialInputMethods) -> Self {
|
||||
pub fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
potential_input_methods: PotentialInputMethods,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Self {
|
||||
ImeInner {
|
||||
xconn,
|
||||
im: ptr::null_mut(),
|
||||
im: None,
|
||||
potential_input_methods,
|
||||
contexts: HashMap::new(),
|
||||
destroy_callback: unsafe { mem::zeroed() },
|
||||
event_sender,
|
||||
is_destroyed: false,
|
||||
is_fallback: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
|
||||
if !self.is_destroyed {
|
||||
close_im(&self.xconn, self.im).map(|_| true)
|
||||
if !self.is_destroyed && self.im.is_some() {
|
||||
close_im(&self.xconn, self.im.as_ref().unwrap().im).map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
@@ -58,10 +67,8 @@ impl ImeInner {
|
||||
}
|
||||
|
||||
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
|
||||
for context in self.contexts.values() {
|
||||
if let &Some(ref context) = context {
|
||||
self.destroy_ic_if_necessary(context.ic)?;
|
||||
}
|
||||
for context in self.contexts.values().flatten() {
|
||||
self.destroy_ic_if_necessary(context.ic)?;
|
||||
}
|
||||
Ok(!self.is_destroyed)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user