mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
290 Commits
v0.19.2
...
v0.20.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5291c9e28 | ||
|
|
35505a3114 | ||
|
|
830d47a5f7 | ||
|
|
1a514dff38 | ||
|
|
2888d5c6cf | ||
|
|
400f75a2b3 | ||
|
|
07bdd3e218 | ||
|
|
35a11ae24f | ||
|
|
3d28283a81 | ||
|
|
aec5a9fa09 | ||
|
|
0f94f62025 | ||
|
|
a95ebc5ee6 | ||
|
|
a70ac1531e | ||
|
|
b6e8dd0d8a | ||
|
|
af80ce842d | ||
|
|
08bae037f0 | ||
|
|
cd39327ea2 | ||
|
|
9828f368d6 | ||
|
|
1ed15c7ec7 | ||
|
|
c66784995d | ||
|
|
dba21c06ed | ||
|
|
72fc6a74ec | ||
|
|
f916311744 | ||
|
|
05a1f4280c | ||
|
|
6608a0241d | ||
|
|
429bbfade0 | ||
|
|
28e3c35547 | ||
|
|
af3ef52252 | ||
|
|
2b5f9c52a6 | ||
|
|
5631cc2528 | ||
|
|
de33a92a1b | ||
|
|
1c6353aa3a | ||
|
|
08cb950226 | ||
|
|
765225d918 | ||
|
|
35bc65f6fa | ||
|
|
676268d461 | ||
|
|
34dce8069f | ||
|
|
f62bb33317 | ||
|
|
a557b3cfb6 | ||
|
|
3ff4834bd5 | ||
|
|
bedb889693 | ||
|
|
157ca9cd17 | ||
|
|
6f5e7e170c | ||
|
|
df7571b369 | ||
|
|
d69e41eba8 | ||
|
|
2d41a7d1b0 | ||
|
|
42e0ccfa1c | ||
|
|
55640a91ae | ||
|
|
4f6ca8792c | ||
|
|
5ced36e319 | ||
|
|
237e7ee2e6 | ||
|
|
02f281569d | ||
|
|
ab4d971c5e | ||
|
|
e21df5831e | ||
|
|
18a0119b06 | ||
|
|
34348435fd | ||
|
|
cf3b0f3b70 | ||
|
|
a336e9e959 | ||
|
|
ea93a0130d | ||
|
|
dcd9ddde50 | ||
|
|
86bafdc104 | ||
|
|
6732fa731d | ||
|
|
8cea3e262b | ||
|
|
3e8669ea7f | ||
|
|
7df040f451 | ||
|
|
472eddcc1b | ||
|
|
c0a7900341 | ||
|
|
28a50817af | ||
|
|
2c47c43f47 | ||
|
|
2ef39651eb | ||
|
|
eb20612d77 | ||
|
|
695547f4ca | ||
|
|
d35ee0d580 | ||
|
|
95581ab92f | ||
|
|
3716f13d8e | ||
|
|
c03ef852a4 | ||
|
|
28a5feef28 | ||
|
|
57a53bda74 | ||
|
|
36f4eccb5c | ||
|
|
b6de19e92e | ||
|
|
e87bc3db20 | ||
|
|
a3739d6bad | ||
|
|
068d114740 | ||
|
|
3273c14dea | ||
|
|
206c3c246c | ||
|
|
bfcd85ab15 | ||
|
|
c99bba1655 | ||
|
|
1e7376847b | ||
|
|
b03e589987 | ||
|
|
0b497b62d8 | ||
|
|
f085b7349c | ||
|
|
dd99b3bd73 | ||
|
|
f53683f01f | ||
|
|
7b707e7d75 | ||
|
|
31110be396 | ||
|
|
604016d69d | ||
|
|
7ee9d5639b | ||
|
|
1aab328e2a | ||
|
|
1366dc326a | ||
|
|
8e73287646 | ||
|
|
7eed52a97a | ||
|
|
31ada5a052 | ||
|
|
30b4f8dc9f | ||
|
|
dbdde3d781 | ||
|
|
cf0b8babbd | ||
|
|
c0c22c8ff1 | ||
|
|
73cf10e4f3 | ||
|
|
8a1c5277eb | ||
|
|
1e4c176506 | ||
|
|
3c27e7d88f | ||
|
|
5bc3cf18d9 | ||
|
|
131e67ddc1 | ||
|
|
e5ba79db04 | ||
|
|
f4e9bf51db | ||
|
|
03f9e8fce0 | ||
|
|
4ae9900363 | ||
|
|
454d4190b7 | ||
|
|
a28b60578d | ||
|
|
5a206de620 | ||
|
|
b547531499 | ||
|
|
39e668ffb0 | ||
|
|
bd1ac6cb1e | ||
|
|
8567758156 | ||
|
|
e897d70733 | ||
|
|
fe12996382 | ||
|
|
e8e4d4ce66 | ||
|
|
44af4f4f52 | ||
|
|
7daf146801 | ||
|
|
7b23d190b1 | ||
|
|
17b8310517 | ||
|
|
5ca828d445 | ||
|
|
1ea29b4de0 | ||
|
|
b00cdadb5b | ||
|
|
53e646dabc | ||
|
|
613fafdfdf | ||
|
|
5d0bc5f607 | ||
|
|
ce5cf97e17 | ||
|
|
3ee59696e5 | ||
|
|
f5c624bcd6 | ||
|
|
026b331ba5 | ||
|
|
93c36ccf78 | ||
|
|
c1f314ccdc | ||
|
|
53a89f28a0 | ||
|
|
f874d76289 | ||
|
|
76645f3b5a | ||
|
|
28775be115 | ||
|
|
7d3ff3d2d9 | ||
|
|
4a5d639d74 | ||
|
|
74a7cf55ea | ||
|
|
9393b14b01 | ||
|
|
f8bd671073 | ||
|
|
2af753f307 | ||
|
|
5cc84f32db | ||
|
|
e89674d337 | ||
|
|
de120280e3 | ||
|
|
7f2ba0ee3e | ||
|
|
5bf303fd26 | ||
|
|
e37e46b155 | ||
|
|
b8192ef6f6 | ||
|
|
23354cf1a5 | ||
|
|
dd38fab2f3 | ||
|
|
ac08601b40 | ||
|
|
ea73dac753 | ||
|
|
2a35646520 | ||
|
|
bb285984da | ||
|
|
34db2d7d4c | ||
|
|
0e20973bdb | ||
|
|
29e2481597 | ||
|
|
1596cc5d9e | ||
|
|
8f66d96915 | ||
|
|
8ad078b964 | ||
|
|
d5368d7979 | ||
|
|
9c5657b86c | ||
|
|
b79089ea57 | ||
|
|
3555de114a | ||
|
|
c5703eb00a | ||
|
|
dbe6a1bcdf | ||
|
|
eea9530f38 | ||
|
|
a195ce8146 | ||
|
|
a0f280e71f | ||
|
|
cf28751ae3 | ||
|
|
9dd15d00d8 | ||
|
|
2442305bb7 | ||
|
|
063648368d | ||
|
|
918b2efce7 | ||
|
|
2467a997f4 | ||
|
|
f457c6a0b8 | ||
|
|
1193cada46 | ||
|
|
b0e09b8ffe | ||
|
|
8d6e8bb8d1 | ||
|
|
e2c84725de | ||
|
|
b571362bf1 | ||
|
|
b1b5aefc4b | ||
|
|
06244dd492 | ||
|
|
de2f0740f7 | ||
|
|
c56a66cb90 | ||
|
|
c1329ff156 | ||
|
|
72509b5b42 | ||
|
|
c35fdc8d61 | ||
|
|
5d31f73302 | ||
|
|
7de1261555 | ||
|
|
91a511ba8c | ||
|
|
94f6294c0a | ||
|
|
77cd3adb01 | ||
|
|
7dabad4d71 | ||
|
|
54b4074369 | ||
|
|
e4d8e22846 | ||
|
|
403dcc02f4 | ||
|
|
64be6e5c5e | ||
|
|
c661006683 | ||
|
|
f879bca21c | ||
|
|
182beb4f8b | ||
|
|
2690306f4a | ||
|
|
b59e3c670b | ||
|
|
2e0bbc091f | ||
|
|
91f05e940f | ||
|
|
db794b976c | ||
|
|
9a11f90a02 | ||
|
|
412516159f | ||
|
|
65587ef43a | ||
|
|
cf713bef31 | ||
|
|
ea5c21950c | ||
|
|
47b5dfa034 | ||
|
|
2b89ddec15 | ||
|
|
f256ff7d58 | ||
|
|
07356b9634 | ||
|
|
f2b6ef2edd | ||
|
|
1409f83fb9 | ||
|
|
37dadab745 | ||
|
|
0eefa3ba42 | ||
|
|
08f8f89702 | ||
|
|
0df436901a | ||
|
|
ae63fbdbbb | ||
|
|
4f29618c73 | ||
|
|
76660f3621 | ||
|
|
3a7350cbb9 | ||
|
|
e5aa906b01 | ||
|
|
4b0162a013 | ||
|
|
5be88e79b0 | ||
|
|
93502e0cda | ||
|
|
8d6a857ba5 | ||
|
|
6ff1370035 | ||
|
|
d5391686ae | ||
|
|
70c7382a09 | ||
|
|
9f801cf79e | ||
|
|
062e0e52ee | ||
|
|
fa99b9ff5a | ||
|
|
8f6e80917f | ||
|
|
e087ebd1c7 | ||
|
|
0d3e75d6b0 | ||
|
|
873f7bcec7 | ||
|
|
e579a03035 | ||
|
|
94f998af0a | ||
|
|
fe5e300062 | ||
|
|
2253565db5 | ||
|
|
2ead1c1c59 | ||
|
|
746e99c958 | ||
|
|
47194b5f3c | ||
|
|
4515b77aa5 | ||
|
|
20b09c4514 | ||
|
|
9874181ccd | ||
|
|
cb93554938 | ||
|
|
6b7bd32c8e | ||
|
|
17a240cd43 | ||
|
|
fc481b6d6d | ||
|
|
09182dc093 | ||
|
|
b682c3dfb5 | ||
|
|
ab0a34012f | ||
|
|
f000b82d74 | ||
|
|
dad8de82fa | ||
|
|
3fea477bfd | ||
|
|
f7d7acb3c5 | ||
|
|
9e25561edf | ||
|
|
7c6bdcc459 | ||
|
|
b09629f1d4 | ||
|
|
85446d81f3 | ||
|
|
96786bbb87 | ||
|
|
a5166baba2 | ||
|
|
d1deba8620 | ||
|
|
aaee72422a | ||
|
|
3dd0e31cc4 | ||
|
|
283a8dec37 | ||
|
|
37d354cf7f | ||
|
|
f698d451df | ||
|
|
c088f8bd03 | ||
|
|
3cd40ef655 | ||
|
|
6513351e0c | ||
|
|
f44e98ddc9 | ||
|
|
fd4db4000c | ||
|
|
9602716ed2 |
@@ -1,56 +0,0 @@
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
|
||||
android-test:
|
||||
working_directory: ~/winit
|
||||
docker:
|
||||
- image: tomaka/cargo-apk
|
||||
steps:
|
||||
- run: apt-get -qq update && apt-get install -y git
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: android-test-cache-{{ checksum "Cargo.toml" }}
|
||||
- run: cargo apk build --example window
|
||||
- save_cache:
|
||||
key: android-test-cache-{{ checksum "Cargo.toml" }}
|
||||
paths:
|
||||
- target
|
||||
|
||||
asmjs-test:
|
||||
working_directory: ~/winit
|
||||
docker:
|
||||
- image: tomaka/rustc-emscripten
|
||||
steps:
|
||||
- run: apt-get -qq update && apt-get install -y git
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: asmjs-test-cache-{{ checksum "Cargo.toml" }}
|
||||
- run: cargo build --example window --target asmjs-unknown-emscripten
|
||||
- save_cache:
|
||||
key: asmjs-test-cache-{{ checksum "Cargo.toml" }}
|
||||
paths:
|
||||
- target
|
||||
|
||||
wasm-test:
|
||||
working_directory: ~/winit
|
||||
docker:
|
||||
- image: tomaka/rustc-emscripten
|
||||
steps:
|
||||
- run: apt-get -qq update && apt-get install -y git
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: wasm-test-cache-{{ checksum "Cargo.toml" }}
|
||||
- run: cargo build --example window --target wasm32-unknown-emscripten
|
||||
- save_cache:
|
||||
key: wasm-test-cache-{{ checksum "Cargo.toml" }}
|
||||
paths:
|
||||
- target
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-test-and-deploy:
|
||||
jobs:
|
||||
- android-test
|
||||
- asmjs-test
|
||||
- wasm-test
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -20,3 +20,5 @@
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
/CHANGELOG.md merge=union
|
||||
|
||||
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,5 +1,8 @@
|
||||
- [ ] Tested on all platforms changed
|
||||
- [ ] Compilation warnings were addressed
|
||||
- [ ] `cargo fmt` has been run on this branch
|
||||
- [ ] `cargo doc` builds successfully
|
||||
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
|
||||
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
|
||||
- [ ] Created an example program if it would help users understand this functionality
|
||||
- [ ] Created or updated an example program if it would help users understand this functionality
|
||||
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,5 +2,9 @@ Cargo.lock
|
||||
target/
|
||||
rls/
|
||||
.vscode/
|
||||
util/
|
||||
*~
|
||||
*.wasm
|
||||
*.ts
|
||||
*.js
|
||||
#*#
|
||||
|
||||
60
.travis.yml
60
.travis.yml
@@ -1,7 +1,5 @@
|
||||
language: rust
|
||||
|
||||
cache: cargo
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Linux 32bit
|
||||
@@ -36,7 +34,7 @@ matrix:
|
||||
os: osx
|
||||
rust: stable
|
||||
|
||||
# iOS
|
||||
# iOS x86_64
|
||||
- env: TARGET=x86_64-apple-ios
|
||||
os: osx
|
||||
rust: nightly
|
||||
@@ -44,18 +42,60 @@ matrix:
|
||||
os: osx
|
||||
rust: stable
|
||||
|
||||
# iOS armv7
|
||||
- env: TARGET=armv7-apple-ios
|
||||
os: osx
|
||||
rust: nightly
|
||||
- env: TARGET=armv7-apple-ios
|
||||
os: osx
|
||||
rust: stable
|
||||
|
||||
# iOS arm64
|
||||
- env: TARGET=aarch64-apple-ios
|
||||
os: osx
|
||||
rust: nightly
|
||||
- env: TARGET=aarch64-apple-ios
|
||||
os: osx
|
||||
rust: stable
|
||||
|
||||
# wasm stdweb
|
||||
- env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb
|
||||
os: linux
|
||||
rust: stable
|
||||
- env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb
|
||||
os: linux
|
||||
rust: nightly
|
||||
# wasm web-sys
|
||||
- env: TARGET=wasm32-unknown-unknown FEATURES=web-sys
|
||||
os: linux
|
||||
rust: stable
|
||||
- env: TARGET=wasm32-unknown-unknown FEATURES=web-sys
|
||||
os: linux
|
||||
rust: nightly
|
||||
|
||||
install:
|
||||
- rustup self update
|
||||
- rustup target add $TARGET; true
|
||||
- rustup toolchain install stable
|
||||
- rustup component add rustfmt --toolchain stable
|
||||
|
||||
script:
|
||||
- cargo build --target $TARGET --verbose
|
||||
- cargo build --target $TARGET --features serde --verbose
|
||||
- cargo build --target $TARGET --features icon_loading --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
|
||||
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features icon_loading --verbose; fi
|
||||
- cargo +stable fmt --all -- --check
|
||||
# Ensure that the documentation builds properly.
|
||||
- cargo doc --no-deps
|
||||
# Install cargo-web to build stdweb
|
||||
- if [[ $WEB = "web" ]]; then cargo install -f cargo-web; fi
|
||||
# Build without serde then with serde
|
||||
- if [[ -z "$FEATURES" ]]; then
|
||||
cargo $WEB build --target $TARGET --verbose;
|
||||
else
|
||||
cargo $WEB build --target $TARGET --features $FEATURES --verbose;
|
||||
fi
|
||||
- cargo $WEB build --target $TARGET --features serde,$FEATURES --verbose
|
||||
# Running iOS apps on macOS requires the Simulator so we skip that for now
|
||||
# The web targets also don't support running tests
|
||||
- if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --verbose; fi
|
||||
- if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --features serde --verbose; fi
|
||||
|
||||
after_success:
|
||||
- |
|
||||
|
||||
163
CHANGELOG.md
163
CHANGELOG.md
@@ -1,8 +1,161 @@
|
||||
# Unreleased
|
||||
|
||||
# Version 0.19.2 (2019-07-29)
|
||||
# # 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.
|
||||
- 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.
|
||||
|
||||
# 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, 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.
|
||||
|
||||
# 0.20.0 Alpha 3 (2019-08-14)
|
||||
|
||||
- On macOS, drop the run closure on exit.
|
||||
- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates.
|
||||
- On X11, fix delayed events after window redraw.
|
||||
- On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface.
|
||||
- On Windows, screen saver won't start if the window is in fullscreen mode.
|
||||
- Change all occurrences of the `new_user_event` method to `with_user_event`.
|
||||
- On macOS, the dock and the menu bar are now hidden in fullscreen mode.
|
||||
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
|
||||
consists of `Fullscreen::Exclusive(VideoMode)` and
|
||||
`Fullscreen::Borderless(MonitorHandle)` variants.
|
||||
- Adds support for exclusive fullscreen mode.
|
||||
- On iOS, add support for hiding the home indicator.
|
||||
- On iOS, add support for deferring system gestures.
|
||||
- On iOS, fix a crash that occurred while acquiring a monitor's name.
|
||||
- On iOS, fix armv7-apple-ios compile target.
|
||||
- Removed the `T: Clone` requirement from the `Clone` impl of `EventLoopProxy<T>`.
|
||||
- On iOS, disable overscan compensation for external displays (removes black
|
||||
bars surrounding the image).
|
||||
- On Linux, the functions `is_wayland`, `is_x11`, `xlib_xconnection` and `wayland_display` have been moved to a new `EventLoopWindowTargetExtUnix` trait.
|
||||
- On iOS, add `set_prefers_status_bar_hidden` extension function instead of
|
||||
hijacking `set_decorations` for this purpose.
|
||||
- On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`.
|
||||
- On iOS, add touch pressure information for touch events.
|
||||
- Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms.
|
||||
- On macOS, fix the signature of `-[NSView drawRect:]`.
|
||||
- On iOS, fix the behavior of `ControlFlow::Poll`. It wasn't polling if that was the only mode ever used by the application.
|
||||
- On iOS, fix DPI sent out by views on creation was `0.0` - now it gives a reasonable number.
|
||||
- On iOS, RedrawRequested now works for gl/metal backed views.
|
||||
- On iOS, RedrawRequested is generally ordered after EventsCleared.
|
||||
|
||||
# 0.20.0 Alpha 2 (2019-07-09)
|
||||
|
||||
- On X11, non-resizable windows now have maximize explicitly disabled.
|
||||
- On Windows, support paths longer than MAX_PATH (260 characters) in `WindowEvent::DroppedFile`
|
||||
and `WindowEvent::HoveredFile`.
|
||||
- On Mac, implement `DeviceEvent::Button`.
|
||||
- Change `Event::Suspended(true / false)` to `Event::Suspended` and `Event::Resumed`.
|
||||
- On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor.
|
||||
- Revert the use of invisible surfaces in Wayland, which introduced graphical glitches with OpenGL (#835)
|
||||
- On X11, implement `_NET_WM_PING` to allow desktop environment to kill unresponsive programs.
|
||||
- On Windows, when a window is initially invisible, it won't take focus from the existing visible windows.
|
||||
- On Windows, fix multiple calls to `request_redraw` during `EventsCleared` sending multiple `RedrawRequested events.`
|
||||
- On Windows, fix edge case where `RedrawRequested` could be dispatched before input events in event loop iteration.
|
||||
- On Windows, fix timing issue that could cause events to be improperly dispatched after `RedrawRequested` but before `EventsCleared`.
|
||||
- On macOS, drop unused Metal dependency.
|
||||
- On Windows, fix the trail effect happening on transparent decorated windows. Borderless (or un-decorated) windows were not affected.
|
||||
- On Windows, fix `with_maximized` not properly setting window size to entire window.
|
||||
- On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`.
|
||||
|
||||
# 0.20.0 Alpha 1 (2019-06-21)
|
||||
|
||||
- Changes below are considered **breaking**.
|
||||
- Change all occurrences of `EventsLoop` to `EventLoop`.
|
||||
- Previously flat API is now exposed through `event`, `event_loop`, `monitor`, and `window` modules.
|
||||
- `os` module changes:
|
||||
- Renamed to `platform`.
|
||||
- All traits now have platform-specific suffixes.
|
||||
- Exposes new `desktop` module on Windows, Mac, and Linux.
|
||||
- Changes to event loop types:
|
||||
- `EventLoopProxy::wakeup` has been removed in favor of `send_event`.
|
||||
- **Major:** New `run` method drives winit event loop.
|
||||
- Returns `!` to ensure API behaves identically across all supported platforms.
|
||||
- This allows `emscripten` implementation to work without lying about the API.
|
||||
- `ControlFlow`'s variants have been replaced with `Wait`, `WaitUntil(Instant)`, `Poll`, and `Exit`.
|
||||
- Is read after `EventsCleared` is processed.
|
||||
- `Wait` waits until new events are available.
|
||||
- `WaitUntil` waits until either new events are available or the provided time has been reached.
|
||||
- `Poll` instantly resumes the event loop.
|
||||
- `Exit` aborts the event loop.
|
||||
- Takes a closure that implements `'static + FnMut(Event<T>, &EventLoop<T>, &mut ControlFlow)`.
|
||||
- `&EventLoop<T>` is provided to allow new `Window`s to be created.
|
||||
- **Major:** `platform::desktop` module exposes `EventLoopExtDesktop` trait with `run_return` method.
|
||||
- Behaves identically to `run`, but returns control flow to the calling context and can take non-`'static` closures.
|
||||
- `EventLoop`'s `poll_events` and `run_forever` methods have been removed in favor of `run` and `run_return`.
|
||||
- Changes to events:
|
||||
- Remove `Event::Awakened` in favor of `Event::UserEvent(T)`.
|
||||
- Can be sent with `EventLoopProxy::send_event`.
|
||||
- Rename `WindowEvent::Refresh` to `WindowEvent::RedrawRequested`.
|
||||
- `RedrawRequested` can be sent by the user with the `Window::request_redraw` method.
|
||||
- `EventLoop`, `EventLoopProxy`, and `Event` are now generic over `T`, for use in `UserEvent`.
|
||||
- **Major:** Add `NewEvents(StartCause)`, `EventsCleared`, and `LoopDestroyed` variants to `Event`.
|
||||
- `NewEvents` is emitted when new events are ready to be processed by event loop.
|
||||
- `StartCause` describes why new events are available, with `ResumeTimeReached`, `Poll`, `WaitCancelled`, and `Init` (sent once at start of loop).
|
||||
- `EventsCleared` is emitted when all available events have been processed.
|
||||
- Can be used to perform logic that depends on all events being processed (e.g. an iteration of a game loop).
|
||||
- `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit.
|
||||
- Rename `MonitorId` to `MonitorHandle`.
|
||||
- Removed `serde` implementations from `ControlFlow`.
|
||||
- Rename several functions to improve both internal consistency and compliance with Rust API guidelines.
|
||||
- Remove `WindowBuilder::multitouch` field, since it was only implemented on a few platforms. Multitouch is always enabled now.
|
||||
- **Breaking:** On macOS, change `ns` identifiers to use snake_case for consistency with iOS's `ui` identifiers.
|
||||
- Add `MonitorHandle::video_modes` method for retrieving supported video modes for the given monitor.
|
||||
- On Wayland, the window now exists even if nothing has been drawn.
|
||||
- On Windows, fix initial dimensions of a fullscreen window.
|
||||
- On Windows, Fix transparent borderless windows rendering wrong.
|
||||
|
||||
# Version 0.19.1 (2019-04-08)
|
||||
|
||||
@@ -10,6 +163,9 @@
|
||||
- On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus.
|
||||
- On macOS, fix command key event left and right reverse.
|
||||
- On FreeBSD, NetBSD, and OpenBSD, fix build of X11 backend.
|
||||
- On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes
|
||||
- On macOS, the numpad's subtract key has been added to the `Subtract` mapping
|
||||
- On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes
|
||||
- On Windows, fix icon not showing up in corner of window.
|
||||
- On X11, change DPI scaling factor behavior. First, winit tries to read it from "Xft.dpi" XResource, and uses DPI calculation from xrandr dimensions as fallback behavior.
|
||||
|
||||
@@ -20,7 +176,7 @@
|
||||
- On Wayland, fix `with_title()` not setting the windows title
|
||||
- On Wayland, add `set_wayland_theme()` to control client decoration color theme
|
||||
- Added serde serialization to `os::unix::XWindowType`.
|
||||
- **Breaking:** `image` crate upgraded to 0.21. This is exposed as part of the `icon_loading` API.
|
||||
- **Breaking:** Remove the `icon_loading` feature and the associated `image` dependency.
|
||||
- On X11, make event loop thread safe by replacing XNextEvent with select(2) and XCheckIfEvent
|
||||
- On Windows, fix malformed function pointer typecast that could invoke undefined behavior.
|
||||
- Refactored Windows state/flag-setting code.
|
||||
@@ -28,9 +184,6 @@
|
||||
- On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area.
|
||||
- On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled.
|
||||
- On Windows, ignore the AltGr key when populating the `ModifersState` type.
|
||||
- On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes
|
||||
- On macOS, the numpad's subtract key has been added to the `Subtract` mapping
|
||||
- On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes
|
||||
|
||||
# Version 0.18.1 (2018-12-30)
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# PLEASE MAKE PRs AGAINST THE `eventloop-2.0` BRANCH.
|
||||
|
||||
All development work for our next version is being done against that branch. Refer to [#459](https://github.com/rust-windowing/winit/issues/459) and [the associated milestone](https://github.com/rust-windowing/winit/milestone/2) for details on what that branch changes.
|
||||
|
||||
# Winit Contributing Guidelines
|
||||
|
||||
## Scope
|
||||
@@ -40,19 +36,7 @@ at least a maintainer of the platform (a maintainer making a PR themselves count
|
||||
|
||||
## Maintainers & Testers
|
||||
|
||||
Winit is managed by several people, each with their specialities, and each maintaining a subset of the
|
||||
backends of winit. As such, depending on your platform of interest, your contacts will be different.
|
||||
The current [list of testers and contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors)
|
||||
can be found on the Wiki.
|
||||
|
||||
This table summarizes who can be contacted in which case, with the following legend:
|
||||
|
||||
- `M` - Maintainer: is a main maintainer for this platform
|
||||
- `C` - Collaborator: can review code and address issues on this platform
|
||||
- `T` - Tester: has the ability of testing the platform
|
||||
- ` `: knows nothing of this platform
|
||||
|
||||
| Platform | Windows | macOS | X11 | Wayland | Android | iOS | Emscripten |
|
||||
| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
||||
| @mitchmindtree | T | | T | T | | | |
|
||||
| @Osspial | M | | T | T | T | | T |
|
||||
| @vberger | | | T | M | | | |
|
||||
| @mtak- | | T | | | T | M | |
|
||||
If you are interested in contributing or testing on a platform, please add yourself to that table!
|
||||
|
||||
73
Cargo.toml
73
Cargo.toml
@@ -1,8 +1,9 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.19.2"
|
||||
version = "0.20.0-alpha5"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2018"
|
||||
keywords = ["windowing"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
@@ -11,17 +12,23 @@ documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["icon_loading", "serde"]
|
||||
features = ["serde"]
|
||||
|
||||
[features]
|
||||
icon_loading = ["image"]
|
||||
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
|
||||
stdweb = ["std_web", "instant/stdweb"]
|
||||
|
||||
[dependencies]
|
||||
instant = "0.1"
|
||||
lazy_static = "1"
|
||||
libc = "0.2"
|
||||
libc = "0.2.64"
|
||||
log = "0.4"
|
||||
image = { version = "0.21", optional = true }
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
raw-window-handle = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
image = "0.21"
|
||||
env_logger = "0.5"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies.android_glue]
|
||||
version = "0.2"
|
||||
@@ -30,19 +37,25 @@ version = "0.2"
|
||||
objc = "0.2.3"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2.3"
|
||||
cocoa = "0.18.4"
|
||||
cocoa = "0.19.1"
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.17.3"
|
||||
dispatch = "0.1.4"
|
||||
objc = "0.2.3"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
backtrace = "0.3"
|
||||
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
|
||||
version = "0.1.3"
|
||||
default_features = false
|
||||
features = ["display_link"]
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies]
|
||||
bitflags = "1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
||||
version = "0.3.6"
|
||||
features = [
|
||||
"combaseapi",
|
||||
"commctrl",
|
||||
"dwmapi",
|
||||
"errhandlingapi",
|
||||
"hidusage",
|
||||
@@ -63,8 +76,44 @@ features = [
|
||||
]
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
|
||||
wayland-client = { version = "0.21", features = [ "dlopen", "egl", "cursor"] }
|
||||
smithay-client-toolkit = "0.4.3"
|
||||
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"
|
||||
parking_lot = "0.8"
|
||||
percent-encoding = "2.0"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
|
||||
version = "0.10"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
|
||||
package = "web-sys"
|
||||
version = "0.3.22"
|
||||
optional = true
|
||||
features = [
|
||||
'console',
|
||||
'BeforeUnloadEvent',
|
||||
'Document',
|
||||
'DomRect',
|
||||
'Element',
|
||||
'Event',
|
||||
'EventTarget',
|
||||
'FocusEvent',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'KeyboardEvent',
|
||||
'MouseEvent',
|
||||
'Node',
|
||||
'PointerEvent',
|
||||
'Window',
|
||||
'WheelEvent'
|
||||
]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
|
||||
version = "0.2.45"
|
||||
optional = true
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.std_web]
|
||||
package = "stdweb"
|
||||
version = "=0.4.20"
|
||||
optional = true
|
||||
features = ["experimental_features_which_may_break_on_minor_version_bumps"]
|
||||
|
||||
92
FEATURES.md
92
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
|
||||
@@ -84,6 +83,9 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **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
|
||||
@@ -92,6 +94,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
|
||||
### System Information
|
||||
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
|
||||
- **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth).
|
||||
|
||||
### Input Handling
|
||||
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
|
||||
@@ -99,6 +102,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
|
||||
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
|
||||
- **Touch events**: Single-touch events.
|
||||
- **Touch pressure**: Touch events contain information about the amount of force being applied.
|
||||
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
|
||||
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
|
||||
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
|
||||
@@ -128,6 +132,21 @@ 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
|
||||
|
||||
## Usability
|
||||
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
||||
|
||||
@@ -142,54 +161,57 @@ 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 |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A**|
|
||||
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
||||
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ |
|
||||
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|
||||
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |
|
||||
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
||||
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
||||
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|
||||
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|
||||
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|
|
||||
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |**N/A**|
|
||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|
||||
|
||||
### System information
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|
||||
|------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
||||
|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 |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**|
|
||||
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|❓ |
|
||||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|
||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |
|
||||
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |
|
||||
|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |✔️ |
|
||||
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|
||||
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|
||||
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❌ |
|
||||
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❌ |
|
||||
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❌ |
|
||||
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|
||||
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|
||||
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|
||||
|
||||
### Pending API Reworks
|
||||
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|
||||
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |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
|
||||
|
||||
@@ -5,7 +5,10 @@ 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.
|
||||
* @francesca64: For taking over the responsibility of maintaining almost every
|
||||
* [@francesca64]: For taking over the responsibility of maintaining almost every
|
||||
Winit backend, and standardizing HiDPI support across all of them
|
||||
|
||||
[@tomaka]: https://github.com/tomaka
|
||||
[@francesca64]: https://github.com/francesca64
|
||||
|
||||
33
README.md
33
README.md
@@ -1,9 +1,3 @@
|
||||
# This branch has been archived
|
||||
|
||||
Please direct all work against `master`.
|
||||
|
||||
--------
|
||||
|
||||
# winit - Cross-platform window creation and management in Rust
|
||||
|
||||
[](https://crates.io/crates/winit)
|
||||
@@ -13,7 +7,7 @@ Please direct all work against `master`.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.19.2"
|
||||
winit = "0.20.0-alpha4"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -41,19 +35,23 @@ show something on the window you need to use the platform-specific getters provi
|
||||
another library.
|
||||
|
||||
```rust
|
||||
extern crate winit;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let window = winit::Window::new(&events_loop).unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
match event {
|
||||
winit::Event::WindowEvent {
|
||||
event: winit::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => winit::ControlFlow::Break,
|
||||
_ => winit::ControlFlow::Continue,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -64,12 +62,11 @@ Winit is only officially supported on the latest stable version of the Rust comp
|
||||
### Cargo Features
|
||||
|
||||
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
|
||||
* `icon_loading`: Enables loading window icons directly from files. Depends on the [`image` crate](https://crates.io/crates/image).
|
||||
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
|
||||
|
||||
### Platform-specific usage
|
||||
|
||||
#### Emscripten and WebAssembly
|
||||
#### WebAssembly
|
||||
|
||||
Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to:
|
||||
|
||||
|
||||
17
appveyor.yml
17
appveyor.yml
@@ -1,13 +1,24 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: stable
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: stable
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: stable
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: stable
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
matrix:
|
||||
allow_failures:
|
||||
- CHANNEL: nightly
|
||||
install:
|
||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init -yv --default-toolchain %CHANNEL% --default-host %TARGET%
|
||||
@@ -21,4 +32,4 @@ build: false
|
||||
test_script:
|
||||
- cargo test --verbose
|
||||
- cargo test --features serde --verbose
|
||||
- cargo test --features icon_loading --verbose
|
||||
- cargo doc --no-deps
|
||||
|
||||
@@ -1,32 +1,83 @@
|
||||
extern crate winit;
|
||||
|
||||
use winit::{Event, ElementState, MouseCursor, WindowEvent, KeyboardInput, ControlFlow};
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{CursorIcon, WindowBuilder},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = winit::WindowBuilder::new().build(&events_loop).unwrap();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
window.set_title("A fantastic window!");
|
||||
|
||||
let cursors = [MouseCursor::Default, MouseCursor::Crosshair, MouseCursor::Hand, MouseCursor::Arrow, MouseCursor::Move, MouseCursor::Text, MouseCursor::Wait, MouseCursor::Help, MouseCursor::Progress, MouseCursor::NotAllowed, MouseCursor::ContextMenu, MouseCursor::Cell, MouseCursor::VerticalText, MouseCursor::Alias, MouseCursor::Copy, MouseCursor::NoDrop, MouseCursor::Grab, MouseCursor::Grabbing, MouseCursor::AllScroll, MouseCursor::ZoomIn, MouseCursor::ZoomOut, MouseCursor::EResize, MouseCursor::NResize, MouseCursor::NeResize, MouseCursor::NwResize, MouseCursor::SResize, MouseCursor::SeResize, MouseCursor::SwResize, MouseCursor::WResize, MouseCursor::EwResize, MouseCursor::NsResize, MouseCursor::NeswResize, MouseCursor::NwseResize, MouseCursor::ColResize, MouseCursor::RowResize];
|
||||
let mut cursor_idx = 0;
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
match event {
|
||||
Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. }, .. } => {
|
||||
println!("Setting cursor to \"{:?}\"", cursors[cursor_idx]);
|
||||
window.set_cursor(cursors[cursor_idx]);
|
||||
if cursor_idx < cursors.len() - 1 {
|
||||
cursor_idx += 1;
|
||||
} else {
|
||||
cursor_idx = 0;
|
||||
}
|
||||
},
|
||||
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
|
||||
return ControlFlow::Break;
|
||||
},
|
||||
_ => ()
|
||||
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;
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
|
||||
const CURSORS: &[CursorIcon] = &[
|
||||
CursorIcon::Default,
|
||||
CursorIcon::Crosshair,
|
||||
CursorIcon::Hand,
|
||||
CursorIcon::Arrow,
|
||||
CursorIcon::Move,
|
||||
CursorIcon::Text,
|
||||
CursorIcon::Wait,
|
||||
CursorIcon::Help,
|
||||
CursorIcon::Progress,
|
||||
CursorIcon::NotAllowed,
|
||||
CursorIcon::ContextMenu,
|
||||
CursorIcon::Cell,
|
||||
CursorIcon::VerticalText,
|
||||
CursorIcon::Alias,
|
||||
CursorIcon::Copy,
|
||||
CursorIcon::NoDrop,
|
||||
CursorIcon::Grab,
|
||||
CursorIcon::Grabbing,
|
||||
CursorIcon::AllScroll,
|
||||
CursorIcon::ZoomIn,
|
||||
CursorIcon::ZoomOut,
|
||||
CursorIcon::EResize,
|
||||
CursorIcon::NResize,
|
||||
CursorIcon::NeResize,
|
||||
CursorIcon::NwResize,
|
||||
CursorIcon::SResize,
|
||||
CursorIcon::SeResize,
|
||||
CursorIcon::SwResize,
|
||||
CursorIcon::WResize,
|
||||
CursorIcon::EwResize,
|
||||
CursorIcon::NsResize,
|
||||
CursorIcon::NeswResize,
|
||||
CursorIcon::NwseResize,
|
||||
CursorIcon::ColResize,
|
||||
CursorIcon::RowResize,
|
||||
];
|
||||
|
||||
@@ -1,38 +1,51 @@
|
||||
extern crate winit;
|
||||
use winit::{
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = winit::WindowBuilder::new()
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
|
||||
.build(&events_loop)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
if let winit::Event::WindowEvent { event, .. } = event {
|
||||
use winit::WindowEvent::*;
|
||||
match event {
|
||||
CloseRequested => return winit::ControlFlow::Break,
|
||||
KeyboardInput {
|
||||
input: winit::KeyboardInput {
|
||||
state: winit::ElementState::Released,
|
||||
virtual_keycode: Some(key),
|
||||
modifiers,
|
||||
..
|
||||
},
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(key),
|
||||
modifiers,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
use winit::VirtualKeyCode::*;
|
||||
use winit::event::VirtualKeyCode::*;
|
||||
match key {
|
||||
Escape => return winit::ControlFlow::Break,
|
||||
G => window.grab_cursor(!modifiers.shift).unwrap(),
|
||||
H => window.hide_cursor(!modifiers.shift),
|
||||
Escape => *control_flow = ControlFlow::Exit,
|
||||
G => window.set_cursor_grab(!modifiers.shift).unwrap(),
|
||||
H => window.set_cursor_visible(modifiers.shift),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
Event::DeviceEvent { event, .. } => match event {
|
||||
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {:?}", delta),
|
||||
DeviceEvent::Button { button, state } => match state {
|
||||
ElementState::Pressed => println!("mouse button {} pressed", button),
|
||||
ElementState::Released => println!("mouse button {} released", button),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
winit::ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
47
examples/custom_events.rs
Normal file
47
examples/custom_events.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum CustomEvent {
|
||||
Timer,
|
||||
}
|
||||
|
||||
let event_loop = EventLoop::<CustomEvent>::with_user_event();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
// `EventLoopProxy` allows you to dispatch custom events to the main Winit event
|
||||
// loop from any thread.
|
||||
let event_loop_proxy = event_loop.create_proxy();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// Wake up the `event_loop` once every second and dispatch a custom event
|
||||
// from a different thread.
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
event_loop_proxy.send_event(CustomEvent::Timer).ok();
|
||||
}
|
||||
});
|
||||
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
Event::UserEvent(event) => println!("user event: {:?}", event),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
panic!("This example is not supported on web.");
|
||||
}
|
||||
@@ -1,101 +1,65 @@
|
||||
extern crate winit;
|
||||
|
||||
use std::io::{self, Write};
|
||||
use winit::{ControlFlow, Event, WindowEvent};
|
||||
use std::io::{stdin, stdout, Write};
|
||||
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::monitor::{MonitorHandle, VideoMode};
|
||||
use winit::window::{Fullscreen, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut macos_use_simple_fullscreen = false;
|
||||
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
|
||||
stdout().flush().unwrap();
|
||||
|
||||
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();
|
||||
stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
|
||||
let mut num = String::new();
|
||||
io::stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
match num {
|
||||
2 => macos_use_simple_fullscreen = true,
|
||||
_ => {}
|
||||
}
|
||||
let fullscreen = Some(match num {
|
||||
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
|
||||
2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
|
||||
_ => panic!("Please enter a valid number"),
|
||||
});
|
||||
|
||||
// Prompt for monitor when using native fullscreen
|
||||
if !macos_use_simple_fullscreen {
|
||||
Some(prompt_for_monitor(&events_loop))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
Some(prompt_for_monitor(&events_loop))
|
||||
};
|
||||
|
||||
let mut is_fullscreen = monitor.is_some();
|
||||
let mut is_maximized = false;
|
||||
let mut decorations = true;
|
||||
|
||||
let window = winit::WindowBuilder::new()
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello world!")
|
||||
.with_fullscreen(monitor)
|
||||
.build(&events_loop)
|
||||
.with_fullscreen(fullscreen.clone())
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
println!("{:?}", event);
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => return ControlFlow::Break,
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
winit::KeyboardInput {
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match (virtual_code, state) {
|
||||
(winit::VirtualKeyCode::Escape, _) => return ControlFlow::Break,
|
||||
(winit::VirtualKeyCode::F, winit::ElementState::Pressed) => {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if macos_use_simple_fullscreen {
|
||||
use winit::os::macos::WindowExt;
|
||||
if WindowExt::set_simple_fullscreen(&window, !is_fullscreen) {
|
||||
is_fullscreen = !is_fullscreen;
|
||||
}
|
||||
|
||||
return ControlFlow::Continue;
|
||||
}
|
||||
}
|
||||
|
||||
is_fullscreen = !is_fullscreen;
|
||||
if !is_fullscreen {
|
||||
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
||||
(VirtualKeyCode::F, ElementState::Pressed) => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
window.set_fullscreen(Some(window.get_current_monitor()));
|
||||
window.set_fullscreen(fullscreen.clone());
|
||||
}
|
||||
}
|
||||
(winit::VirtualKeyCode::S, winit::ElementState::Pressed) => {
|
||||
println!("window.get_fullscreen {:?}", window.get_fullscreen());
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use winit::os::macos::WindowExt;
|
||||
println!("window.get_simple_fullscreen {:?}", WindowExt::get_simple_fullscreen(&window));
|
||||
}
|
||||
(VirtualKeyCode::S, ElementState::Pressed) => {
|
||||
println!("window.fullscreen {:?}", window.fullscreen());
|
||||
}
|
||||
(winit::VirtualKeyCode::M, winit::ElementState::Pressed) => {
|
||||
(VirtualKeyCode::M, ElementState::Pressed) => {
|
||||
is_maximized = !is_maximized;
|
||||
window.set_maximized(is_maximized);
|
||||
}
|
||||
(winit::VirtualKeyCode::D, winit::ElementState::Pressed) => {
|
||||
(VirtualKeyCode::D, ElementState::Pressed) => {
|
||||
decorations = !decorations;
|
||||
window.set_decorations(decorations);
|
||||
}
|
||||
@@ -105,26 +69,48 @@ fn main() {
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
// Enumerate monitors and prompt user to choose one
|
||||
fn prompt_for_monitor(events_loop: &winit::EventsLoop) -> winit::MonitorId {
|
||||
for (num, monitor) in events_loop.get_available_monitors().enumerate() {
|
||||
println!("Monitor #{}: {:?}", num, monitor.get_name());
|
||||
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();
|
||||
stdout().flush().unwrap();
|
||||
|
||||
let mut num = String::new();
|
||||
io::stdin().read_line(&mut num).unwrap();
|
||||
stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
let monitor = events_loop.get_available_monitors().nth(num).expect("Please enter a valid ID");
|
||||
let monitor = event_loop
|
||||
.available_monitors()
|
||||
.nth(num)
|
||||
.expect("Please enter a valid ID");
|
||||
|
||||
println!("Using {:?}", monitor.get_name());
|
||||
println!("Using {:?}", monitor.name());
|
||||
|
||||
monitor
|
||||
}
|
||||
|
||||
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
|
||||
for (i, video_mode) in monitor.video_modes().enumerate() {
|
||||
println!("Video mode #{}: {}", i, video_mode);
|
||||
}
|
||||
|
||||
print!("Please write the number of the video mode to use: ");
|
||||
stdout().flush().unwrap();
|
||||
|
||||
let mut num = String::new();
|
||||
stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
let video_mode = monitor
|
||||
.video_modes()
|
||||
.nth(num)
|
||||
.expect("Please enter a valid ID");
|
||||
|
||||
println!("Using {}", video_mode);
|
||||
|
||||
video_mode
|
||||
}
|
||||
|
||||
@@ -1,74 +1,82 @@
|
||||
extern crate winit;
|
||||
use winit::{
|
||||
event::{Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = winit::WindowBuilder::new()
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("Your faithful window")
|
||||
.build(&events_loop)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut close_requested = false;
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
use winit::WindowEvent::*;
|
||||
use winit::ElementState::Released;
|
||||
use winit::VirtualKeyCode::{N, Y};
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
use winit::event::{
|
||||
ElementState::Released,
|
||||
VirtualKeyCode::{N, Y},
|
||||
};
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
winit::Event::WindowEvent { event, .. } => match event {
|
||||
CloseRequested => {
|
||||
// `CloseRequested` is sent when the close button on the window is pressed (or
|
||||
// through whatever other mechanisms the window manager provides for closing a
|
||||
// window). If you don't handle this event, the close button won't actually do
|
||||
// anything.
|
||||
Event::WindowEvent { event, .. } => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
// `CloseRequested` is sent when the close button on the window is pressed (or
|
||||
// through whatever other mechanisms the window manager provides for closing a
|
||||
// window). If you don't handle this event, the close button won't actually do
|
||||
// anything.
|
||||
|
||||
// A common thing to do here is prompt the user if they have unsaved work.
|
||||
// Creating a proper dialog box for that is far beyond the scope of this
|
||||
// example, so here we'll just respond to the Y and N keys.
|
||||
println!("Are you ready to bid your window farewell? [Y/N]");
|
||||
close_requested = true;
|
||||
// A common thing to do here is prompt the user if they have unsaved work.
|
||||
// Creating a proper dialog box for that is far beyond the scope of this
|
||||
// example, so here we'll just respond to the Y and N keys.
|
||||
println!("Are you ready to bid your window farewell? [Y/N]");
|
||||
close_requested = true;
|
||||
|
||||
// In applications where you can safely close the window without further
|
||||
// action from the user, this is generally where you'd handle cleanup before
|
||||
// closing the window. How to close the window is detailed in the handler for
|
||||
// the Y key.
|
||||
}
|
||||
KeyboardInput {
|
||||
input:
|
||||
winit::KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state: Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match virtual_code {
|
||||
Y => {
|
||||
if close_requested {
|
||||
// This is where you'll want to do any cleanup you need.
|
||||
println!("Buh-bye!");
|
||||
|
||||
// For a single-window application like this, you'd normally just
|
||||
// break out of the event loop here. If you wanted to keep running the
|
||||
// event loop (i.e. if it's a multi-window application), you need to
|
||||
// drop the window. That closes it, and results in `Destroyed` being
|
||||
// sent.
|
||||
return winit::ControlFlow::Break;
|
||||
}
|
||||
// In applications where you can safely close the window without further
|
||||
// action from the user, this is generally where you'd handle cleanup before
|
||||
// closing the window. How to close the window is detailed in the handler for
|
||||
// the Y key.
|
||||
}
|
||||
N => {
|
||||
if close_requested {
|
||||
println!("Your window will continue to stay by your side.");
|
||||
close_requested = false;
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state: Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
match virtual_code {
|
||||
Y => {
|
||||
if close_requested {
|
||||
// This is where you'll want to do any cleanup you need.
|
||||
println!("Buh-bye!");
|
||||
|
||||
// For a single-window application like this, you'd normally just
|
||||
// break out of the event loop here. If you wanted to keep running the
|
||||
// event loop (i.e. if it's a multi-window application), you need to
|
||||
// drop the window. That closes it, and results in `Destroyed` being
|
||||
// sent.
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
N => {
|
||||
if close_requested {
|
||||
println!("Your window will continue to stay by your side.");
|
||||
close_requested = false;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
winit::ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
extern crate winit;
|
||||
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = winit::WindowBuilder::new()
|
||||
.build(&events_loop)
|
||||
.unwrap();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
window.set_min_dimensions(Some(LogicalSize::new(400.0, 200.0)));
|
||||
window.set_max_dimensions(Some(LogicalSize::new(800.0, 400.0)));
|
||||
window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0)));
|
||||
window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0)));
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
winit::Event::WindowEvent { event: winit::WindowEvent::CloseRequested, .. } => winit::ControlFlow::Break,
|
||||
_ => winit::ControlFlow::Continue,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
extern crate winit;
|
||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
let event_loop = winit::EventsLoop::new();
|
||||
let window = winit::WindowBuilder::new().build(&event_loop).unwrap();
|
||||
println!("{:#?}\nPrimary: {:#?}", window.get_available_monitors(), window.get_primary_monitor());
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
dbg!(window.available_monitors().collect::<Vec<_>>());
|
||||
dbg!(window.primary_monitor());
|
||||
}
|
||||
|
||||
176
examples/multithreaded.rs
Normal file
176
examples/multithreaded.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
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, Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
const WINDOW_COUNT: usize = 3;
|
||||
const WINDOW_SIZE: (u32, u32) = (600, 400);
|
||||
|
||||
env_logger::init();
|
||||
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())
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect();
|
||||
let mut video_mode_id = 0usize;
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
window_senders.insert(window.id(), tx);
|
||||
thread::spawn(move || {
|
||||
while let Ok(event) = rx.recv() {
|
||||
match event {
|
||||
WindowEvent::Moved { .. } => {
|
||||
// We need to update our chosen video mode if the window
|
||||
// was moved to an another monitor, so that the window
|
||||
// appears on this monitor instead when we go fullscreen
|
||||
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
|
||||
video_modes = window.current_monitor().video_modes().collect();
|
||||
video_mode_id = video_mode_id.min(video_modes.len());
|
||||
let video_mode = video_modes.iter().nth(video_mode_id);
|
||||
|
||||
// Different monitors may support different video modes,
|
||||
// and the index we chose previously may now point to a
|
||||
// completely different video mode, so notify the user
|
||||
if video_mode != previous_video_mode.as_ref() {
|
||||
println!(
|
||||
"Window moved to another monitor, picked video mode: {}",
|
||||
video_modes.iter().nth(video_mode_id).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(key),
|
||||
modifiers,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
window.set_title(&format!("{:?}", key));
|
||||
let state = !modifiers.shift;
|
||||
use VirtualKeyCode::*;
|
||||
match key {
|
||||
A => window.set_always_on_top(state),
|
||||
C => window.set_cursor_icon(match state {
|
||||
true => CursorIcon::Progress,
|
||||
false => CursorIcon::Default,
|
||||
}),
|
||||
D => window.set_decorations(!state),
|
||||
// Cycle through video modes
|
||||
Right | Left => {
|
||||
video_mode_id = match key {
|
||||
Left => video_mode_id.saturating_sub(1),
|
||||
Right => (video_modes.len() - 1).min(video_mode_id + 1),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
println!(
|
||||
"Picking video mode: {}",
|
||||
video_modes.iter().nth(video_mode_id).unwrap()
|
||||
);
|
||||
}
|
||||
F => window.set_fullscreen(match (state, modifiers.alt) {
|
||||
(true, false) => {
|
||||
Some(Fullscreen::Borderless(window.current_monitor()))
|
||||
}
|
||||
(true, true) => Some(Fullscreen::Exclusive(
|
||||
video_modes.iter().nth(video_mode_id).unwrap().clone(),
|
||||
)),
|
||||
(false, _) => None,
|
||||
}),
|
||||
G => window.set_cursor_grab(state).unwrap(),
|
||||
H => window.set_cursor_visible(!state),
|
||||
I => {
|
||||
println!("Info:");
|
||||
println!("-> outer_position : {:?}", window.outer_position());
|
||||
println!("-> inner_position : {:?}", window.inner_position());
|
||||
println!("-> outer_size : {:?}", window.outer_size());
|
||||
println!("-> inner_size : {:?}", window.inner_size());
|
||||
println!("-> fullscreen : {:?}", window.fullscreen());
|
||||
}
|
||||
L => window.set_min_inner_size(match state {
|
||||
true => Some(WINDOW_SIZE.into()),
|
||||
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;
|
||||
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,
|
||||
}
|
||||
.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));
|
||||
window.set_visible(true);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
event_loop.run(move |event, _event_loop, control_flow| {
|
||||
*control_flow = match !window_senders.is_empty() {
|
||||
true => ControlFlow::Wait,
|
||||
false => ControlFlow::Exit,
|
||||
};
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => match event {
|
||||
WindowEvent::CloseRequested
|
||||
| WindowEvent::Destroyed
|
||||
| WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
window_senders.remove(&window_id);
|
||||
}
|
||||
_ => {
|
||||
if let Some(tx) = window_senders.get(&window_id) {
|
||||
tx.send(event).unwrap();
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
panic!("Example not supported on Wasm");
|
||||
}
|
||||
@@ -1,33 +1,49 @@
|
||||
extern crate winit;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
for _ in 0..3 {
|
||||
let window = winit::Window::new(&events_loop).unwrap();
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
event_loop.run(move |event, event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
match event {
|
||||
winit::Event::WindowEvent {
|
||||
event: winit::WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} => {
|
||||
println!("Window {:?} has received the signal to close", window_id);
|
||||
Event::WindowEvent { event, window_id } => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("Window {:?} has received the signal to close", window_id);
|
||||
|
||||
// This drops the window, causing it to close.
|
||||
windows.remove(&window_id);
|
||||
// This drops the window, causing it to close.
|
||||
windows.remove(&window_id);
|
||||
|
||||
if windows.is_empty() {
|
||||
return winit::ControlFlow::Break;
|
||||
if windows.is_empty() {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
winit::ControlFlow::Continue
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
extern crate winit;
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
|
||||
let _window = winit::WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&events_loop)
|
||||
.unwrap();
|
||||
|
||||
let proxy = events_loop.create_proxy();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// Wake up the `events_loop` once every second.
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
proxy.wakeup().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
println!("{:?}", event);
|
||||
match event {
|
||||
winit::Event::WindowEvent { event: winit::WindowEvent::CloseRequested, .. } =>
|
||||
winit::ControlFlow::Break,
|
||||
_ => winit::ControlFlow::Continue,
|
||||
}
|
||||
});
|
||||
}
|
||||
35
examples/request_redraw.rs
Normal file
35
examples/request_redraw.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use instant::Instant;
|
||||
use std::time::Duration;
|
||||
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| 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::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
println!("{:?}", event);
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
@@ -1,26 +1,31 @@
|
||||
extern crate winit;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let mut resizable = false;
|
||||
|
||||
let window = winit::WindowBuilder::new()
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hit space to toggle resizability.")
|
||||
.with_dimensions((400, 200).into())
|
||||
.with_inner_size((400, 200).into())
|
||||
.with_resizable(resizable)
|
||||
.build(&events_loop)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
match event {
|
||||
winit::Event::WindowEvent { event, .. } => match event {
|
||||
winit::WindowEvent::CloseRequested => return winit::ControlFlow::Break,
|
||||
winit::WindowEvent::KeyboardInput {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
winit::KeyboardInput {
|
||||
virtual_keycode: Some(winit::VirtualKeyCode::Space),
|
||||
state: winit::ElementState::Released,
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(VirtualKeyCode::Space),
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
@@ -33,6 +38,5 @@ fn main() {
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
winit::ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
37
examples/timer.rs
Normal file
37
examples/timer.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use instant::Instant;
|
||||
use std::time::Duration;
|
||||
use winit::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let timer_length = Duration::new(1, 0);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => {
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length)
|
||||
}
|
||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length);
|
||||
println!("\nTimer\n");
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,20 +1,29 @@
|
||||
extern crate winit;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = winit::WindowBuilder::new().with_decorations(false)
|
||||
.with_transparency(true)
|
||||
.build(&events_loop).unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
.with_decorations(false)
|
||||
.with_transparent(true)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
window.set_title("A fantastic window!");
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
winit::Event::WindowEvent { event: winit::WindowEvent::CloseRequested, .. } => winit::ControlFlow::Break,
|
||||
_ => winit::ControlFlow::Continue,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
12
examples/video_modes.rs
Normal file
12
examples/video_modes.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use winit::event_loop::EventLoop;
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
let monitor = event_loop.primary_monitor();
|
||||
|
||||
println!("Listing available video modes:");
|
||||
|
||||
for mode in monitor.video_modes() {
|
||||
println!("{}", mode);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,26 @@
|
||||
extern crate winit;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = winit::WindowBuilder::new()
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&events_loop)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
winit::Event::WindowEvent {
|
||||
event: winit::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => winit::ControlFlow::Break,
|
||||
_ => winit::ControlFlow::Continue,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
121
examples/window_debug.rs
Normal file
121
examples/window_debug.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
// This example is used by developers to test various window functions.
|
||||
|
||||
use winit::{
|
||||
dpi::{LogicalSize, PhysicalSize},
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(LogicalSize::from((100, 100)))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
eprintln!("debugging keys:");
|
||||
eprintln!(" (E) Enter exclusive fullscreen");
|
||||
eprintln!(" (F) Toggle borderless fullscreen");
|
||||
#[cfg(waiting_for_set_minimized)]
|
||||
eprintln!(" (M) Toggle minimized");
|
||||
eprintln!(" (Q) Quit event loop");
|
||||
eprintln!(" (V) Toggle visibility");
|
||||
eprintln!(" (X) Toggle maximized");
|
||||
|
||||
#[cfg(waiting_for_set_minimized)]
|
||||
let mut minimized = false;
|
||||
let mut maximized = false;
|
||||
let mut visible = true;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::DeviceEvent {
|
||||
event:
|
||||
DeviceEvent::Key(KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
}),
|
||||
..
|
||||
} => match key {
|
||||
#[cfg(waiting_for_set_minimized)]
|
||||
VirtualKeyCode::M => {
|
||||
if minimized {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
}
|
||||
VirtualKeyCode::V => {
|
||||
if !visible {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::KeyboardInput { input, .. },
|
||||
..
|
||||
} => match input {
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
} => match key {
|
||||
VirtualKeyCode::E => {
|
||||
fn area(size: PhysicalSize) -> f64 {
|
||||
size.width * size.height
|
||||
}
|
||||
|
||||
let monitor = window.current_monitor();
|
||||
if let Some(mode) = monitor.video_modes().max_by(|a, b| {
|
||||
area(a.size())
|
||||
.partial_cmp(&area(b.size()))
|
||||
.expect("NaN in video mode 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)));
|
||||
}
|
||||
}
|
||||
#[cfg(waiting_for_set_minimized)]
|
||||
VirtualKeyCode::M => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
VirtualKeyCode::Q => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
VirtualKeyCode::V => {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
}
|
||||
VirtualKeyCode::X => {
|
||||
maximized = !maximized;
|
||||
window.set_maximized(maximized);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,94 +1,55 @@
|
||||
// Heads up: you need to compile this example with `--features icon_loading`.
|
||||
// `Icon::from_path` won't be available otherwise, though for your own applications, you could use
|
||||
// `Icon::from_rgba` if you don't want to depend on the `image` crate.
|
||||
|
||||
extern crate winit;
|
||||
#[cfg(feature = "icon_loading")]
|
||||
extern crate image;
|
||||
use std::path::Path;
|
||||
use winit::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{Icon, WindowBuilder},
|
||||
};
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
fn main() {
|
||||
use winit::Icon;
|
||||
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
|
||||
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
|
||||
// since it seems to work well enough in most cases. Be careful about going too high, or
|
||||
// you'll be bitten by the low-quality downscaling built into the WM.
|
||||
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
|
||||
// While `Icon::from_path` is the most straightforward, you have a few other options. If you
|
||||
// want to use the `include_bytes` macro, then pass the result to `Icon::from_bytes`. See the
|
||||
// docs for the full list of options (you'll have to generate the docs with the `icon_loading`
|
||||
// feature enabled).
|
||||
let icon = Icon::from_path(path).expect("Failed to open icon");
|
||||
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
let icon = load_icon(Path::new(path));
|
||||
|
||||
let window = winit::WindowBuilder::new()
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("An iconic window!")
|
||||
// At present, this only does anything on Windows and X11, so if you want to save load
|
||||
// time, you can put icon loading behind a function that returns `None` on other platforms.
|
||||
.with_window_icon(Some(icon))
|
||||
.build(&events_loop)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
if let winit::Event::WindowEvent { event, .. } = event {
|
||||
use winit::WindowEvent::*;
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
use winit::event::WindowEvent::*;
|
||||
match event {
|
||||
CloseRequested => return winit::ControlFlow::Break,
|
||||
CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
DroppedFile(path) => {
|
||||
use image::GenericImageView;
|
||||
|
||||
let icon_image = image::open(path).expect("Failed to open window icon");
|
||||
|
||||
let (width, height) = icon_image.dimensions();
|
||||
const DESIRED_SIZE: u32 = 32;
|
||||
let (new_width, new_height) = if width == height {
|
||||
(DESIRED_SIZE, DESIRED_SIZE)
|
||||
} else {
|
||||
// Note that this will never divide by zero, due to the previous condition.
|
||||
let aspect_adjustment = DESIRED_SIZE as f64
|
||||
/ std::cmp::max(width, height) as f64;
|
||||
(
|
||||
(width as f64 * aspect_adjustment) as u32,
|
||||
(height as f64 * aspect_adjustment) as u32,
|
||||
)
|
||||
};
|
||||
|
||||
// By scaling the icon ourselves, we get higher-quality filtering and save
|
||||
// some memory.
|
||||
let icon = image::imageops::resize(
|
||||
&icon_image,
|
||||
new_width,
|
||||
new_height,
|
||||
image::FilterType::Lanczos3,
|
||||
);
|
||||
|
||||
let (offset_x, offset_y) = (
|
||||
(DESIRED_SIZE - new_width) / 2,
|
||||
(DESIRED_SIZE - new_height) / 2,
|
||||
);
|
||||
|
||||
let mut canvas = image::ImageBuffer::new(DESIRED_SIZE, DESIRED_SIZE);
|
||||
image::imageops::replace(
|
||||
&mut canvas,
|
||||
&icon,
|
||||
offset_x,
|
||||
offset_y,
|
||||
);
|
||||
|
||||
window.set_window_icon(Some(canvas.into()));
|
||||
},
|
||||
window.set_window_icon(Some(load_icon(&path)));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
winit::ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "icon_loading"))]
|
||||
fn main() {
|
||||
print!(
|
||||
r#"This example requires the `icon_loading` feature:
|
||||
cargo run --example window_icon --features icon_loading
|
||||
"#);
|
||||
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 (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);
|
||||
}
|
||||
(rgba, width, height)
|
||||
};
|
||||
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
|
||||
}
|
||||
|
||||
58
examples/window_run_return.rs
Normal file
58
examples/window_run_return.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
// Limit this example to only compatible platforms.
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
fn main() {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
platform::desktop::EventLoopExtDesktop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
let mut event_loop = EventLoop::new();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut quit = false;
|
||||
|
||||
while !quit {
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
// Print only Window events to reduce noise
|
||||
println!("{:?}", event);
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
quit = true;
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
Event::EventsCleared => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
});
|
||||
|
||||
// Sleep for 1/60 second to simulate rendering
|
||||
sleep(Duration::from_millis(16));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
println!("This platform doesn't support run_return.");
|
||||
}
|
||||
3
rustfmt.toml
Normal file
3
rustfmt.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
force_explicit_abi=true
|
||||
use_field_init_shorthand=true
|
||||
# merge_imports=true
|
||||
24
src/dpi.rs
24
src/dpi.rs
@@ -1,4 +1,3 @@
|
||||
|
||||
//! DPI 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
|
||||
@@ -29,18 +28,18 @@
|
||||
//! them entering an existential panic. Once users enter that state, they will no longer be focused on your application.
|
||||
//!
|
||||
//! There are two ways to get the DPI factor:
|
||||
//! - You can track the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) event of your
|
||||
//! - You can track the [`HiDpiFactorChanged`](crate::event::WindowEvent::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
|
||||
//! [`MonitorId::get_hidpi_factor`](../struct.MonitorId.html#method.get_hidpi_factor), or the
|
||||
//! [`MonitorHandle::hidpi_factor`](crate::monitor::MonitorHandle::hidpi_factor), or the
|
||||
//! current DPI factor applied to a window by calling
|
||||
//! [`Window::get_hidpi_factor`](../struct.Window.html#method.get_hidpi_factor), which is roughly equivalent
|
||||
//! to `window.get_current_monitor().get_hidpi_factor()`.
|
||||
//! [`Window::hidpi_factor`](crate::window::Window::hidpi_factor), which is roughly equivalent
|
||||
//! to `window.current_monitor().hidpi_factor()`.
|
||||
//!
|
||||
//! 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)
|
||||
//! the most robust way is to monitor the [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged)
|
||||
//! event and dynamically adapt your drawing logic to follow the DPI factor.
|
||||
//!
|
||||
//! Here's an overview of what sort of DPI factors you can expect, and where they come from:
|
||||
@@ -50,30 +49,31 @@
|
||||
//! - **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
|
||||
//! - **X11:** On X11, we calculate 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.
|
||||
//! - **Web:** DPI factors are handled by the browser and will always be 1.0 for your 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`](../enum.WindowEvent.html#variant.Resized) event, even on platforms where no resize actually occurs,
|
||||
//! [`Resized`](crate::event::WindowEvent::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`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) if you're only listening for size.
|
||||
//! [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) if you're only listening for size.
|
||||
//!
|
||||
//! 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.
|
||||
//!
|
||||
//! `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
|
||||
//! `winit` will send [`Resized`](crate::event::WindowEvent::Resized) events whenever a window's logical size
|
||||
//! changes, and [`HiDpiFactorChanged`](crate::event::WindowEvent::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.
|
||||
//!
|
||||
//! If you never received any [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events,
|
||||
//! If you never received any [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events,
|
||||
//! then your window's DPI factor is 1.
|
||||
|
||||
/// Checks that the DPI factor is a normal positive `f64`.
|
||||
|
||||
82
src/error.rs
Normal file
82
src/error.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use std::{error, fmt};
|
||||
|
||||
use crate::platform_impl;
|
||||
|
||||
/// An error whose cause it outside Winit's control.
|
||||
#[derive(Debug)]
|
||||
pub enum ExternalError {
|
||||
/// The operation is not supported by the backend.
|
||||
NotSupported(NotSupportedError),
|
||||
/// The OS cannot perform the operation.
|
||||
Os(OsError),
|
||||
}
|
||||
|
||||
/// The error type for when the requested operation is not supported by the backend.
|
||||
#[derive(Clone)]
|
||||
pub struct NotSupportedError {
|
||||
_marker: (),
|
||||
}
|
||||
|
||||
/// The error type for when the OS cannot perform the requested operation.
|
||||
#[derive(Debug)]
|
||||
pub struct OsError {
|
||||
line: u32,
|
||||
file: &'static str,
|
||||
error: platform_impl::OsError,
|
||||
}
|
||||
|
||||
impl NotSupportedError {
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn new() -> NotSupportedError {
|
||||
NotSupportedError { _marker: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl OsError {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError {
|
||||
OsError { line, file, error }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! os_error {
|
||||
($error:expr) => {{
|
||||
crate::error::OsError::new(line!(), file!(), $error)
|
||||
}};
|
||||
}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.pad(&format!(
|
||||
"os error at {}:{}: {}",
|
||||
self.file, self.line, self.error
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ExternalError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
ExternalError::NotSupported(e) => e.fmt(f),
|
||||
ExternalError::Os(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NotSupportedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("NotSupportedError").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NotSupportedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.pad("the requested operation is not supported by Winit")
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for OsError {}
|
||||
impl error::Error for ExternalError {}
|
||||
impl error::Error for NotSupportedError {}
|
||||
@@ -1,24 +1,90 @@
|
||||
//! The `Event` enum and assorted supporting types.
|
||||
//!
|
||||
//! These are sent to the closure given to [`EventLoop::run(...)`][event_loop_run], where they get
|
||||
//! processed and used to modify the program state. For more details, see the root-level documentation.
|
||||
//!
|
||||
//! [event_loop_run]: crate::event_loop::EventLoop::run
|
||||
use instant::Instant;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use {DeviceId, LogicalPosition, LogicalSize, WindowId};
|
||||
use crate::{
|
||||
dpi::{LogicalPosition, LogicalSize},
|
||||
platform_impl,
|
||||
window::WindowId,
|
||||
};
|
||||
|
||||
/// Describes a generic event.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Event {
|
||||
pub enum Event<T> {
|
||||
/// Emitted when the OS sends an event to a winit window.
|
||||
WindowEvent {
|
||||
window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
},
|
||||
/// Emitted when the OS sends an event to a device.
|
||||
DeviceEvent {
|
||||
device_id: DeviceId,
|
||||
event: DeviceEvent,
|
||||
},
|
||||
Awakened,
|
||||
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event)
|
||||
UserEvent(T),
|
||||
/// Emitted when new events arrive from the OS to be processed.
|
||||
NewEvents(StartCause),
|
||||
/// Emitted when all of the event loop's events have been processed and control flow is about
|
||||
/// to be taken away from the program.
|
||||
EventsCleared,
|
||||
|
||||
/// The application has been suspended or resumed.
|
||||
///
|
||||
/// The parameter is true if app was suspended, and false if it has been resumed.
|
||||
Suspended(bool),
|
||||
/// Emitted when the event loop is being shut down. This is irreversable - if this event is
|
||||
/// emitted, it is guaranteed to be the last event emitted.
|
||||
LoopDestroyed,
|
||||
|
||||
/// Emitted when the application has been suspended.
|
||||
Suspended,
|
||||
|
||||
/// Emitted when the application has been resumed.
|
||||
Resumed,
|
||||
}
|
||||
|
||||
impl<T> Event<T> {
|
||||
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
|
||||
use self::Event::*;
|
||||
match self {
|
||||
UserEvent(_) => Err(self),
|
||||
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
|
||||
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
|
||||
NewEvents(cause) => Ok(NewEvents(cause)),
|
||||
EventsCleared => Ok(EventsCleared),
|
||||
LoopDestroyed => Ok(LoopDestroyed),
|
||||
Suspended => Ok(Suspended),
|
||||
Resumed => Ok(Resumed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the reason the event loop is resuming.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StartCause {
|
||||
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
|
||||
/// moment the timeout was requested and the requested resume time. The actual resume time is
|
||||
/// guaranteed to be equal to or after the requested resume time.
|
||||
ResumeTimeReached {
|
||||
start: Instant,
|
||||
requested_resume: Instant,
|
||||
},
|
||||
|
||||
/// Sent if the OS has new events to send to the window, after a wait was requested. Contains
|
||||
/// the moment the wait was requested and the resume time, if requested.
|
||||
WaitCancelled {
|
||||
start: Instant,
|
||||
requested_resume: Option<Instant>,
|
||||
},
|
||||
|
||||
/// Sent if the event loop is being resumed after the loop's control flow was set to
|
||||
/// `ControlFlow::Poll`.
|
||||
Poll,
|
||||
|
||||
/// Sent once, immediately after `run` is called. Indicates that the loop was just initialized.
|
||||
Init,
|
||||
}
|
||||
|
||||
/// Describes an event from a `Window`.
|
||||
@@ -37,19 +103,19 @@ pub enum WindowEvent {
|
||||
Destroyed,
|
||||
|
||||
/// A file has been dropped into the window.
|
||||
///
|
||||
///
|
||||
/// When the user drops multiple files at once, this event will be emitted for each file
|
||||
/// separately.
|
||||
DroppedFile(PathBuf),
|
||||
|
||||
/// A file is being hovered over the window.
|
||||
///
|
||||
///
|
||||
/// When the user hovers multiple files at once, this event will be emitted for each file
|
||||
/// separately.
|
||||
HoveredFile(PathBuf),
|
||||
|
||||
/// A file was hovered, but has exited the window.
|
||||
///
|
||||
///
|
||||
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
|
||||
/// hovered.
|
||||
HoveredFileCancelled,
|
||||
@@ -63,7 +129,19 @@ pub enum WindowEvent {
|
||||
Focused(bool),
|
||||
|
||||
/// An event from the keyboard has been received.
|
||||
KeyboardInput { device_id: DeviceId, input: KeyboardInput },
|
||||
KeyboardInput {
|
||||
device_id: DeviceId,
|
||||
input: KeyboardInput,
|
||||
/// If `true`, the event was generated synthetically by winit
|
||||
/// in one of the following circumstances:
|
||||
///
|
||||
/// * **X11**: Synthetic key press events are generated for all keys pressed
|
||||
/// when a window gains focus. Likewise, synthetic key release events
|
||||
/// are generated for all keys pressed when a window goes out of focus.
|
||||
///
|
||||
/// Otherwise, this value is always `false`.
|
||||
is_synthetic: bool,
|
||||
},
|
||||
|
||||
/// The cursor has moved on the window.
|
||||
CursorMoved {
|
||||
@@ -73,7 +151,7 @@ pub enum WindowEvent {
|
||||
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
|
||||
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
|
||||
position: LogicalPosition,
|
||||
modifiers: ModifiersState
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
/// The cursor has entered the window.
|
||||
@@ -83,24 +161,41 @@ pub enum WindowEvent {
|
||||
CursorLeft { device_id: DeviceId },
|
||||
|
||||
/// A mouse wheel movement or touchpad scroll occurred.
|
||||
MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, modifiers: ModifiersState },
|
||||
MouseWheel {
|
||||
device_id: DeviceId,
|
||||
delta: MouseScrollDelta,
|
||||
phase: TouchPhase,
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
/// An mouse button press has been received.
|
||||
MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton, modifiers: ModifiersState },
|
||||
|
||||
MouseInput {
|
||||
device_id: DeviceId,
|
||||
state: ElementState,
|
||||
button: MouseButton,
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
/// Touchpad pressure event.
|
||||
///
|
||||
/// At the moment, only supported on Apple forcetouch-capable macbooks.
|
||||
/// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad
|
||||
/// is being pressed) and stage (integer representing the click level).
|
||||
TouchpadPressure { device_id: DeviceId, pressure: f32, stage: i64 },
|
||||
TouchpadPressure {
|
||||
device_id: DeviceId,
|
||||
pressure: f32,
|
||||
stage: i64,
|
||||
},
|
||||
|
||||
/// Motion on some analog axis. May report data redundant to other, more specific events.
|
||||
AxisMotion { device_id: DeviceId, axis: AxisId, value: f64 },
|
||||
AxisMotion {
|
||||
device_id: DeviceId,
|
||||
axis: AxisId,
|
||||
value: f64,
|
||||
},
|
||||
|
||||
/// The window needs to be redrawn.
|
||||
Refresh,
|
||||
/// The OS or application has requested that the window be redrawn.
|
||||
RedrawRequested,
|
||||
|
||||
/// Touch event has been received
|
||||
Touch(Touch),
|
||||
@@ -113,10 +208,29 @@ pub enum WindowEvent {
|
||||
/// * Changing the display's DPI factor (e.g. in Control Panel on Windows).
|
||||
/// * Moving the window to a display with a different DPI factor.
|
||||
///
|
||||
/// For more information about DPI in general, see the [`dpi`](dpi/index.html) module.
|
||||
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
|
||||
HiDpiFactorChanged(f64),
|
||||
}
|
||||
|
||||
/// Identifier of an input device.
|
||||
///
|
||||
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
|
||||
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
|
||||
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
|
||||
|
||||
impl DeviceId {
|
||||
/// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return
|
||||
/// value of this function is that it will always be equal to itself and to future values returned
|
||||
/// by this function. No other guarantees are made. This may be equal to a real `DeviceId`.
|
||||
///
|
||||
/// **Passing this into a winit function will result in undefined behavior.**
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId(platform_impl::DeviceId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents raw hardware events that are not associated with any particular window.
|
||||
///
|
||||
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
|
||||
@@ -148,11 +262,27 @@ pub enum DeviceEvent {
|
||||
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
|
||||
/// that winit supports on this platform, including mouse devices. If the device is a mouse
|
||||
/// device then this will be reported alongside the MouseMotion event.
|
||||
Motion { axis: AxisId, value: f64 },
|
||||
Motion {
|
||||
axis: AxisId,
|
||||
value: f64,
|
||||
},
|
||||
|
||||
Button {
|
||||
button: ButtonId,
|
||||
state: ElementState,
|
||||
},
|
||||
|
||||
Button { button: ButtonId, state: ElementState },
|
||||
Key(KeyboardInput),
|
||||
Text { codepoint: char },
|
||||
|
||||
/// Keyboard modifiers have changed
|
||||
#[doc(hidden)]
|
||||
ModifiersChanged {
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
Text {
|
||||
codepoint: char,
|
||||
},
|
||||
}
|
||||
|
||||
/// Describes a keyboard input event.
|
||||
@@ -178,7 +308,7 @@ pub struct KeyboardInput {
|
||||
///
|
||||
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
|
||||
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
|
||||
pub modifiers: ModifiersState
|
||||
pub modifiers: ModifiersState,
|
||||
}
|
||||
|
||||
/// Describes touch-screen input state.
|
||||
@@ -188,31 +318,95 @@ pub enum TouchPhase {
|
||||
Started,
|
||||
Moved,
|
||||
Ended,
|
||||
Cancelled
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
/// Represents touch event
|
||||
/// Represents a touch event
|
||||
///
|
||||
/// Every time user touches screen new Start event with some finger id is generated.
|
||||
/// When the finger is removed from the screen End event with same id is generated.
|
||||
/// Every time the user touches the screen, a new `Start` event with an unique
|
||||
/// identifier for the finger is generated. When the finger is lifted, an `End`
|
||||
/// event is generated with the same finger id.
|
||||
///
|
||||
/// For every id there will be at least 2 events with phases Start and End (or Cancelled).
|
||||
/// There may be 0 or more Move events.
|
||||
/// After a `Start` event has been emitted, there may be zero or more `Move`
|
||||
/// events when the finger is moved or the touch pressure changes.
|
||||
///
|
||||
/// The finger id may be reused by the system after an `End` event. The user
|
||||
/// should assume that a new `Start` event received with the same id has nothing
|
||||
/// to do with the old finger and is a new finger.
|
||||
///
|
||||
/// Depending on platform implementation id may or may not be reused by system after End event.
|
||||
///
|
||||
/// Gesture regonizer using this event should assume that Start event received with same id
|
||||
/// as previously received End event is a new finger and has nothing to do with an old one.
|
||||
///
|
||||
/// Touch may be cancelled if for example window lost focus.
|
||||
/// A `Cancelled` event is emitted when the system has canceled tracking this
|
||||
/// touch, such as when the window loses focus, or on iOS if the user moves the
|
||||
/// device against their face.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Touch {
|
||||
pub device_id: DeviceId,
|
||||
pub phase: TouchPhase,
|
||||
pub location: LogicalPosition,
|
||||
/// unique identifier of a finger.
|
||||
pub id: u64
|
||||
/// Describes how hard the screen was pressed. May be `None` if the platform
|
||||
/// does not support pressure sensitivity.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
|
||||
pub force: Option<Force>,
|
||||
/// Unique identifier of a finger.
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
/// Describes the force of a touch event
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Force {
|
||||
/// On iOS, the force is calibrated so that the same number corresponds to
|
||||
/// roughly the same amount of pressure on the screen regardless of the
|
||||
/// device.
|
||||
Calibrated {
|
||||
/// The force of the touch, where a value of 1.0 represents the force of
|
||||
/// an average touch (predetermined by the system, not user-specific).
|
||||
///
|
||||
/// The force reported by Apple Pencil is measured along the axis of the
|
||||
/// pencil. If you want a force perpendicular to the device, you need to
|
||||
/// calculate this value using the `altitude_angle` value.
|
||||
force: f64,
|
||||
/// The maximum possible force for a touch.
|
||||
///
|
||||
/// The value of this field is sufficiently high to provide a wide
|
||||
/// dynamic range for values of the `force` field.
|
||||
max_possible_force: f64,
|
||||
/// The altitude (in radians) of the stylus.
|
||||
///
|
||||
/// A value of 0 radians indicates that the stylus is parallel to the
|
||||
/// surface. The value of this property is Pi/2 when the stylus is
|
||||
/// perpendicular to the surface.
|
||||
altitude_angle: Option<f64>,
|
||||
},
|
||||
/// If the platform reports the force as normalized, we have no way of
|
||||
/// knowing how much pressure 1.0 corresponds to – we know it's the maximum
|
||||
/// amount of force, but as to how much force, you might either have to
|
||||
/// press really really hard, or not hard at all, depending on the device.
|
||||
Normalized(f64),
|
||||
}
|
||||
|
||||
impl Force {
|
||||
/// Returns the force normalized to the range between 0.0 and 1.0 inclusive.
|
||||
/// Instead of normalizing the force, you should prefer to handle
|
||||
/// `Force::Calibrated` so that the amount of force the user has to apply is
|
||||
/// consistent across devices.
|
||||
pub fn normalized(&self) -> f64 {
|
||||
match self {
|
||||
Force::Calibrated {
|
||||
force,
|
||||
max_possible_force,
|
||||
altitude_angle,
|
||||
} => {
|
||||
let force = match altitude_angle {
|
||||
Some(altitude_angle) => force / altitude_angle.sin(),
|
||||
None => *force,
|
||||
};
|
||||
force / max_possible_force
|
||||
}
|
||||
Force::Normalized(force) => *force,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hardware-dependent keyboard scan code.
|
||||
@@ -246,19 +440,19 @@ pub enum MouseButton {
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum MouseScrollDelta {
|
||||
/// Amount in lines or rows to scroll in the horizontal
|
||||
/// and vertical directions.
|
||||
///
|
||||
/// Positive values indicate movement forward
|
||||
/// (away from the user) or rightwards.
|
||||
LineDelta(f32, f32),
|
||||
/// Amount in pixels to scroll in the horizontal and
|
||||
/// vertical direction.
|
||||
///
|
||||
/// Scroll events are expressed as a PixelDelta if
|
||||
/// supported by the device (eg. a touchpad) and
|
||||
/// platform.
|
||||
PixelDelta(LogicalPosition),
|
||||
/// Amount in lines or rows to scroll in the horizontal
|
||||
/// and vertical directions.
|
||||
///
|
||||
/// Positive values indicate movement forward
|
||||
/// (away from the user) or rightwards.
|
||||
LineDelta(f32, f32),
|
||||
/// Amount in pixels to scroll in the horizontal and
|
||||
/// vertical direction.
|
||||
///
|
||||
/// Scroll events are expressed as a PixelDelta if
|
||||
/// supported by the device (eg. a touchpad) and
|
||||
/// platform.
|
||||
PixelDelta(LogicalPosition),
|
||||
}
|
||||
|
||||
/// Symbolic name for a keyboard key.
|
||||
@@ -418,7 +612,7 @@ pub enum VirtualKeyCode {
|
||||
Multiply,
|
||||
Mute,
|
||||
MyComputer,
|
||||
NavigateForward, // also called "Prior"
|
||||
NavigateForward, // also called "Prior"
|
||||
NavigateBackward, // also called "Next"
|
||||
NextTrack,
|
||||
NoConvert,
|
||||
@@ -476,5 +670,5 @@ pub struct ModifiersState {
|
||||
/// The "logo" key
|
||||
///
|
||||
/// This is the "windows" key on PC and "command" key on Mac.
|
||||
pub logo: bool
|
||||
pub logo: bool,
|
||||
}
|
||||
228
src/event_loop.rs
Normal file
228
src/event_loop.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
//! The `EventLoop` struct and assorted supporting types, including `ControlFlow`.
|
||||
//!
|
||||
//! If you want to send custom events to the event loop, use [`EventLoop::create_proxy()`][create_proxy]
|
||||
//! to acquire an [`EventLoopProxy`][event_loop_proxy] and call its [`send_event`][send_event] method.
|
||||
//!
|
||||
//! See the root-level documentation for information on how to create and use an event loop to
|
||||
//! handle events.
|
||||
//!
|
||||
//! [create_proxy]: crate::event_loop::EventLoop::create_proxy
|
||||
//! [event_loop_proxy]: crate::event_loop::EventLoopProxy
|
||||
//! [send_event]: crate::event_loop::EventLoopProxy::send_event
|
||||
use instant::Instant;
|
||||
use std::ops::Deref;
|
||||
use std::{error, fmt};
|
||||
|
||||
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
|
||||
|
||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
||||
/// the events loop.
|
||||
///
|
||||
/// An `EventLoop` can be seen more or less as a "context". Calling `EventLoop::new()`
|
||||
/// initializes everything that will be required to create windows. For example on Linux creating
|
||||
/// an event loop opens a connection to the X or Wayland server.
|
||||
///
|
||||
/// To wake up an `EventLoop` from a another thread, see the `EventLoopProxy` docs.
|
||||
///
|
||||
/// Note that the `EventLoop` cannot be shared across threads (due to platform-dependant logic
|
||||
/// forbidding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the
|
||||
/// `Window` created from this `EventLoop` _can_ be sent to an other thread, and the
|
||||
/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread.
|
||||
///
|
||||
pub struct EventLoop<T: 'static> {
|
||||
pub(crate) event_loop: platform_impl::EventLoop<T>,
|
||||
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
|
||||
}
|
||||
|
||||
/// Target that associates windows with an `EventLoop`.
|
||||
///
|
||||
/// This type exists to allow you to create new windows while Winit executes
|
||||
/// your callback. `EventLoop` will coerce into this type (`impl<T> Deref for
|
||||
/// EventLoop<T>`), so functions that take this as a parameter can also take
|
||||
/// `&EventLoop`.
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
|
||||
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for EventLoop<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("EventLoop { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("EventLoopWindowTarget { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
/// Set by the user callback given to the `EventLoop::run` method.
|
||||
///
|
||||
/// Indicates the desired behavior of the event loop after [`Event::EventsCleared`][events_cleared]
|
||||
/// is emitted. Defaults to `Poll`.
|
||||
///
|
||||
/// ## Persistency
|
||||
/// Almost every change is persistent between multiple calls to the event loop closure within a
|
||||
/// given run loop. The only exception to this is `Exit` which, once set, cannot be unset. Changes
|
||||
/// are **not** persistent between multiple calls to `run_return` - issuing a new call will reset
|
||||
/// the control flow to `Poll`.
|
||||
///
|
||||
/// [events_cleared]: crate::event::Event::EventsCleared
|
||||
#[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.
|
||||
Poll,
|
||||
/// When the current loop iteration finishes, suspend the thread until another event arrives.
|
||||
Wait,
|
||||
/// When the current loop iteration finishes, suspend the thread until either another event
|
||||
/// arrives or the given time is reached.
|
||||
WaitUntil(Instant),
|
||||
/// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set,
|
||||
/// `control_flow` cannot be changed from `Exit`, and any future attempts to do so will result
|
||||
/// in the `control_flow` parameter being reset to `Exit`.
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl Default for ControlFlow {
|
||||
#[inline(always)]
|
||||
fn default() -> ControlFlow {
|
||||
ControlFlow::Poll
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop<()> {
|
||||
/// Builds a new event loop with a `()` as the user event type.
|
||||
///
|
||||
/// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.***
|
||||
/// Attempting to create the event loop on a different thread will panic. This restriction isn't
|
||||
/// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
|
||||
/// porting to platforms that require it. `EventLoopExt::new_any_thread` functions are exposed
|
||||
/// in the relevant `platform` module if the target platform supports creating an event loop on
|
||||
/// any thread.
|
||||
///
|
||||
/// Usage will result in display backend initialisation, this can be controlled on linux
|
||||
/// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
|
||||
/// If it is not set, winit will try to connect to a wayland connection, and if it fails will
|
||||
/// fallback on x11. If this variable is set with any other value, winit will panic.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
pub fn new() -> EventLoop<()> {
|
||||
EventLoop::<()>::with_user_event()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoop<T> {
|
||||
/// Builds a new event loop.
|
||||
///
|
||||
/// All caveats documented in [`EventLoop::new`] apply to this function.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
pub fn with_user_event() -> EventLoop<T> {
|
||||
EventLoop {
|
||||
event_loop: platform_impl::EventLoop::new(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Hijacks the calling thread and initializes the winit event loop with the provided
|
||||
/// closure. Since the closure is `'static`, it must be a `move` closure if it needs to
|
||||
/// access any data from the calling context.
|
||||
///
|
||||
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
|
||||
/// event loop's behavior.
|
||||
///
|
||||
/// Any values not passed to this function will *not* be dropped.
|
||||
///
|
||||
/// [`ControlFlow`]: crate::event_loop::ControlFlow
|
||||
#[inline]
|
||||
pub fn run<F>(self, event_handler: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(Event<T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
self.event_loop.run(event_handler)
|
||||
}
|
||||
|
||||
/// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy {
|
||||
event_loop_proxy: self.event_loop.create_proxy(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of all the monitors available on the system.
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
||||
self.event_loop
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(|inner| MonitorHandle { inner })
|
||||
}
|
||||
|
||||
/// Returns the primary monitor of the system.
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle {
|
||||
inner: self.event_loop.primary_monitor(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for EventLoop<T> {
|
||||
type Target = EventLoopWindowTarget<T>;
|
||||
fn deref(&self) -> &EventLoopWindowTarget<T> {
|
||||
self.event_loop.window_target()
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to send custom events to `EventLoop`.
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
event_loop_proxy: platform_impl::EventLoopProxy<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
event_loop_proxy: self.event_loop_proxy.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopProxy<T> {
|
||||
/// Send an event to the `EventLoop` from which this proxy was created. This emits a
|
||||
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
|
||||
/// function.
|
||||
///
|
||||
/// Returns an `Err` if the associated `EventLoop` no longer exists.
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
self.event_loop_proxy.send_event(event)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("EventLoopProxy { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
/// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that
|
||||
/// no longer exists. Contains the original event given to `send_event`.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct EventLoopClosed<T>(pub T);
|
||||
|
||||
impl<T: fmt::Debug> fmt::Display for EventLoopClosed<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", error::Error::description(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {
|
||||
fn description(&self) -> &str {
|
||||
"Tried to wake up a closed `EventLoop`"
|
||||
}
|
||||
}
|
||||
100
src/icon.rs
100
src/icon.rs
@@ -1,12 +1,4 @@
|
||||
use std::{fmt, mem};
|
||||
use std::error::Error;
|
||||
#[cfg(feature = "icon_loading")]
|
||||
use std::io::{BufRead, Seek};
|
||||
#[cfg(feature = "icon_loading")]
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
use image;
|
||||
use std::{error::Error, fmt, mem};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
@@ -24,9 +16,7 @@ pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
||||
pub enum BadIcon {
|
||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
||||
/// safely interpreted as 32bpp RGBA pixels.
|
||||
ByteCountNotDivisibleBy4 {
|
||||
byte_count: usize,
|
||||
},
|
||||
ByteCountNotDivisibleBy4 { byte_count: usize },
|
||||
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
|
||||
/// At least one of your arguments is incorrect.
|
||||
DimensionsVsPixelCount {
|
||||
@@ -38,7 +28,7 @@ pub enum BadIcon {
|
||||
}
|
||||
|
||||
impl fmt::Display for BadIcon {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let msg = match self {
|
||||
&BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!(
|
||||
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
|
||||
@@ -54,7 +44,7 @@ impl fmt::Display for BadIcon {
|
||||
width, height, pixel_count, width_x_height,
|
||||
),
|
||||
};
|
||||
write!(formatter, "{}", msg)
|
||||
write!(f, "{}", msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,17 +53,13 @@ impl Error for BadIcon {
|
||||
"A valid icon cannot be created from these arguments"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// An icon used for the window titlebar, taskbar, etc.
|
||||
///
|
||||
/// Enabling the `icon_loading` feature provides you with several convenience methods for creating
|
||||
/// an `Icon` from any format supported by the [image](https://github.com/PistonDevelopers/image)
|
||||
/// crate.
|
||||
pub struct Icon {
|
||||
pub(crate) rgba: Vec<u8>,
|
||||
pub(crate) width: u32,
|
||||
@@ -87,7 +73,9 @@ impl Icon {
|
||||
/// `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() });
|
||||
return Err(BadIcon::ByteCountNotDivisibleBy4 {
|
||||
byte_count: rgba.len(),
|
||||
});
|
||||
}
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
if pixel_count != (width * height) as usize {
|
||||
@@ -98,73 +86,11 @@ impl Icon {
|
||||
pixel_count,
|
||||
})
|
||||
} else {
|
||||
Ok(Icon { rgba, width, height })
|
||||
Ok(Icon {
|
||||
rgba,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Loads an `Icon` from the path of an image on the filesystem.
|
||||
///
|
||||
/// Requires the `icon_loading` feature.
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> image::ImageResult<Self> {
|
||||
image::open(path).map(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Loads an `Icon` from anything implementing `BufRead` and `Seek`.
|
||||
///
|
||||
/// Requires the `icon_loading` feature.
|
||||
pub fn from_reader<R: BufRead + Seek>(
|
||||
reader: R,
|
||||
format: image::ImageFormat,
|
||||
) -> image::ImageResult<Self> {
|
||||
image::load(reader, format).map(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Loads an `Icon` from the unprocessed bytes of an image file.
|
||||
/// Uses heuristics to determine format.
|
||||
///
|
||||
/// Requires the `icon_loading` feature.
|
||||
pub fn from_bytes(bytes: &[u8]) -> image::ImageResult<Self> {
|
||||
image::load_from_memory(bytes).map(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Loads an `Icon` from the unprocessed bytes of an image.
|
||||
///
|
||||
/// Requires the `icon_loading` feature.
|
||||
pub fn from_bytes_with_format(
|
||||
bytes: &[u8],
|
||||
format: image::ImageFormat,
|
||||
) -> image::ImageResult<Self> {
|
||||
image::load_from_memory_with_format(bytes, format).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Requires the `icon_loading` feature.
|
||||
impl From<image::DynamicImage> for Icon {
|
||||
fn from(image: image::DynamicImage) -> Self {
|
||||
use image::{GenericImageView, Pixel};
|
||||
let (width, height) = image.dimensions();
|
||||
let mut rgba = Vec::with_capacity((width * height) as usize * PIXEL_SIZE);
|
||||
for (_, _, pixel) in image.pixels() {
|
||||
rgba.extend_from_slice(&pixel.to_rgba().data);
|
||||
}
|
||||
Icon { rgba, width, height }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Requires the `icon_loading` feature.
|
||||
impl From<image::RgbaImage> for Icon {
|
||||
fn from(buf: image::RgbaImage) -> Self {
|
||||
let (width, height) = buf.dimensions();
|
||||
let mut rgba = Vec::with_capacity((width * height) as usize * PIXEL_SIZE);
|
||||
for (_, _, pixel) in buf.enumerate_pixels() {
|
||||
rgba.extend_from_slice(&pixel.data);
|
||||
}
|
||||
Icon { rgba, width, height }
|
||||
}
|
||||
}
|
||||
|
||||
589
src/lib.rs
589
src/lib.rs
@@ -2,541 +2,142 @@
|
||||
//!
|
||||
//! # Building a window
|
||||
//!
|
||||
//! Before you can build a window, you first need to build an `EventsLoop`. This is done with the
|
||||
//! `EventsLoop::new()` function. Example:
|
||||
//! Before you can build a [`Window`], you first need to build an [`EventLoop`]. This is done with the
|
||||
//! [`EventLoop::new()`] function.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use winit::EventsLoop;
|
||||
//! let events_loop = EventsLoop::new();
|
||||
//! use winit::event_loop::EventLoop;
|
||||
//! let event_loop = EventLoop::new();
|
||||
//! ```
|
||||
//!
|
||||
//! Once this is done there are two ways to create a window:
|
||||
//! Once this is done there are two ways to create a [`Window`]:
|
||||
//!
|
||||
//! - Calling `Window::new(&events_loop)`.
|
||||
//! - Calling `let builder = WindowBuilder::new()` then `builder.build(&events_loop)`.
|
||||
//! - Calling [`Window::new(&event_loop)`][window_new].
|
||||
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
|
||||
//!
|
||||
//! The first way is the 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 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`].
|
||||
//!
|
||||
//! # Events handling
|
||||
//! # Event handling
|
||||
//!
|
||||
//! Once a window has been created, it will *generate events*. For example whenever the user moves
|
||||
//! the window, resizes the window, moves the mouse, etc. an event is generated.
|
||||
//! 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.
|
||||
//!
|
||||
//! The events generated by a window can be retrieved from the `EventsLoop` the window was created
|
||||
//! with.
|
||||
//! Events can be retreived by using an [`EventLoop`]. A [`Window`] will send its events to the
|
||||
//! [`EventLoop`] object it was created with.
|
||||
//!
|
||||
//! There are two ways to do so. The first is to call `events_loop.poll_events(...)`, which will
|
||||
//! retrieve all the events pending on the windows and immediately return after no new event is
|
||||
//! available. You usually want to use this method in application that render continuously on the
|
||||
//! screen, such as video games.
|
||||
//! 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, WindowEvent};
|
||||
//! use winit::dpi::LogicalSize;
|
||||
//! # use winit::EventsLoop;
|
||||
//! # let mut events_loop = EventsLoop::new();
|
||||
//! use winit::{
|
||||
//! event::{Event, WindowEvent},
|
||||
//! event_loop::{ControlFlow, EventLoop},
|
||||
//! window::WindowBuilder,
|
||||
//! };
|
||||
//!
|
||||
//! loop {
|
||||
//! events_loop.poll_events(|event| {
|
||||
//! match event {
|
||||
//! Event::WindowEvent {
|
||||
//! event: WindowEvent::Resized(LogicalSize { width, height }),
|
||||
//! ..
|
||||
//! } => {
|
||||
//! println!("The window was resized to {}x{}", width, height);
|
||||
//! },
|
||||
//! _ => ()
|
||||
//! }
|
||||
//! });
|
||||
//! }
|
||||
//! ```
|
||||
//! let event_loop = EventLoop::new();
|
||||
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
//!
|
||||
//! The second way is to call `events_loop.run_forever(...)`. As its name tells, it will run
|
||||
//! forever unless it is stopped by returning `ControlFlow::Break`.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use winit::{ControlFlow, Event, WindowEvent};
|
||||
//! # use winit::EventsLoop;
|
||||
//! # let mut events_loop = EventsLoop::new();
|
||||
//!
|
||||
//! events_loop.run_forever(|event| {
|
||||
//! event_loop.run(move |event, _, control_flow| {
|
||||
//! match event {
|
||||
//! Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
|
||||
//! println!("The close button was pressed; stopping");
|
||||
//! ControlFlow::Break
|
||||
//! Event::EventsCleared => {
|
||||
//! // Application update code.
|
||||
//!
|
||||
//! // Queue a RedrawRequested event.
|
||||
//! window.request_redraw();
|
||||
//! },
|
||||
//! _ => ControlFlow::Continue,
|
||||
//! 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
|
||||
//! },
|
||||
//! // 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,
|
||||
//! }
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! If you use multiple windows, the `WindowEvent` event has a member named `window_id`. You can
|
||||
//! compare it with the value returned by the `id()` method of `Window` in order to know which
|
||||
//! window has received the event.
|
||||
//! 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.
|
||||
//!
|
||||
//! # 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 `os` module for that), which in turn allows you
|
||||
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the window.
|
||||
//! Winit doesn't 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`].
|
||||
//!
|
||||
//! [`EventLoop`]: event_loop::EventLoop
|
||||
//! [`EventLoop::new()`]: event_loop::EventLoop::new
|
||||
//! [event_loop_run]: event_loop::EventLoop::run
|
||||
//! [`ControlFlow`]: event_loop::ControlFlow
|
||||
//! [`Exit`]: event_loop::ControlFlow::Exit
|
||||
//! [`Window`]: window::Window
|
||||
//! [`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
|
||||
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "icon_loading")]
|
||||
extern crate image;
|
||||
#[cfg(feature = "serde")]
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate winapi;
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate backtrace;
|
||||
#[macro_use]
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "ios", target_os = "windows"))]
|
||||
extern crate bitflags;
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
#[cfg(target_os = "macos")]
|
||||
extern crate cocoa;
|
||||
#[cfg(target_os = "macos")]
|
||||
extern crate core_foundation;
|
||||
#[cfg(target_os = "macos")]
|
||||
extern crate core_graphics;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
extern crate x11_dl;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
extern crate parking_lot;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
extern crate percent_encoding;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
extern crate smithay_client_toolkit as sctk;
|
||||
|
||||
pub(crate) use dpi::*; // TODO: Actually change the imports throughout the codebase.
|
||||
pub use events::*;
|
||||
pub use window::{AvailableMonitorsIter, MonitorId};
|
||||
pub use icon::*;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "std_web"))]
|
||||
extern crate std_web as stdweb;
|
||||
|
||||
pub mod dpi;
|
||||
mod events;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
pub mod event;
|
||||
pub mod event_loop;
|
||||
mod icon;
|
||||
mod platform;
|
||||
mod window;
|
||||
pub mod monitor;
|
||||
mod platform_impl;
|
||||
pub mod window;
|
||||
|
||||
pub mod os;
|
||||
|
||||
/// Represents a window.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use winit::{Event, EventsLoop, Window, WindowEvent, ControlFlow};
|
||||
///
|
||||
/// let mut events_loop = EventsLoop::new();
|
||||
/// let window = Window::new(&events_loop).unwrap();
|
||||
///
|
||||
/// events_loop.run_forever(|event| {
|
||||
/// match event {
|
||||
/// Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
|
||||
/// ControlFlow::Break
|
||||
/// },
|
||||
/// _ => ControlFlow::Continue,
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
pub struct Window {
|
||||
window: platform::Window,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Window {
|
||||
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmtr.pad("Window { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier of a window. Unique for each window.
|
||||
///
|
||||
/// Can be obtained with `window.id()`.
|
||||
///
|
||||
/// Whenever you receive an event specific to a window, this event contains a `WindowId` which you
|
||||
/// can then compare to the ids of your windows.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(platform::WindowId);
|
||||
|
||||
impl WindowId {
|
||||
/// Returns a dummy `WindowId`, useful for unit testing. The only guarantee made about the return
|
||||
/// value of this function is that it will always be equal to itself and to future values returned
|
||||
/// by this function. No other guarantees are made. This may be equal to a real `WindowId`.
|
||||
///
|
||||
/// **Passing this into a winit function will result in undefined behavior.**
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId(platform::WindowId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier of an input device.
|
||||
///
|
||||
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
|
||||
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
|
||||
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(platform::DeviceId);
|
||||
|
||||
impl DeviceId {
|
||||
/// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return
|
||||
/// value of this function is that it will always be equal to itself and to future values returned
|
||||
/// by this function. No other guarantees are made. This may be equal to a real `DeviceId`.
|
||||
///
|
||||
/// **Passing this into a winit function will result in undefined behavior.**
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId(platform::DeviceId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
||||
/// the events loop.
|
||||
///
|
||||
/// An `EventsLoop` can be seen more or less as a "context". Calling `EventsLoop::new()`
|
||||
/// initializes everything that will be required to create windows. For example on Linux creating
|
||||
/// an events loop opens a connection to the X or Wayland server.
|
||||
///
|
||||
/// To wake up an `EventsLoop` from a another thread, see the `EventsLoopProxy` docs.
|
||||
///
|
||||
/// Note that the `EventsLoop` cannot be shared accross threads (due to platform-dependant logic
|
||||
/// forbiding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the
|
||||
/// `Window` created from this `EventsLoop` _can_ be sent to an other thread, and the
|
||||
/// `EventsLoopProxy` allows you to wakeup an `EventsLoop` from an other thread.
|
||||
pub struct EventsLoop {
|
||||
events_loop: platform::EventsLoop,
|
||||
_marker: ::std::marker::PhantomData<*mut ()> // Not Send nor Sync
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EventsLoop {
|
||||
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmtr.pad("EventsLoop { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returned by the user callback given to the `EventsLoop::run_forever` method.
|
||||
///
|
||||
/// Indicates whether the `run_forever` method should continue or complete.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ControlFlow {
|
||||
/// Continue looping and waiting for events.
|
||||
Continue,
|
||||
/// Break from the event loop.
|
||||
Break,
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
/// Builds a new events loop.
|
||||
///
|
||||
/// Usage will result in display backend initialisation, this can be controlled on linux
|
||||
/// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
|
||||
/// If it is not set, winit will try to connect to a wayland connection, and if it fails will
|
||||
/// fallback on x11. If this variable is set with any other value, winit will panic.
|
||||
pub fn new() -> EventsLoop {
|
||||
EventsLoop {
|
||||
events_loop: platform::EventsLoop::new(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of all the monitors available on the system.
|
||||
///
|
||||
// Note: should be replaced with `-> impl Iterator` once stable.
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> AvailableMonitorsIter {
|
||||
let data = self.events_loop.get_available_monitors();
|
||||
AvailableMonitorsIter{ data: data.into_iter() }
|
||||
}
|
||||
|
||||
/// Returns the primary monitor of the system.
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId { inner: self.events_loop.get_primary_monitor() }
|
||||
}
|
||||
|
||||
/// Fetches all the events that are pending, calls the callback function for each of them,
|
||||
/// and returns.
|
||||
#[inline]
|
||||
pub fn poll_events<F>(&mut self, callback: F)
|
||||
where F: FnMut(Event)
|
||||
{
|
||||
self.events_loop.poll_events(callback)
|
||||
}
|
||||
|
||||
/// Calls `callback` every time an event is received. If no event is available, sleeps the
|
||||
/// current thread and waits for an event. If the callback returns `ControlFlow::Break` then
|
||||
/// `run_forever` will immediately return.
|
||||
///
|
||||
/// # Danger!
|
||||
///
|
||||
/// The callback is run after *every* event, so if its execution time is non-trivial the event queue may not empty
|
||||
/// at a sufficient rate. Rendering in the callback with vsync enabled **will** cause significant lag.
|
||||
#[inline]
|
||||
pub fn run_forever<F>(&mut self, callback: F)
|
||||
where F: FnMut(Event) -> ControlFlow
|
||||
{
|
||||
self.events_loop.run_forever(callback)
|
||||
}
|
||||
|
||||
/// Creates an `EventsLoopProxy` that can be used to wake up the `EventsLoop` from another
|
||||
/// thread.
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
EventsLoopProxy {
|
||||
events_loop_proxy: self.events_loop.create_proxy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to wake up the `EventsLoop` from another thread.
|
||||
#[derive(Clone)]
|
||||
pub struct EventsLoopProxy {
|
||||
events_loop_proxy: platform::EventsLoopProxy,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EventsLoopProxy {
|
||||
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmtr.pad("EventsLoopProxy { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
/// Wake up the `EventsLoop` from which this proxy was created.
|
||||
///
|
||||
/// This causes the `EventsLoop` to emit an `Awakened` event.
|
||||
///
|
||||
/// Returns an `Err` if the associated `EventsLoop` no longer exists.
|
||||
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
|
||||
self.events_loop_proxy.wakeup()
|
||||
}
|
||||
}
|
||||
|
||||
/// The error that is returned when an `EventsLoopProxy` attempts to wake up an `EventsLoop` that
|
||||
/// no longer exists.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct EventsLoopClosed;
|
||||
|
||||
impl std::fmt::Display for EventsLoopClosed {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", std::error::Error::description(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for EventsLoopClosed {
|
||||
fn description(&self) -> &str {
|
||||
"Tried to wake up a closed `EventsLoop`"
|
||||
}
|
||||
}
|
||||
|
||||
/// Object that allows you to build windows.
|
||||
#[derive(Clone)]
|
||||
pub struct WindowBuilder {
|
||||
/// The attributes to use to create the window.
|
||||
pub window: WindowAttributes,
|
||||
|
||||
// Platform-specific configuration. Private.
|
||||
platform_specific: platform::PlatformSpecificWindowBuilderAttributes,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for WindowBuilder {
|
||||
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmtr.debug_struct("WindowBuilder")
|
||||
.field("window", &self.window)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can happen while creating a window or a headless renderer.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CreationError {
|
||||
OsError(String),
|
||||
/// TODO: remove this error
|
||||
NotSupported,
|
||||
}
|
||||
|
||||
impl CreationError {
|
||||
fn to_string(&self) -> &str {
|
||||
match *self {
|
||||
CreationError::OsError(ref text) => &text,
|
||||
CreationError::NotSupported => "Some of the requested attributes are not supported",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CreationError {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
formatter.write_str(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CreationError {
|
||||
fn description(&self) -> &str {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the appearance of the mouse cursor.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum MouseCursor {
|
||||
/// The platform-dependent default cursor.
|
||||
Default,
|
||||
/// A simple crosshair.
|
||||
Crosshair,
|
||||
/// A hand (often used to indicate links in web browsers).
|
||||
Hand,
|
||||
/// Self explanatory.
|
||||
Arrow,
|
||||
/// Indicates something is to be moved.
|
||||
Move,
|
||||
/// Indicates text that may be selected or edited.
|
||||
Text,
|
||||
/// Program busy indicator.
|
||||
Wait,
|
||||
/// Help indicator (often rendered as a "?")
|
||||
Help,
|
||||
/// Progress indicator. Shows that processing is being done. But in contrast
|
||||
/// with "Wait" the user may still interact with the program. Often rendered
|
||||
/// as a spinning beach ball, or an arrow with a watch or hourglass.
|
||||
Progress,
|
||||
|
||||
/// Cursor showing that something cannot be done.
|
||||
NotAllowed,
|
||||
ContextMenu,
|
||||
Cell,
|
||||
VerticalText,
|
||||
Alias,
|
||||
Copy,
|
||||
NoDrop,
|
||||
Grab,
|
||||
Grabbing,
|
||||
AllScroll,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
|
||||
/// Indicate that some edge is to be moved. For example, the 'SeResize' cursor
|
||||
/// is used when the movement starts from the south-east corner of the box.
|
||||
EResize,
|
||||
NResize,
|
||||
NeResize,
|
||||
NwResize,
|
||||
SResize,
|
||||
SeResize,
|
||||
SwResize,
|
||||
WResize,
|
||||
EwResize,
|
||||
NsResize,
|
||||
NeswResize,
|
||||
NwseResize,
|
||||
ColResize,
|
||||
RowResize,
|
||||
}
|
||||
|
||||
impl Default for MouseCursor {
|
||||
fn default() -> Self {
|
||||
MouseCursor::Default
|
||||
}
|
||||
}
|
||||
|
||||
/// Attributes to use when creating a window.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WindowAttributes {
|
||||
/// The dimensions of the window. If this is `None`, some platform-specific dimensions will be
|
||||
/// used.
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub dimensions: Option<LogicalSize>,
|
||||
|
||||
/// The minimum dimensions a window can be, If this is `None`, the window will have no minimum dimensions (aside from reserved).
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub min_dimensions: Option<LogicalSize>,
|
||||
|
||||
/// The maximum dimensions a window can be, If this is `None`, the maximum will have no maximum or will be set to the primary monitor's dimensions by the platform.
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub max_dimensions: Option<LogicalSize>,
|
||||
|
||||
/// Whether the window is resizable or not.
|
||||
///
|
||||
/// The default is `true`.
|
||||
pub resizable: bool,
|
||||
|
||||
/// Whether the window should be set as fullscreen upon creation.
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub fullscreen: Option<MonitorId>,
|
||||
|
||||
/// The title of the window in the title bar.
|
||||
///
|
||||
/// The default is `"winit window"`.
|
||||
pub title: String,
|
||||
|
||||
/// Whether the window should be maximized upon creation.
|
||||
///
|
||||
/// The default is `false`.
|
||||
pub maximized: bool,
|
||||
|
||||
/// Whether the window should be immediately visible upon creation.
|
||||
///
|
||||
/// The default is `true`.
|
||||
pub visible: bool,
|
||||
|
||||
/// Whether the the window should be transparent. If this is true, writing colors
|
||||
/// with alpha values different than `1.0` will produce a transparent window.
|
||||
///
|
||||
/// The default is `false`.
|
||||
pub transparent: bool,
|
||||
|
||||
/// Whether the window should have borders and bars.
|
||||
///
|
||||
/// The default is `true`.
|
||||
pub decorations: bool,
|
||||
|
||||
/// Whether the window should always be on top of other windows.
|
||||
///
|
||||
/// The default is `false`.
|
||||
pub always_on_top: bool,
|
||||
|
||||
/// The window icon.
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub window_icon: Option<Icon>,
|
||||
|
||||
/// [iOS only] Enable multitouch,
|
||||
/// see [multipleTouchEnabled](https://developer.apple.com/documentation/uikit/uiview/1622519-multipletouchenabled)
|
||||
pub multitouch: bool,
|
||||
}
|
||||
|
||||
impl Default for WindowAttributes {
|
||||
#[inline]
|
||||
fn default() -> WindowAttributes {
|
||||
WindowAttributes {
|
||||
dimensions: None,
|
||||
min_dimensions: None,
|
||||
max_dimensions: None,
|
||||
resizable: true,
|
||||
title: "winit window".to_owned(),
|
||||
maximized: false,
|
||||
fullscreen: None,
|
||||
visible: true,
|
||||
transparent: false,
|
||||
decorations: true,
|
||||
always_on_top: false,
|
||||
window_icon: None,
|
||||
multitouch: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub mod platform;
|
||||
|
||||
174
src/monitor.rs
Normal file
174
src/monitor.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
//! Types useful for interacting with a user's monitors.
|
||||
//!
|
||||
//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle]
|
||||
//! type. This is retreived from one of the following methods, which return an iterator of
|
||||
//! [`MonitorHandle`][monitor_handle]:
|
||||
//! - [`EventLoop::available_monitors`][loop_get]
|
||||
//! - [`Window::available_monitors`][window_get].
|
||||
//!
|
||||
//! [monitor_handle]: crate::monitor::MonitorHandle
|
||||
//! [loop_get]: crate::event_loop::EventLoop::available_monitors
|
||||
//! [window_get]: crate::window::Window::available_monitors
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
platform_impl,
|
||||
};
|
||||
|
||||
/// Describes a fullscreen video mode of a monitor.
|
||||
///
|
||||
/// Can be acquired with:
|
||||
/// - [`MonitorHandle::video_modes`][monitor_get].
|
||||
///
|
||||
/// [monitor_get]: crate::monitor::MonitorHandle::video_modes
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) video_mode: platform_impl::VideoMode,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VideoMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.video_mode.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for VideoMode {
|
||||
fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for VideoMode {
|
||||
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
|
||||
// TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32`
|
||||
// to `u32` there
|
||||
let size: (u32, u32) = self.size().into();
|
||||
let other_size: (u32, u32) = other.size().into();
|
||||
self.monitor().cmp(&other.monitor()).then(
|
||||
size.cmp(&other_size)
|
||||
.then(
|
||||
self.refresh_rate()
|
||||
.cmp(&other.refresh_rate())
|
||||
.then(self.bit_depth().cmp(&other.bit_depth())),
|
||||
)
|
||||
.reverse(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
/// Returns the resolution of this video mode.
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize {
|
||||
self.video_mode.size()
|
||||
}
|
||||
|
||||
/// Returns the bit depth of this video mode, as in how many bits you have
|
||||
/// available per color. This is generally 24 bits or 32 bits on modern
|
||||
/// systems, depending on whether the alpha channel is counted or not.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Wayland:** Always returns 32.
|
||||
/// - **iOS:** Always returns 32.
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> u16 {
|
||||
self.video_mode.bit_depth()
|
||||
}
|
||||
|
||||
/// Returns the refresh rate of this video mode. **Note**: the returned
|
||||
/// refresh rate is an integer approximation, and you shouldn't rely on this
|
||||
/// value to be exact.
|
||||
#[inline]
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.video_mode.refresh_rate()
|
||||
}
|
||||
|
||||
/// Returns the monitor that this video mode is valid for. Each monitor has
|
||||
/// a separate set of valid video modes.
|
||||
#[inline]
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.video_mode.monitor()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VideoMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}x{} @ {} Hz ({} bpp)",
|
||||
self.size().width,
|
||||
self.size().height,
|
||||
self.refresh_rate(),
|
||||
self.bit_depth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle to a monitor.
|
||||
///
|
||||
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct MonitorHandle {
|
||||
pub(crate) inner: platform_impl::MonitorHandle,
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
/// Returns a human-readable name of the monitor.
|
||||
///
|
||||
/// Returns `None` if the monitor doesn't exist anymore.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Always returns None
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
self.inner.name()
|
||||
}
|
||||
|
||||
/// Returns the monitor's resolution.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Always returns (0,0)
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize {
|
||||
self.inner.size()
|
||||
}
|
||||
|
||||
/// Returns the top-left corner position of the monitor relative to the larger full
|
||||
/// screen area.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Always returns (0,0)
|
||||
#[inline]
|
||||
pub fn position(&self) -> PhysicalPosition {
|
||||
self.inner.position()
|
||||
}
|
||||
|
||||
/// Returns the DPI 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_HIDPI_FACTOR` environment variable.
|
||||
/// - **Android:** Always returns 1.0.
|
||||
/// - **Web:** Always returns 1.0
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f64 {
|
||||
self.inner.hidpi_factor()
|
||||
}
|
||||
|
||||
/// Returns all fullscreen video modes supported by this monitor.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Always returns an empty iterator
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.inner.video_modes()
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#![cfg(any(target_os = "android"))]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
use EventsLoop;
|
||||
use Window;
|
||||
use WindowBuilder;
|
||||
|
||||
/// Additional methods on `EventsLoop` that are specific to Android.
|
||||
pub trait EventsLoopExt {
|
||||
/// Makes it possible for glutin to register a callback when a suspend event happens on Android
|
||||
fn set_suspend_callback(&self, cb: Option<Box<Fn(bool) -> ()>>);
|
||||
}
|
||||
|
||||
impl EventsLoopExt for EventsLoop {
|
||||
fn set_suspend_callback(&self, cb: Option<Box<Fn(bool) -> ()>>) {
|
||||
self.events_loop.set_suspend_callback(cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Android.
|
||||
pub trait WindowExt {
|
||||
fn get_native_window(&self) -> *const c_void;
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
#[inline]
|
||||
fn get_native_window(&self) -> *const c_void {
|
||||
self.window.get_native_window()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Android.
|
||||
pub trait WindowBuilderExt {
|
||||
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#![cfg(target_os = "ios")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use {MonitorId, Window, WindowBuilder};
|
||||
|
||||
/// Additional methods on `Window` that are specific to iOS.
|
||||
pub trait WindowExt {
|
||||
/// Returns a pointer to the `UIWindow` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
fn get_uiwindow(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the `UIView` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
fn get_uiview(&self) -> *mut c_void;
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
#[inline]
|
||||
fn get_uiwindow(&self) -> *mut c_void {
|
||||
self.window.get_uiwindow() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_uiview(&self) -> *mut c_void {
|
||||
self.window.get_uiview() as _
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to iOS.
|
||||
pub trait WindowBuilderExt {
|
||||
/// 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]`
|
||||
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder {
|
||||
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorId` that are specific to iOS.
|
||||
pub trait MonitorIdExt {
|
||||
/// Returns a pointer to the `UIScreen` that is used by this monitor.
|
||||
fn get_uiscreen(&self) -> *mut c_void;
|
||||
}
|
||||
|
||||
impl MonitorIdExt for MonitorId {
|
||||
#[inline]
|
||||
fn get_uiscreen(&self) -> *mut c_void {
|
||||
self.inner.get_uiscreen() as _
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
//! Contains traits with platform-specific methods in them.
|
||||
//!
|
||||
//! Contains the follow modules:
|
||||
//!
|
||||
//! - `android`
|
||||
//! - `ios`
|
||||
//! - `macos`
|
||||
//! - `unix`
|
||||
//! - `windows`
|
||||
//!
|
||||
//! However only the module corresponding to the platform you're compiling to will be available.
|
||||
//!
|
||||
pub mod android;
|
||||
pub mod ios;
|
||||
pub mod macos;
|
||||
pub mod unix;
|
||||
pub mod windows;
|
||||
@@ -1,117 +0,0 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use libc;
|
||||
use winapi::shared::windef::HWND;
|
||||
|
||||
use {DeviceId, EventsLoop, Icon, MonitorId, Window, WindowBuilder};
|
||||
use platform::EventsLoop as WindowsEventsLoop;
|
||||
|
||||
/// Additional methods on `EventsLoop` that are specific to Windows.
|
||||
pub trait EventsLoopExt {
|
||||
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's
|
||||
/// undesirable, you can create an `EventsLoop` using this function instead.
|
||||
fn new_dpi_unaware() -> Self where Self: Sized;
|
||||
}
|
||||
|
||||
impl EventsLoopExt for EventsLoop {
|
||||
#[inline]
|
||||
fn new_dpi_unaware() -> Self {
|
||||
EventsLoop {
|
||||
events_loop: WindowsEventsLoop::with_dpi_awareness(false),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Windows.
|
||||
pub trait WindowExt {
|
||||
/// Returns the native handle that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the native window was destroyed.
|
||||
fn get_hwnd(&self) -> *mut libc::c_void;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
#[inline]
|
||||
fn get_hwnd(&self) -> *mut libc::c_void {
|
||||
self.window.hwnd() as *mut _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
|
||||
self.window.set_taskbar_icon(taskbar_icon)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Windows.
|
||||
pub trait WindowBuilderExt {
|
||||
/// Sets a parent to the window to be created.
|
||||
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
|
||||
|
||||
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
|
||||
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
|
||||
self.platform_specific.parent = Some(parent);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> WindowBuilder {
|
||||
self.platform_specific.taskbar_icon = taskbar_icon;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder {
|
||||
self.platform_specific.no_redirection_bitmap = flag;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorId` that are specific to Windows.
|
||||
pub trait MonitorIdExt {
|
||||
/// Returns the name of the monitor adapter specific to the Win32 API.
|
||||
fn native_id(&self) -> String;
|
||||
|
||||
/// Returns the handle of the monitor - `HMONITOR`.
|
||||
fn hmonitor(&self) -> *mut c_void;
|
||||
}
|
||||
|
||||
impl MonitorIdExt for MonitorId {
|
||||
#[inline]
|
||||
fn native_id(&self) -> String {
|
||||
self.inner.get_native_identifier()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hmonitor(&self) -> *mut c_void {
|
||||
self.inner.get_hmonitor() as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `DeviceId` that are specific to Windows.
|
||||
pub trait DeviceIdExt {
|
||||
/// Returns an identifier that persistently refers to this specific device.
|
||||
///
|
||||
/// Will return `None` if the device is no longer available.
|
||||
fn get_persistent_identifier(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
impl DeviceIdExt for DeviceId {
|
||||
#[inline]
|
||||
fn get_persistent_identifier(&self) -> Option<String> {
|
||||
self.0.get_persistent_identifier()
|
||||
}
|
||||
}
|
||||
33
src/platform/android.rs
Normal file
33
src/platform/android.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
#![cfg(any(target_os = "android"))]
|
||||
|
||||
use crate::{EventLoop, Window, WindowBuilder};
|
||||
use std::os::raw::c_void;
|
||||
|
||||
/// 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) -> ()>>);
|
||||
}
|
||||
|
||||
impl EventLoopExtAndroid for EventLoop {
|
||||
fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>) {
|
||||
self.event_loop.set_suspend_callback(cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Android.
|
||||
pub trait WindowExtAndroid {
|
||||
fn native_window(&self) -> *const c_void;
|
||||
}
|
||||
|
||||
impl WindowExtAndroid for Window {
|
||||
#[inline]
|
||||
fn native_window(&self) -> *const c_void {
|
||||
self.window.native_window()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Android.
|
||||
pub trait WindowBuilderExtAndroid {}
|
||||
|
||||
impl WindowBuilderExtAndroid for WindowBuilder {}
|
||||
@@ -1,108 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use libc;
|
||||
use std::os::raw;
|
||||
|
||||
#[link(name = "android")]
|
||||
#[link(name = "EGL")]
|
||||
#[link(name = "GLESv2")]
|
||||
extern {}
|
||||
|
||||
/**
|
||||
* asset_manager.h
|
||||
*/
|
||||
pub type AAssetManager = raw::c_void;
|
||||
|
||||
/**
|
||||
* native_window.h
|
||||
*/
|
||||
pub type ANativeWindow = raw::c_void;
|
||||
|
||||
extern {
|
||||
pub fn ANativeWindow_getHeight(window: *const ANativeWindow) -> libc::int32_t;
|
||||
pub fn ANativeWindow_getWidth(window: *const ANativeWindow) -> libc::int32_t;
|
||||
}
|
||||
|
||||
/**
|
||||
* native_activity.h
|
||||
*/
|
||||
pub type JavaVM = ();
|
||||
pub type JNIEnv = ();
|
||||
pub type jobject = *const libc::c_void;
|
||||
|
||||
pub type AInputQueue = (); // FIXME: wrong
|
||||
pub type ARect = (); // FIXME: wrong
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ANativeActivity {
|
||||
pub callbacks: *mut ANativeActivityCallbacks,
|
||||
pub vm: *mut JavaVM,
|
||||
pub env: *mut JNIEnv,
|
||||
pub clazz: jobject,
|
||||
pub internalDataPath: *const libc::c_char,
|
||||
pub externalDataPath: *const libc::c_char,
|
||||
pub sdkVersion: libc::int32_t,
|
||||
pub instance: *mut libc::c_void,
|
||||
pub assetManager: *mut AAssetManager,
|
||||
pub obbPath: *const libc::c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ANativeActivityCallbacks {
|
||||
pub onStart: extern fn(*mut ANativeActivity),
|
||||
pub onResume: extern fn(*mut ANativeActivity),
|
||||
pub onSaveInstanceState: extern fn(*mut ANativeActivity, *mut libc::size_t),
|
||||
pub onPause: extern fn(*mut ANativeActivity),
|
||||
pub onStop: extern fn(*mut ANativeActivity),
|
||||
pub onDestroy: extern fn(*mut ANativeActivity),
|
||||
pub onWindowFocusChanged: extern fn(*mut ANativeActivity, libc::c_int),
|
||||
pub onNativeWindowCreated: extern fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowResized: extern fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowRedrawNeeded: extern fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowDestroyed: extern fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onInputQueueCreated: extern fn(*mut ANativeActivity, *mut AInputQueue),
|
||||
pub onInputQueueDestroyed: extern fn(*mut ANativeActivity, *mut AInputQueue),
|
||||
pub onContentRectChanged: extern fn(*mut ANativeActivity, *const ARect),
|
||||
pub onConfigurationChanged: extern fn(*mut ANativeActivity),
|
||||
pub onLowMemory: extern fn(*mut ANativeActivity),
|
||||
}
|
||||
|
||||
/**
|
||||
* looper.h
|
||||
*/
|
||||
pub type ALooper = ();
|
||||
|
||||
#[link(name = "android")]
|
||||
extern {
|
||||
pub fn ALooper_forThread() -> *const ALooper;
|
||||
pub fn ALooper_acquire(looper: *const ALooper);
|
||||
pub fn ALooper_release(looper: *const ALooper);
|
||||
pub fn ALooper_prepare(opts: libc::c_int) -> *const ALooper;
|
||||
pub fn ALooper_pollOnce(timeoutMillis: libc::c_int, outFd: *mut libc::c_int,
|
||||
outEvents: *mut libc::c_int, outData: *mut *mut libc::c_void) -> libc::c_int;
|
||||
pub fn ALooper_pollAll(timeoutMillis: libc::c_int, outFd: *mut libc::c_int,
|
||||
outEvents: *mut libc::c_int, outData: *mut *mut libc::c_void) -> libc::c_int;
|
||||
pub fn ALooper_wake(looper: *const ALooper);
|
||||
pub fn ALooper_addFd(looper: *const ALooper, fd: libc::c_int, ident: libc::c_int,
|
||||
events: libc::c_int, callback: ALooper_callbackFunc, data: *mut libc::c_void)
|
||||
-> libc::c_int;
|
||||
pub fn ALooper_removeFd(looper: *const ALooper, fd: libc::c_int) -> libc::c_int;
|
||||
}
|
||||
|
||||
pub const ALOOPER_PREPARE_ALLOW_NON_CALLBACKS: libc::c_int = 1 << 0;
|
||||
|
||||
pub const ALOOPER_POLL_WAKE: libc::c_int = -1;
|
||||
pub const ALOOPER_POLL_CALLBACK: libc::c_int = -2;
|
||||
pub const ALOOPER_POLL_TIMEOUT: libc::c_int = -3;
|
||||
pub const ALOOPER_POLL_ERROR: libc::c_int = -4;
|
||||
|
||||
pub const ALOOPER_EVENT_INPUT: libc::c_int = 1 << 0;
|
||||
pub const ALOOPER_EVENT_OUTPUT: libc::c_int = 1 << 1;
|
||||
pub const ALOOPER_EVENT_ERROR: libc::c_int = 1 << 2;
|
||||
pub const ALOOPER_EVENT_HANGUP: libc::c_int = 1 << 3;
|
||||
pub const ALOOPER_EVENT_INVALID: libc::c_int = 1 << 4;
|
||||
|
||||
pub type ALooper_callbackFunc = extern fn(libc::c_int, libc::c_int, *mut libc::c_void) -> libc::c_int;
|
||||
45
src/platform/desktop.rs
Normal file
45
src/platform/desktop.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
#![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)
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
#![allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||
|
||||
use std::os::raw::{c_int, c_char, c_void, c_ulong, c_double, c_long, c_ushort};
|
||||
#[cfg(test)]
|
||||
use std::mem;
|
||||
|
||||
pub type EM_BOOL = c_int;
|
||||
pub type EM_UTF8 = c_char;
|
||||
pub type EMSCRIPTEN_RESULT = c_int;
|
||||
|
||||
pub const EM_TRUE: EM_BOOL = 1;
|
||||
pub const EM_FALSE: EM_BOOL = 0;
|
||||
|
||||
// values for EMSCRIPTEN_RESULT
|
||||
pub const EMSCRIPTEN_RESULT_SUCCESS: c_int = 0;
|
||||
pub const EMSCRIPTEN_RESULT_DEFERRED: c_int = 1;
|
||||
pub const EMSCRIPTEN_RESULT_NOT_SUPPORTED: c_int = -1;
|
||||
pub const EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED: c_int = -2;
|
||||
pub const EMSCRIPTEN_RESULT_INVALID_TARGET: c_int = -3;
|
||||
pub const EMSCRIPTEN_RESULT_UNKNOWN_TARGET: c_int = -4;
|
||||
pub const EMSCRIPTEN_RESULT_INVALID_PARAM: c_int = -5;
|
||||
pub const EMSCRIPTEN_RESULT_FAILED: c_int = -6;
|
||||
pub const EMSCRIPTEN_RESULT_NO_DATA: c_int = -7;
|
||||
|
||||
// values for EMSCRIPTEN EVENT
|
||||
pub const EMSCRIPTEN_EVENT_KEYPRESS: c_int = 1;
|
||||
pub const EMSCRIPTEN_EVENT_KEYDOWN: c_int = 2;
|
||||
pub const EMSCRIPTEN_EVENT_KEYUP: c_int = 3;
|
||||
pub const EMSCRIPTEN_EVENT_CLICK: c_int = 4;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEDOWN: c_int = 5;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEUP: c_int = 6;
|
||||
pub const EMSCRIPTEN_EVENT_DBLCLICK: c_int = 7;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEMOVE: c_int = 8;
|
||||
pub const EMSCRIPTEN_EVENT_WHEEL: c_int = 9;
|
||||
pub const EMSCRIPTEN_EVENT_RESIZE: c_int = 10;
|
||||
pub const EMSCRIPTEN_EVENT_SCROLL: c_int = 11;
|
||||
pub const EMSCRIPTEN_EVENT_BLUR: c_int = 12;
|
||||
pub const EMSCRIPTEN_EVENT_FOCUS: c_int = 13;
|
||||
pub const EMSCRIPTEN_EVENT_FOCUSIN: c_int = 14;
|
||||
pub const EMSCRIPTEN_EVENT_FOCUSOUT: c_int = 15;
|
||||
pub const EMSCRIPTEN_EVENT_DEVICEORIENTATION: c_int = 16;
|
||||
pub const EMSCRIPTEN_EVENT_DEVICEMOTION: c_int = 17;
|
||||
pub const EMSCRIPTEN_EVENT_ORIENTATIONCHANGE: c_int = 18;
|
||||
pub const EMSCRIPTEN_EVENT_FULLSCREENCHANGE: c_int = 19;
|
||||
pub const EMSCRIPTEN_EVENT_POINTERLOCKCHANGE: c_int = 20;
|
||||
pub const EMSCRIPTEN_EVENT_VISIBILITYCHANGE: c_int = 21;
|
||||
pub const EMSCRIPTEN_EVENT_TOUCHSTART: c_int = 22;
|
||||
pub const EMSCRIPTEN_EVENT_TOUCHEND: c_int = 23;
|
||||
pub const EMSCRIPTEN_EVENT_TOUCHMOVE: c_int = 24;
|
||||
pub const EMSCRIPTEN_EVENT_TOUCHCANCEL: c_int = 25;
|
||||
pub const EMSCRIPTEN_EVENT_GAMEPADCONNECTED: c_int = 26;
|
||||
pub const EMSCRIPTEN_EVENT_GAMEPADDISCONNECTED: c_int = 27;
|
||||
pub const EMSCRIPTEN_EVENT_BEFOREUNLOAD: c_int = 28;
|
||||
pub const EMSCRIPTEN_EVENT_BATTERYCHARGINGCHANGE: c_int = 29;
|
||||
pub const EMSCRIPTEN_EVENT_BATTERYLEVELCHANGE: c_int = 30;
|
||||
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: c_int = 31;
|
||||
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: c_int = 32;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEENTER: c_int = 33;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSELEAVE: c_int = 34;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEOVER: c_int = 35;
|
||||
pub const EMSCRIPTEN_EVENT_MOUSEOUT: c_int = 36;
|
||||
pub const EMSCRIPTEN_EVENT_CANVASRESIZED: c_int = 37;
|
||||
pub const EMSCRIPTEN_EVENT_POINTERLOCKERROR: c_int = 38;
|
||||
|
||||
pub const EM_HTML5_SHORT_STRING_LEN_BYTES: usize = 32;
|
||||
|
||||
pub const DOM_KEY_LOCATION_STANDARD: c_ulong = 0x00;
|
||||
pub const DOM_KEY_LOCATION_LEFT: c_ulong = 0x01;
|
||||
pub const DOM_KEY_LOCATION_RIGHT: c_ulong = 0x02;
|
||||
pub const DOM_KEY_LOCATION_NUMPAD: c_ulong = 0x03;
|
||||
|
||||
pub type em_callback_func = Option<unsafe extern "C" fn()>;
|
||||
|
||||
pub type em_key_callback_func = Option<unsafe extern "C" fn(
|
||||
eventType: c_int,
|
||||
keyEvent: *const EmscriptenKeyboardEvent,
|
||||
userData: *mut c_void) -> EM_BOOL>;
|
||||
|
||||
pub type em_mouse_callback_func = Option<unsafe extern "C" fn(
|
||||
eventType: c_int,
|
||||
mouseEvent: *const EmscriptenMouseEvent,
|
||||
userData: *mut c_void) -> EM_BOOL>;
|
||||
|
||||
pub type em_pointerlockchange_callback_func = Option<unsafe extern "C" fn(
|
||||
eventType: c_int,
|
||||
pointerlockChangeEvent: *const EmscriptenPointerlockChangeEvent,
|
||||
userData: *mut c_void) -> EM_BOOL>;
|
||||
|
||||
pub type em_fullscreenchange_callback_func = Option<unsafe extern "C" fn(
|
||||
eventType: c_int,
|
||||
fullscreenChangeEvent: *const EmscriptenFullscreenChangeEvent,
|
||||
userData: *mut c_void) -> EM_BOOL>;
|
||||
|
||||
pub type em_touch_callback_func = Option<unsafe extern "C" fn(
|
||||
eventType: c_int,
|
||||
touchEvent: *const EmscriptenTouchEvent,
|
||||
userData: *mut c_void) -> EM_BOOL>;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct EmscriptenFullscreenChangeEvent {
|
||||
pub isFullscreen: c_int,
|
||||
pub fullscreenEnabled: c_int,
|
||||
pub nodeName: [c_char; 128usize],
|
||||
pub id: [c_char; 128usize],
|
||||
pub elementWidth: c_int,
|
||||
pub elementHeight: c_int,
|
||||
pub screenWidth: c_int,
|
||||
pub screenHeight: c_int,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenFullscreenChangeEvent() {
|
||||
assert_eq!(mem::size_of::<EmscriptenFullscreenChangeEvent>(), 280usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenFullscreenChangeEvent>(), 4usize);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy)]
|
||||
pub struct EmscriptenKeyboardEvent {
|
||||
pub key: [c_char; 32usize],
|
||||
pub code: [c_char; 32usize],
|
||||
pub location: c_ulong,
|
||||
pub ctrlKey: c_int,
|
||||
pub shiftKey: c_int,
|
||||
pub altKey: c_int,
|
||||
pub metaKey: c_int,
|
||||
pub repeat: c_int,
|
||||
pub locale: [c_char; 32usize],
|
||||
pub charValue: [c_char; 32usize],
|
||||
pub charCode: c_ulong,
|
||||
pub keyCode: c_ulong,
|
||||
pub which: c_ulong,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenKeyboardEvent() {
|
||||
assert_eq!(mem::size_of::<EmscriptenKeyboardEvent>(), 184usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenKeyboardEvent>(), 8usize);
|
||||
}
|
||||
impl Clone for EmscriptenKeyboardEvent {
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct EmscriptenMouseEvent {
|
||||
pub timestamp: f64,
|
||||
pub screenX: c_long,
|
||||
pub screenY: c_long,
|
||||
pub clientX: c_long,
|
||||
pub clientY: c_long,
|
||||
pub ctrlKey: c_int,
|
||||
pub shiftKey: c_int,
|
||||
pub altKey: c_int,
|
||||
pub metaKey: c_int,
|
||||
pub button: c_ushort,
|
||||
pub buttons: c_ushort,
|
||||
pub movementX: c_long,
|
||||
pub movementY: c_long,
|
||||
pub targetX: c_long,
|
||||
pub targetY: c_long,
|
||||
pub canvasX: c_long,
|
||||
pub canvasY: c_long,
|
||||
pub padding: c_long,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenMouseEvent() {
|
||||
assert_eq!(mem::size_of::<EmscriptenMouseEvent>(), 120usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenMouseEvent>(), 8usize);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct EmscriptenTouchPoint {
|
||||
pub identifier: c_long,
|
||||
pub screenX: c_long,
|
||||
pub screenY: c_long,
|
||||
pub clientX: c_long,
|
||||
pub clientY: c_long,
|
||||
pub pageX: c_long,
|
||||
pub pageY: c_long,
|
||||
pub isChanged: c_int,
|
||||
pub onTarget: c_int,
|
||||
pub targetX: c_long,
|
||||
pub targetY: c_long,
|
||||
pub canvasX: c_long,
|
||||
pub canvasY: c_long,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenTouchPoint() {
|
||||
assert_eq!(mem::size_of::<EmscriptenTouchPoint>(), 96usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenTouchPoint>(), 8usize);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct EmscriptenTouchEvent {
|
||||
pub numTouches: c_int,
|
||||
pub ctrlKey: c_int,
|
||||
pub shiftKey: c_int,
|
||||
pub altKey: c_int,
|
||||
pub metaKey: c_int,
|
||||
pub touches: [EmscriptenTouchPoint; 32usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenTouchEvent() {
|
||||
assert_eq!(mem::size_of::<EmscriptenTouchEvent>(), 3096usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenTouchEvent>(), 8usize);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct EmscriptenPointerlockChangeEvent {
|
||||
pub isActive: c_int,
|
||||
pub nodeName: [c_char; 128usize],
|
||||
pub id: [c_char; 128usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_EmscriptenPointerlockChangeEvent() {
|
||||
assert_eq!(mem::size_of::<EmscriptenPointerlockChangeEvent>(), 260usize);
|
||||
assert_eq!(mem::align_of::<EmscriptenPointerlockChangeEvent>(), 4usize);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn emscripten_set_canvas_size(
|
||||
width: c_int, height: c_int)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_get_canvas_size(
|
||||
width: *mut c_int, height: *mut c_int,
|
||||
is_fullscreen: *mut c_int)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_element_css_size(
|
||||
target: *const c_char, width: c_double,
|
||||
height: c_double) -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_get_element_css_size(
|
||||
target: *const c_char, width: *mut c_double,
|
||||
height: *mut c_double) -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_request_pointerlock(
|
||||
target: *const c_char, deferUntilInEventHandler: EM_BOOL)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_exit_pointerlock() -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_request_fullscreen(
|
||||
target: *const c_char, deferUntilInEventHandler: EM_BOOL)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_exit_fullscreen() -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_keydown_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: EM_BOOL, callback: em_key_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_keyup_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: EM_BOOL, callback: em_key_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_mousemove_callback(
|
||||
target: *const c_char, user_data: *mut c_void,
|
||||
use_capture: EM_BOOL, callback: em_mouse_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_mousedown_callback(
|
||||
target: *const c_char, user_data: *mut c_void,
|
||||
use_capture: EM_BOOL, callback: em_mouse_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_mouseup_callback(
|
||||
target: *const c_char, user_data: *mut c_void,
|
||||
use_capture: EM_BOOL, callback: em_mouse_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_hide_mouse();
|
||||
|
||||
pub fn emscripten_get_device_pixel_ratio() -> f64;
|
||||
|
||||
pub fn emscripten_set_pointerlockchange_callback(
|
||||
target: *const c_char, userData: *mut c_void, useCapture: EM_BOOL,
|
||||
callback: em_pointerlockchange_callback_func) -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_fullscreenchange_callback(
|
||||
target: *const c_char, userData: *mut c_void, useCapture: EM_BOOL,
|
||||
callback: em_fullscreenchange_callback_func) -> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_asm_const(code: *const c_char);
|
||||
|
||||
pub fn emscripten_set_main_loop(
|
||||
func: em_callback_func, fps: c_int, simulate_infinite_loop: EM_BOOL);
|
||||
|
||||
pub fn emscripten_cancel_main_loop();
|
||||
|
||||
pub fn emscripten_set_touchstart_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: c_int, callback: em_touch_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_touchend_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: c_int, callback: em_touch_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_touchmove_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: c_int, callback: em_touch_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
|
||||
pub fn emscripten_set_touchcancel_callback(
|
||||
target: *const c_char, userData: *mut c_void,
|
||||
useCapture: c_int, callback: em_touch_callback_func)
|
||||
-> EMSCRIPTEN_RESULT;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
317
src/platform/ios.rs
Normal file
317
src/platform/ios.rs
Normal file
@@ -0,0 +1,317 @@
|
||||
#![cfg(target_os = "ios")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use crate::{
|
||||
event_loop::EventLoop,
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Additional methods on [`EventLoop`] that are specific to iOS.
|
||||
pub trait EventLoopExtIOS {
|
||||
/// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
|
||||
fn idiom(&self) -> Idiom;
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
|
||||
fn idiom(&self) -> Idiom {
|
||||
self.event_loop.idiom()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to iOS.
|
||||
pub trait WindowExtIOS {
|
||||
/// Returns a pointer to the [`UIWindow`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
///
|
||||
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
||||
fn ui_window(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the [`UIViewController`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
///
|
||||
/// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc
|
||||
fn ui_view_controller(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the [`UIView`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
///
|
||||
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
|
||||
fn ui_view(&self) -> *mut c_void;
|
||||
|
||||
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
|
||||
///
|
||||
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
||||
/// this to [`MonitorHandle::hidpi_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_hidpi_factor(&self, hidpi_factor: f64);
|
||||
|
||||
/// Sets the valid orientations for the [`Window`].
|
||||
///
|
||||
/// The default value is [`ValidOrientations::LandscapeAndPortrait`].
|
||||
///
|
||||
/// This changes the value returned by
|
||||
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc),
|
||||
/// and then calls
|
||||
/// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc).
|
||||
fn set_valid_orientations(&self, valid_orientations: ValidOrientations);
|
||||
|
||||
/// Sets whether the [`Window`] prefers the home indicator hidden.
|
||||
///
|
||||
/// The default is to prefer showing the home indicator.
|
||||
///
|
||||
/// This changes the value returned by
|
||||
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc),
|
||||
/// and then calls
|
||||
/// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc).
|
||||
///
|
||||
/// This only has an effect on iOS 11.0+.
|
||||
fn set_prefers_home_indicator_hidden(&self, hidden: bool);
|
||||
|
||||
/// Sets the screen edges for which the system gestures will take a lower priority than the
|
||||
/// application's touch handling.
|
||||
///
|
||||
/// This changes the value returned by
|
||||
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc),
|
||||
/// and then calls
|
||||
/// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc).
|
||||
///
|
||||
/// This only has an effect on iOS 11.0+.
|
||||
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge);
|
||||
|
||||
/// Sets whether the [`Window`] prefers the status bar hidden.
|
||||
///
|
||||
/// The default is to prefer showing the status bar.
|
||||
///
|
||||
/// This changes the value returned by
|
||||
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc),
|
||||
/// and then calls
|
||||
/// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc).
|
||||
fn set_prefers_status_bar_hidden(&self, hidden: bool);
|
||||
}
|
||||
|
||||
impl WindowExtIOS for Window {
|
||||
#[inline]
|
||||
fn ui_window(&self) -> *mut c_void {
|
||||
self.window.ui_window() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ui_view_controller(&self) -> *mut c_void {
|
||||
self.window.ui_view_controller() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ui_view(&self) -> *mut c_void {
|
||||
self.window.ui_view() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_hidpi_factor(&self, hidpi_factor: f64) {
|
||||
self.window.set_hidpi_factor(hidpi_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
||||
self.window.set_valid_orientations(valid_orientations)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||
self.window.set_prefers_home_indicator_hidden(hidden)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
||||
self.window
|
||||
.set_preferred_screen_edges_deferring_system_gestures(edges)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||
self.window.set_prefers_status_bar_hidden(hidden)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to iOS.
|
||||
pub trait WindowBuilderExtIOS {
|
||||
/// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided.
|
||||
///
|
||||
/// An instance of the class will be initialized by calling [`-[UIView initWithFrame:]`](https://developer.apple.com/documentation/uikit/uiview/1622488-initwithframe?language=objc).
|
||||
///
|
||||
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
|
||||
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
|
||||
|
||||
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
|
||||
///
|
||||
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
||||
/// this to [`MonitorHandle::hidpi_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_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder;
|
||||
|
||||
/// Sets the valid orientations for the [`Window`].
|
||||
///
|
||||
/// The default value is [`ValidOrientations::LandscapeAndPortrait`].
|
||||
///
|
||||
/// This sets the initial value returned by
|
||||
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
|
||||
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
|
||||
|
||||
/// Sets whether the [`Window`] prefers the home indicator hidden.
|
||||
///
|
||||
/// The default is to prefer showing the home indicator.
|
||||
///
|
||||
/// This sets the initial value returned by
|
||||
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
|
||||
///
|
||||
/// This only has an effect on iOS 11.0+.
|
||||
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder;
|
||||
|
||||
/// Sets the screen edges for which the system gestures will take a lower priority than the
|
||||
/// application's touch handling.
|
||||
///
|
||||
/// This sets the initial value returned by
|
||||
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
|
||||
///
|
||||
/// This only has an effect on iOS 11.0+.
|
||||
fn with_preferred_screen_edges_deferring_system_gestures(
|
||||
self,
|
||||
edges: ScreenEdge,
|
||||
) -> WindowBuilder;
|
||||
|
||||
/// Sets whether the [`Window`] prefers the status bar hidden.
|
||||
///
|
||||
/// The default is to prefer showing the status bar.
|
||||
///
|
||||
/// This sets the initial value returned by
|
||||
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
|
||||
fn with_prefers_status_bar_hidden(self, hidden: bool) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtIOS for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder {
|
||||
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_hidpi_factor(mut self, hidpi_factor: f64) -> WindowBuilder {
|
||||
self.platform_specific.hidpi_factor = Some(hidpi_factor);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> WindowBuilder {
|
||||
self.platform_specific.valid_orientations = valid_orientations;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.prefers_home_indicator_hidden = hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_preferred_screen_edges_deferring_system_gestures(
|
||||
mut self,
|
||||
edges: ScreenEdge,
|
||||
) -> WindowBuilder {
|
||||
self.platform_specific
|
||||
.preferred_screen_edges_deferring_system_gestures = edges;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.prefers_status_bar_hidden = hidden;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`MonitorHandle`] that are specific to iOS.
|
||||
pub trait MonitorHandleExtIOS {
|
||||
/// Returns a pointer to the [`UIScreen`] that is used by this monitor.
|
||||
///
|
||||
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
|
||||
fn ui_screen(&self) -> *mut c_void;
|
||||
|
||||
/// Returns the preferred [`VideoMode`] for this monitor.
|
||||
///
|
||||
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
|
||||
fn preferred_video_mode(&self) -> VideoMode;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtIOS for MonitorHandle {
|
||||
#[inline]
|
||||
fn ui_screen(&self) -> *mut c_void {
|
||||
self.inner.ui_screen() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn preferred_video_mode(&self) -> VideoMode {
|
||||
self.inner.preferred_video_mode()
|
||||
}
|
||||
}
|
||||
|
||||
/// Valid orientations for a particular [`Window`].
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ValidOrientations {
|
||||
/// Excludes `PortraitUpsideDown` on iphone
|
||||
LandscapeAndPortrait,
|
||||
|
||||
Landscape,
|
||||
|
||||
/// Excludes `PortraitUpsideDown` on iphone
|
||||
Portrait,
|
||||
}
|
||||
|
||||
impl Default for ValidOrientations {
|
||||
#[inline]
|
||||
fn default() -> ValidOrientations {
|
||||
ValidOrientations::LandscapeAndPortrait
|
||||
}
|
||||
}
|
||||
|
||||
/// The device [idiom].
|
||||
///
|
||||
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Idiom {
|
||||
Unspecified,
|
||||
|
||||
/// iPhone and iPod touch.
|
||||
Phone,
|
||||
|
||||
/// iPad.
|
||||
Pad,
|
||||
|
||||
/// tvOS and Apple TV.
|
||||
TV,
|
||||
CarPlay,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// The [edges] of a screen.
|
||||
///
|
||||
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
|
||||
#[derive(Default)]
|
||||
pub struct ScreenEdge: u8 {
|
||||
const NONE = 0;
|
||||
const TOP = 1 << 0;
|
||||
const LEFT = 1 << 1;
|
||||
const BOTTOM = 1 << 2;
|
||||
const RIGHT = 1 << 3;
|
||||
const ALL = ScreenEdge::TOP.bits | ScreenEdge::LEFT.bits
|
||||
| ScreenEdge::BOTTOM.bits | ScreenEdge::RIGHT.bits;
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::*;
|
||||
|
||||
use objc::runtime::Object;
|
||||
|
||||
pub type id = *mut Object;
|
||||
pub const nil: id = 0 as id;
|
||||
|
||||
pub type CFStringRef = *const c_void;
|
||||
pub type CFTimeInterval = f64;
|
||||
pub type Boolean = u32;
|
||||
|
||||
pub const kCFRunLoopRunHandledSource: i32 = 4;
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
pub type CGFloat = f32;
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub type CGFloat = f64;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGPoint {
|
||||
pub x: CGFloat,
|
||||
pub y: CGFloat,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGRect {
|
||||
pub origin: CGPoint,
|
||||
pub size: CGSize,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGSize {
|
||||
pub width: CGFloat,
|
||||
pub height: CGFloat,
|
||||
}
|
||||
|
||||
#[link(name = "UIKit", kind = "framework")]
|
||||
#[link(name = "CoreFoundation", kind = "framework")]
|
||||
#[link(name = "GlKit", kind = "framework")]
|
||||
extern {
|
||||
pub static kCFRunLoopDefaultMode: CFStringRef;
|
||||
|
||||
// int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName );
|
||||
pub fn UIApplicationMain(
|
||||
argc: c_int,
|
||||
argv: *const c_char,
|
||||
principalClassName: id,
|
||||
delegateClassName: id,
|
||||
) -> c_int;
|
||||
|
||||
// SInt32 CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled );
|
||||
pub fn CFRunLoopRunInMode(
|
||||
mode: CFStringRef,
|
||||
seconds: CFTimeInterval,
|
||||
returnAfterSourceHandled: Boolean,
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
extern {
|
||||
pub fn setjmp(env: *mut c_void) -> c_int;
|
||||
pub fn longjmp(env: *mut c_void, val: c_int) -> !;
|
||||
}
|
||||
|
||||
// values taken from "setjmp.h" header in xcode iPhoneOS/iPhoneSimulator SDK
|
||||
#[cfg(any(target_arch = "x86_64"))]
|
||||
pub const JBLEN: usize = (9 * 2) + 3 + 16;
|
||||
#[cfg(any(target_arch = "x86"))]
|
||||
pub const JBLEN: usize = 18;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub const JBLEN: usize = 10 + 16 + 2;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub const JBLEN: usize = (14 + 8 + 2) * 2;
|
||||
|
||||
pub type JmpBuf = [c_int; JBLEN];
|
||||
|
||||
pub trait NSString: Sized {
|
||||
unsafe fn alloc(_: Self) -> id {
|
||||
msg_send![class!(NSString), alloc]
|
||||
}
|
||||
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id;
|
||||
unsafe fn stringByAppendingString_(self, other: id) -> id;
|
||||
unsafe fn init_str(self, string: &str) -> Self;
|
||||
unsafe fn UTF8String(self) -> *const c_char;
|
||||
}
|
||||
|
||||
impl NSString for id {
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
|
||||
msg_send![self, initWithUTF8String:c_string as id]
|
||||
}
|
||||
|
||||
unsafe fn stringByAppendingString_(self, other: id) -> id {
|
||||
msg_send![self, stringByAppendingString:other]
|
||||
}
|
||||
|
||||
unsafe fn init_str(self, string: &str) -> id {
|
||||
let cstring = CString::new(string).unwrap();
|
||||
self.initWithUTF8String_(cstring.as_ptr())
|
||||
}
|
||||
|
||||
unsafe fn UTF8String(self) -> *const c_char {
|
||||
msg_send![self, UTF8String]
|
||||
}
|
||||
}
|
||||
@@ -1,709 +0,0 @@
|
||||
//! iOS support
|
||||
//!
|
||||
//! # Building app
|
||||
//! To build ios app you will need rustc built for this targets:
|
||||
//!
|
||||
//! - armv7-apple-ios
|
||||
//! - armv7s-apple-ios
|
||||
//! - i386-apple-ios
|
||||
//! - aarch64-apple-ios
|
||||
//! - x86_64-apple-ios
|
||||
//!
|
||||
//! Then
|
||||
//!
|
||||
//! ```
|
||||
//! cargo build --target=...
|
||||
//! ```
|
||||
//! The simplest way to integrate your app into xcode environment is to build it
|
||||
//! as a static library. Wrap your main function and export it.
|
||||
//!
|
||||
//! ```rust, ignore
|
||||
//! #[no_mangle]
|
||||
//! pub extern fn start_winit_app() {
|
||||
//! start_inner()
|
||||
//! }
|
||||
//!
|
||||
//! fn start_inner() {
|
||||
//! ...
|
||||
//! }
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! void start_winit_app();
|
||||
//! ```
|
||||
//!
|
||||
//! Use start_winit_app inside your xcode's main function.
|
||||
//!
|
||||
//!
|
||||
//! # App lifecycle and events
|
||||
//!
|
||||
//! iOS environment is very different from other platforms and you must be very
|
||||
//! careful with it's events. Familiarize yourself with
|
||||
//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/).
|
||||
//!
|
||||
//!
|
||||
//! This is how those event are represented in winit:
|
||||
//!
|
||||
//! - applicationDidBecomeActive is Focused(true)
|
||||
//! - applicationWillResignActive is Focused(false)
|
||||
//! - applicationDidEnterBackground is Suspended(true)
|
||||
//! - applicationWillEnterForeground is Suspended(false)
|
||||
//! - applicationWillTerminate is Destroyed
|
||||
//!
|
||||
//! Keep in mind that after Destroyed event is received every attempt to draw with
|
||||
//! opengl will result in segfault.
|
||||
//!
|
||||
//! Also note that app will not receive Destroyed event if suspended, it will be SIGKILL'ed
|
||||
|
||||
#![cfg(target_os = "ios")]
|
||||
|
||||
use std::{fmt, mem, ptr};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::os::raw::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{BOOL, Class, Object, Sel, YES};
|
||||
|
||||
use {
|
||||
CreationError,
|
||||
Event,
|
||||
LogicalPosition,
|
||||
LogicalSize,
|
||||
MouseCursor,
|
||||
PhysicalPosition,
|
||||
PhysicalSize,
|
||||
WindowAttributes,
|
||||
WindowEvent,
|
||||
WindowId as RootEventId,
|
||||
};
|
||||
use events::{Touch, TouchPhase};
|
||||
use window::MonitorId as RootMonitorId;
|
||||
|
||||
mod ffi;
|
||||
use self::ffi::{
|
||||
CFTimeInterval,
|
||||
CFRunLoopRunInMode,
|
||||
CGFloat,
|
||||
CGPoint,
|
||||
CGRect,
|
||||
id,
|
||||
JBLEN,
|
||||
JmpBuf,
|
||||
kCFRunLoopDefaultMode,
|
||||
kCFRunLoopRunHandledSource,
|
||||
longjmp,
|
||||
nil,
|
||||
NSString,
|
||||
setjmp,
|
||||
UIApplicationMain,
|
||||
};
|
||||
|
||||
static mut JMPBUF: Option<Box<JmpBuf>> = None;
|
||||
|
||||
pub struct Window {
|
||||
_events_queue: Arc<RefCell<VecDeque<Event>>>,
|
||||
delegate_state: Box<DelegateState>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Window {}
|
||||
unsafe impl Sync for Window {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DelegateState {
|
||||
window: id,
|
||||
controller: id,
|
||||
view: id,
|
||||
size: LogicalSize,
|
||||
scale: f64,
|
||||
}
|
||||
|
||||
impl DelegateState {
|
||||
fn new(window: id, controller: id, view: id, size: LogicalSize, scale: f64) -> DelegateState {
|
||||
DelegateState {
|
||||
window,
|
||||
controller,
|
||||
view,
|
||||
size,
|
||||
scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DelegateState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _: () = msg_send![self.window, release];
|
||||
let _: () = msg_send![self.controller, release];
|
||||
let _: () = msg_send![self.view, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId;
|
||||
|
||||
impl fmt::Debug for MonitorId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorId {
|
||||
name: Option<String>,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorId {
|
||||
name: self.get_name(),
|
||||
dimensions: self.get_dimensions(),
|
||||
position: self.get_position(),
|
||||
hidpi_factor: self.get_hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
#[inline]
|
||||
pub fn get_uiscreen(&self) -> id {
|
||||
let class = class!(UIScreen);
|
||||
unsafe { msg_send![class, mainScreen] }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some("Primary".to_string())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
let bounds: CGRect = unsafe { msg_send![self.get_uiscreen(), nativeBounds] };
|
||||
(bounds.size.width as f64, bounds.size.height as f64).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
// iOS assumes single screen
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
let scale: CGFloat = unsafe { msg_send![self.get_uiscreen(), nativeScale] };
|
||||
scale as f64
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventsLoop {
|
||||
events_queue: Arc<RefCell<VecDeque<Event>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventsLoopProxy;
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn new() -> EventsLoop {
|
||||
unsafe {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
panic!("`EventsLoop` can only be created on the main thread on iOS");
|
||||
}
|
||||
}
|
||||
EventsLoop { events_queue: Default::default() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorId);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event)
|
||||
{
|
||||
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
|
||||
callback(event);
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// jump hack, so we won't quit on willTerminate event before processing it
|
||||
assert!(JMPBUF.is_some(), "`EventsLoop::poll_events` must be called after window creation on iOS");
|
||||
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
|
||||
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
|
||||
callback(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// run runloop
|
||||
let seconds: CFTimeInterval = 0.000002;
|
||||
while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {}
|
||||
}
|
||||
|
||||
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
|
||||
callback(event)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event) -> ::ControlFlow,
|
||||
{
|
||||
// Yeah that's a very bad implementation.
|
||||
loop {
|
||||
let mut control_flow = ::ControlFlow::Continue;
|
||||
self.poll_events(|e| {
|
||||
if let ::ControlFlow::Break = callback(e) {
|
||||
control_flow = ::ControlFlow::Break;
|
||||
}
|
||||
});
|
||||
if let ::ControlFlow::Break = control_flow {
|
||||
break;
|
||||
}
|
||||
::std::thread::sleep(::std::time::Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
EventsLoopProxy
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), ::EventsLoopClosed> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId;
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub root_view_class: &'static Class,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
fn default() -> Self {
|
||||
PlatformSpecificWindowBuilderAttributes {
|
||||
root_view_class: class!(UIView),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: AFAIK transparency is enabled by default on iOS,
|
||||
// so to be consistent with other platforms we have to change that.
|
||||
impl Window {
|
||||
pub fn new(
|
||||
ev: &EventsLoop,
|
||||
_attributes: WindowAttributes,
|
||||
pl_attributes: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, CreationError> {
|
||||
unsafe {
|
||||
debug_assert!(mem::size_of_val(&JMPBUF) == mem::size_of::<Box<JmpBuf>>());
|
||||
assert!(mem::replace(&mut JMPBUF, Some(Box::new([0; JBLEN]))).is_none(), "Only one `Window` is supported on iOS");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
|
||||
let app_class = class!(UIApplication);
|
||||
let app: id = msg_send![app_class, sharedApplication];
|
||||
let delegate: id = msg_send![app, delegate];
|
||||
let state: *mut c_void = *(&*delegate).get_ivar("winitState");
|
||||
let mut delegate_state = Box::from_raw(state as *mut DelegateState);
|
||||
let events_queue = &*ev.events_queue;
|
||||
(&mut *delegate).set_ivar("eventsQueue", mem::transmute::<_, *mut c_void>(events_queue));
|
||||
|
||||
// easiest? way to get access to PlatformSpecificWindowBuilderAttributes to configure the view
|
||||
let rect: CGRect = msg_send![MonitorId.get_uiscreen(), bounds];
|
||||
|
||||
let uiview_class = class!(UIView);
|
||||
let root_view_class = pl_attributes.root_view_class;
|
||||
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class];
|
||||
assert!(is_uiview == YES, "`root_view_class` must inherit from `UIView`");
|
||||
|
||||
delegate_state.view = msg_send![root_view_class, alloc];
|
||||
assert!(!delegate_state.view.is_null(), "Failed to create `UIView` instance");
|
||||
delegate_state.view = msg_send![delegate_state.view, initWithFrame:rect];
|
||||
assert!(!delegate_state.view.is_null(), "Failed to initialize `UIView` instance");
|
||||
|
||||
let _: () = msg_send![delegate_state.controller, setView:delegate_state.view];
|
||||
let _: () = msg_send![delegate_state.window, makeKeyAndVisible];
|
||||
|
||||
return Ok(Window {
|
||||
_events_queue: ev.events_queue.clone(),
|
||||
delegate_state,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
create_delegate_class();
|
||||
start_app();
|
||||
|
||||
panic!("Couldn't create `UIApplication`!")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_uiwindow(&self) -> id {
|
||||
self.delegate_state.window
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_uiview(&self) -> id {
|
||||
self.delegate_state.view
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, _title: &str) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
// N/A
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
// N/A
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, _position: LogicalPosition) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
Some(self.delegate_state.size)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
self.get_inner_size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, _size: LogicalSize) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, _resizable: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, _cursor: MouseCursor) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
|
||||
Err("Cursor grabbing is not possible on iOS.".to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide_cursor(&self, _hide: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.delegate_state.scale
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
|
||||
Err("Setting cursor position is not possible on iOS.".to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, _maximized: bool) {
|
||||
// N/A
|
||||
// iOS has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
|
||||
// N/A
|
||||
// iOS has single screen maximized apps so nothing to do
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
|
||||
// N/A
|
||||
// iOS has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, _decorations: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
RootMonitorId { inner: MonitorId }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorId);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId
|
||||
}
|
||||
}
|
||||
|
||||
fn create_delegate_class() {
|
||||
extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL {
|
||||
let screen_class = class!(UIScreen);
|
||||
let window_class = class!(UIWindow);
|
||||
let controller_class = class!(UIViewController);
|
||||
unsafe {
|
||||
let main_screen: id = msg_send![screen_class, mainScreen];
|
||||
let bounds: CGRect = msg_send![main_screen, bounds];
|
||||
let scale: CGFloat = msg_send![main_screen, nativeScale];
|
||||
|
||||
let window: id = msg_send![window_class, alloc];
|
||||
let window: id = msg_send![window, initWithFrame:bounds.clone()];
|
||||
|
||||
let size = (bounds.size.width as f64, bounds.size.height as f64).into();
|
||||
|
||||
let view_controller: id = msg_send![controller_class, alloc];
|
||||
let view_controller: id = msg_send![view_controller, init];
|
||||
|
||||
let _: () = msg_send![window, setRootViewController:view_controller];
|
||||
|
||||
let state = Box::new(DelegateState::new(window, view_controller, ptr::null_mut(), size, scale as f64));
|
||||
let state_ptr: *mut DelegateState = mem::transmute(state);
|
||||
this.set_ivar("winitState", state_ptr as *mut c_void);
|
||||
|
||||
// The `UIView` is setup in `Window::new` which gets `longjmp`'ed to here.
|
||||
// This makes it easier to configure the specific `UIView` type.
|
||||
let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0];
|
||||
}
|
||||
YES
|
||||
}
|
||||
|
||||
extern fn post_launch(_: &Object, _: Sel, _: id) {
|
||||
unsafe { longjmp(mem::transmute_copy(&mut JMPBUF), 1); }
|
||||
}
|
||||
|
||||
extern fn did_become_active(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
events_queue.borrow_mut().push_back(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Focused(true),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extern fn will_resign_active(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
events_queue.borrow_mut().push_back(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Focused(false),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extern fn will_enter_foreground(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
events_queue.borrow_mut().push_back(Event::Suspended(false));
|
||||
}
|
||||
}
|
||||
|
||||
extern fn did_enter_background(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
events_queue.borrow_mut().push_back(Event::Suspended(true));
|
||||
}
|
||||
}
|
||||
|
||||
extern fn will_terminate(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
// push event to the front to garantee that we'll process it
|
||||
// immidiatly after jump
|
||||
events_queue.borrow_mut().push_front(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Destroyed,
|
||||
});
|
||||
longjmp(mem::transmute_copy(&mut JMPBUF), 1);
|
||||
}
|
||||
}
|
||||
|
||||
extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) {
|
||||
unsafe {
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
|
||||
let touches_enum: id = msg_send![touches, objectEnumerator];
|
||||
|
||||
loop {
|
||||
let touch: id = msg_send![touches_enum, nextObject];
|
||||
if touch == nil {
|
||||
break
|
||||
}
|
||||
let location: CGPoint = msg_send![touch, locationInView:nil];
|
||||
let touch_id = touch as u64;
|
||||
let phase: i32 = msg_send![touch, phase];
|
||||
|
||||
events_queue.borrow_mut().push_back(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
device_id: DEVICE_ID,
|
||||
id: touch_id,
|
||||
location: (location.x as f64, location.y as f64).into(),
|
||||
phase: match phase {
|
||||
0 => TouchPhase::Started,
|
||||
1 => TouchPhase::Moved,
|
||||
// 2 is UITouchPhaseStationary and is not expected here
|
||||
3 => TouchPhase::Ended,
|
||||
4 => TouchPhase::Cancelled,
|
||||
_ => panic!("unexpected touch phase: {:?}", phase)
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ui_responder = class!(UIResponder);
|
||||
let mut decl = ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
|
||||
|
||||
unsafe {
|
||||
decl.add_method(sel!(application:didFinishLaunchingWithOptions:),
|
||||
did_finish_launching as extern fn(&mut Object, Sel, id, id) -> BOOL);
|
||||
|
||||
decl.add_method(sel!(applicationDidBecomeActive:),
|
||||
did_become_active as extern fn(&Object, Sel, id));
|
||||
|
||||
decl.add_method(sel!(applicationWillResignActive:),
|
||||
will_resign_active as extern fn(&Object, Sel, id));
|
||||
|
||||
decl.add_method(sel!(applicationWillEnterForeground:),
|
||||
will_enter_foreground as extern fn(&Object, Sel, id));
|
||||
|
||||
decl.add_method(sel!(applicationDidEnterBackground:),
|
||||
did_enter_background as extern fn(&Object, Sel, id));
|
||||
|
||||
decl.add_method(sel!(applicationWillTerminate:),
|
||||
will_terminate as extern fn(&Object, Sel, id));
|
||||
|
||||
|
||||
decl.add_method(sel!(touchesBegan:withEvent:),
|
||||
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
|
||||
|
||||
decl.add_method(sel!(touchesMoved:withEvent:),
|
||||
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
|
||||
|
||||
decl.add_method(sel!(touchesEnded:withEvent:),
|
||||
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
|
||||
|
||||
decl.add_method(sel!(touchesCancelled:withEvent:),
|
||||
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
|
||||
|
||||
|
||||
decl.add_method(sel!(postLaunch:),
|
||||
post_launch as extern fn(&Object, Sel, id));
|
||||
|
||||
decl.add_ivar::<*mut c_void>("winitState");
|
||||
decl.add_ivar::<*mut c_void>("eventsQueue");
|
||||
|
||||
decl.register();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn start_app() {
|
||||
unsafe {
|
||||
UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate"));
|
||||
}
|
||||
}
|
||||
|
||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
||||
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
|
||||
@@ -1,15 +0,0 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::os::raw::{c_void, c_char, c_int};
|
||||
|
||||
pub const RTLD_LAZY: c_int = 0x001;
|
||||
pub const RTLD_NOW: c_int = 0x002;
|
||||
|
||||
#[link(name = "dl")]
|
||||
extern {
|
||||
pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
|
||||
pub fn dlerror() -> *mut c_char;
|
||||
pub fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
|
||||
pub fn dlclose(handle: *mut c_void) -> c_int;
|
||||
}
|
||||
@@ -1,551 +0,0 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::{env, mem};
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use sctk::reexports::client::ConnectError;
|
||||
|
||||
use {
|
||||
CreationError,
|
||||
EventsLoopClosed,
|
||||
Icon,
|
||||
MouseCursor,
|
||||
ControlFlow,
|
||||
WindowAttributes,
|
||||
};
|
||||
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
use window::MonitorId as RootMonitorId;
|
||||
use self::x11::{XConnection, XError};
|
||||
use self::x11::ffi::XVisualInfo;
|
||||
pub use self::x11::XNotSupported;
|
||||
|
||||
mod dlopen;
|
||||
pub mod wayland;
|
||||
pub mod x11;
|
||||
|
||||
/// Environment variable specifying which backend should be used on unix platform.
|
||||
///
|
||||
/// Legal values are x11 and wayland. If this variable is set only the named backend
|
||||
/// will be tried by winit. If it is not set, winit will try to connect to a wayland connection,
|
||||
/// and if it fails will fallback on x11.
|
||||
///
|
||||
/// If this variable is set with any other value, winit will panic.
|
||||
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub visual_infos: Option<XVisualInfo>,
|
||||
pub screen_id: Option<i32>,
|
||||
pub resize_increments: Option<(u32, u32)>,
|
||||
pub base_size: Option<(u32, u32)>,
|
||||
pub class: Option<(String, String)>,
|
||||
pub override_redirect: bool,
|
||||
pub x11_window_type: x11::util::WindowType,
|
||||
pub gtk_theme_variant: Option<String>,
|
||||
pub app_id: Option<String>
|
||||
}
|
||||
|
||||
lazy_static!(
|
||||
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> = {
|
||||
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))
|
||||
};
|
||||
);
|
||||
|
||||
pub enum Window {
|
||||
X(x11::Window),
|
||||
Wayland(wayland::Window),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum WindowId {
|
||||
X(x11::WindowId),
|
||||
Wayland(wayland::WindowId),
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId::X(x11::WindowId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DeviceId {
|
||||
X(x11::DeviceId),
|
||||
Wayland(wayland::DeviceId),
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId::X(x11::DeviceId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MonitorId {
|
||||
X(x11::MonitorId),
|
||||
Wayland(wayland::MonitorId),
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_name(),
|
||||
&MonitorId::Wayland(ref m) => m.get_name(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> u32 {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_native_identifier(),
|
||||
&MonitorId::Wayland(ref m) => m.get_native_identifier(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_dimensions(),
|
||||
&MonitorId::Wayland(ref m) => m.get_dimensions(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_position(),
|
||||
&MonitorId::Wayland(ref m) => m.get_position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_hidpi_factor(),
|
||||
&MonitorId::Wayland(ref m) => m.get_hidpi_factor() as f64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
#[inline]
|
||||
pub fn new(
|
||||
events_loop: &EventsLoop,
|
||||
attribs: WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Self, CreationError> {
|
||||
match *events_loop {
|
||||
EventsLoop::Wayland(ref events_loop) => {
|
||||
wayland::Window::new(events_loop, attribs, pl_attribs).map(Window::Wayland)
|
||||
},
|
||||
EventsLoop::X(ref events_loop) => {
|
||||
x11::Window::new(events_loop, attribs, pl_attribs).map(Window::X)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
match self {
|
||||
&Window::X(ref w) => WindowId::X(w.id()),
|
||||
&Window::Wayland(ref w) => WindowId::Wayland(w.id()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, title: &str) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_title(title),
|
||||
&Window::Wayland(ref w) => w.set_title(title),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.show(),
|
||||
&Window::Wayland(ref w) => w.show(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.hide(),
|
||||
&Window::Wayland(ref w) => w.hide(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_position(),
|
||||
&Window::Wayland(ref w) => w.get_position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
match self {
|
||||
&Window::X(ref m) => m.get_inner_position(),
|
||||
&Window::Wayland(ref m) => m.get_inner_position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, position: LogicalPosition) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_position(position),
|
||||
&Window::Wayland(ref w) => w.set_position(position),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_inner_size(),
|
||||
&Window::Wayland(ref w) => w.get_inner_size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_outer_size(),
|
||||
&Window::Wayland(ref w) => w.get_outer_size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: LogicalSize) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_inner_size(size),
|
||||
&Window::Wayland(ref w) => w.set_inner_size(size),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_min_dimensions(dimensions),
|
||||
&Window::Wayland(ref w) => w.set_min_dimensions(dimensions),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_max_dimensions(dimensions),
|
||||
&Window::Wayland(ref w) => w.set_max_dimensions(dimensions),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_resizable(resizable),
|
||||
&Window::Wayland(ref w) => w.set_resizable(resizable),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, cursor: MouseCursor) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_cursor(cursor),
|
||||
&Window::Wayland(ref w) => w.set_cursor(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
|
||||
match self {
|
||||
&Window::X(ref window) => window.grab_cursor(grab),
|
||||
&Window::Wayland(ref window) => window.grab_cursor(grab),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide_cursor(&self, hide: bool) {
|
||||
match self {
|
||||
&Window::X(ref window) => window.hide_cursor(hide),
|
||||
&Window::Wayland(ref window) => window.hide_cursor(hide),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_hidpi_factor(),
|
||||
&Window::Wayland(ref w) => w.hidpi_factor() as f64,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_cursor_position(position),
|
||||
&Window::Wayland(ref w) => w.set_cursor_position(position),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_maximized(maximized),
|
||||
&Window::Wayland(ref w) => w.set_maximized(maximized),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_fullscreen(),
|
||||
&Window::Wayland(ref w) => w.get_fullscreen()
|
||||
.map(|monitor_id| RootMonitorId { inner: MonitorId::Wayland(monitor_id) })
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_fullscreen(monitor),
|
||||
&Window::Wayland(ref w) => w.set_fullscreen(monitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_decorations(decorations),
|
||||
&Window::Wayland(ref w) => w.set_decorations(decorations)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, always_on_top: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_always_on_top(always_on_top),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_window_icon(window_icon),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_spot(&self, position: LogicalPosition) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_ime_spot(position),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
match self {
|
||||
&Window::X(ref window) => RootMonitorId { inner: MonitorId::X(window.get_current_monitor()) },
|
||||
&Window::Wayland(ref window) => RootMonitorId { inner: MonitorId::Wayland(window.get_current_monitor()) },
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
match self {
|
||||
&Window::X(ref window) => window.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorId::X)
|
||||
.collect(),
|
||||
&Window::Wayland(ref window) => window.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorId::Wayland)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
match self {
|
||||
&Window::X(ref window) => MonitorId::X(window.get_primary_monitor()),
|
||||
&Window::Wayland(ref window) => MonitorId::Wayland(window.get_primary_monitor()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn x_error_callback(
|
||||
display: *mut x11::ffi::Display,
|
||||
event: *mut x11::ffi::XErrorEvent,
|
||||
) -> c_int {
|
||||
let xconn_lock = X11_BACKEND.lock();
|
||||
if let Ok(ref xconn) = *xconn_lock {
|
||||
let mut buf: [c_char; 1024] = mem::uninitialized();
|
||||
(xconn.xlib.XGetErrorText)(
|
||||
display,
|
||||
(*event).error_code as c_int,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len() as c_int,
|
||||
);
|
||||
let description = CStr::from_ptr(buf.as_ptr()).to_string_lossy();
|
||||
|
||||
let error = XError {
|
||||
description: description.into_owned(),
|
||||
error_code: (*event).error_code,
|
||||
request_code: (*event).request_code,
|
||||
minor_code: (*event).minor_code,
|
||||
};
|
||||
|
||||
error!("X11 error: {:#?}", error);
|
||||
|
||||
*xconn.latest_error.lock() = Some(error);
|
||||
}
|
||||
// Fun fact: this return value is completely ignored.
|
||||
0
|
||||
}
|
||||
|
||||
pub enum EventsLoop {
|
||||
Wayland(wayland::EventsLoop),
|
||||
X(x11::EventsLoop)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum EventsLoopProxy {
|
||||
X(x11::EventsLoopProxy),
|
||||
Wayland(wayland::EventsLoopProxy),
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn new() -> EventsLoop {
|
||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
||||
match env_var.as_str() {
|
||||
"x11" => {
|
||||
// TODO: propagate
|
||||
return EventsLoop::new_x11().expect("Failed to initialize X11 backend");
|
||||
},
|
||||
"wayland" => {
|
||||
return EventsLoop::new_wayland()
|
||||
.expect("Failed to initialize Wayland backend");
|
||||
},
|
||||
_ => panic!(
|
||||
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
|
||||
BACKEND_PREFERENCE_ENV_VAR,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
let wayland_err = match EventsLoop::new_wayland() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
let x11_err = match EventsLoop::new_x11() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
let err_string = format!(
|
||||
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
|
||||
wayland_err,
|
||||
x11_err,
|
||||
);
|
||||
panic!(err_string);
|
||||
}
|
||||
|
||||
pub fn new_wayland() -> Result<EventsLoop, ConnectError> {
|
||||
wayland::EventsLoop::new()
|
||||
.map(EventsLoop::Wayland)
|
||||
}
|
||||
|
||||
pub fn new_x11() -> Result<EventsLoop, XNotSupported> {
|
||||
X11_BACKEND
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map(Arc::clone)
|
||||
.map(x11::EventsLoop::new)
|
||||
.map(EventsLoop::X)
|
||||
.map_err(|err| err.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref evlp) => evlp
|
||||
.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorId::Wayland)
|
||||
.collect(),
|
||||
EventsLoop::X(ref evlp) => evlp
|
||||
.x_connection()
|
||||
.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorId::X)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref evlp) => MonitorId::Wayland(evlp.get_primary_monitor()),
|
||||
EventsLoop::X(ref evlp) => MonitorId::X(evlp.x_connection().get_primary_monitor()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref evlp) => EventsLoopProxy::Wayland(evlp.create_proxy()),
|
||||
EventsLoop::X(ref evlp) => EventsLoopProxy::X(evlp.create_proxy()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, callback: F)
|
||||
where F: FnMut(::Event)
|
||||
{
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref mut evlp) => evlp.poll_events(callback),
|
||||
EventsLoop::X(ref mut evlp) => evlp.poll_events(callback)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, callback: F)
|
||||
where F: FnMut(::Event) -> ControlFlow
|
||||
{
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref mut evlp) => evlp.run_forever(callback),
|
||||
EventsLoop::X(ref mut evlp) => evlp.run_forever(callback)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_wayland(&self) -> bool {
|
||||
match *self {
|
||||
EventsLoop::Wayland(_) => true,
|
||||
EventsLoop::X(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x_connection(&self) -> Option<&Arc<XConnection>> {
|
||||
match *self {
|
||||
EventsLoop::Wayland(_) => None,
|
||||
EventsLoop::X(ref ev) => Some(ev.x_connection()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
|
||||
match *self {
|
||||
EventsLoopProxy::Wayland(ref proxy) => proxy.wakeup(),
|
||||
EventsLoopProxy::X(ref proxy) => proxy.wakeup(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,538 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
|
||||
use {ControlFlow, EventsLoopClosed, PhysicalPosition, PhysicalSize};
|
||||
|
||||
use super::window::WindowStore;
|
||||
use super::WindowId;
|
||||
|
||||
use sctk::output::OutputMgr;
|
||||
use sctk::reexports::client::protocol::{
|
||||
wl_keyboard, wl_output, wl_pointer, wl_registry, wl_seat, wl_touch,
|
||||
};
|
||||
use sctk::reexports::client::{ConnectError, Display, EventQueue, GlobalEvent, Proxy};
|
||||
use sctk::Environment;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_display::RequestsTrait as DisplayRequests;
|
||||
use sctk::reexports::client::protocol::wl_surface::RequestsTrait;
|
||||
|
||||
use ModifiersState;
|
||||
|
||||
pub struct EventsLoopSink {
|
||||
buffer: VecDeque<::Event>,
|
||||
}
|
||||
|
||||
impl EventsLoopSink {
|
||||
pub fn new() -> EventsLoopSink {
|
||||
EventsLoopSink {
|
||||
buffer: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_event(&mut self, evt: ::WindowEvent, wid: WindowId) {
|
||||
let evt = ::Event::WindowEvent {
|
||||
event: evt,
|
||||
window_id: ::WindowId(::platform::WindowId::Wayland(wid)),
|
||||
};
|
||||
self.buffer.push_back(evt);
|
||||
}
|
||||
|
||||
pub fn send_raw_event(&mut self, evt: ::Event) {
|
||||
self.buffer.push_back(evt);
|
||||
}
|
||||
|
||||
fn empty_with<F>(&mut self, callback: &mut F)
|
||||
where
|
||||
F: FnMut(::Event),
|
||||
{
|
||||
for evt in self.buffer.drain(..) {
|
||||
callback(evt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventsLoop {
|
||||
// The Event Queue
|
||||
pub evq: RefCell<EventQueue>,
|
||||
// our sink, shared with some handlers, buffering the events
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
// Whether or not there is a pending `Awakened` event to be emitted.
|
||||
pending_wakeup: Arc<AtomicBool>,
|
||||
// The window store
|
||||
pub store: 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, Proxy<wl_seat::WlSeat>)>>>,
|
||||
}
|
||||
|
||||
// A handle that can be sent across threads and used to wake up the `EventsLoop`.
|
||||
//
|
||||
// We should only try and wake up the `EventsLoop` if it still exists, so we hold Weak ptrs.
|
||||
#[derive(Clone)]
|
||||
pub struct EventsLoopProxy {
|
||||
display: Weak<Display>,
|
||||
pending_wakeup: Weak<AtomicBool>,
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
// Causes the `EventsLoop` to stop blocking on `run_forever` and emit an `Awakened` event.
|
||||
//
|
||||
// Returns `Err` if the associated `EventsLoop` no longer exists.
|
||||
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
|
||||
let display = self.display.upgrade();
|
||||
let wakeup = self.pending_wakeup.upgrade();
|
||||
match (display, wakeup) {
|
||||
(Some(display), Some(wakeup)) => {
|
||||
// Update the `EventsLoop`'s `pending_wakeup` flag.
|
||||
wakeup.store(true, Ordering::Relaxed);
|
||||
// Cause the `EventsLoop` to break from `dispatch` if it is currently blocked.
|
||||
let _ = display.sync(|callback| callback.implement(|_, _| {}, ()));
|
||||
display.flush().map_err(|_| EventsLoopClosed)?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(EventsLoopClosed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn new() -> Result<EventsLoop, ConnectError> {
|
||||
let (display, mut event_queue) = Display::connect_to_env()?;
|
||||
|
||||
let display = Arc::new(display);
|
||||
let pending_wakeup = Arc::new(AtomicBool::new(false));
|
||||
let sink = Arc::new(Mutex::new(EventsLoopSink::new()));
|
||||
let store = Arc::new(Mutex::new(WindowStore::new()));
|
||||
let seats = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
let mut seat_manager = SeatManager {
|
||||
sink: sink.clone(),
|
||||
store: store.clone(),
|
||||
seats: seats.clone(),
|
||||
events_loop_proxy: EventsLoopProxy {
|
||||
display: Arc::downgrade(&display),
|
||||
pending_wakeup: Arc::downgrade(&pending_wakeup),
|
||||
},
|
||||
};
|
||||
|
||||
let env = Environment::from_display_with_cb(
|
||||
&display,
|
||||
&mut event_queue,
|
||||
move |event, registry| {
|
||||
match event {
|
||||
GlobalEvent::New { id, ref interface, version } => {
|
||||
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();
|
||||
|
||||
Ok(EventsLoop {
|
||||
display,
|
||||
evq: RefCell::new(event_queue),
|
||||
sink,
|
||||
pending_wakeup,
|
||||
store,
|
||||
env,
|
||||
cleanup_needed: Arc::new(Mutex::new(false)),
|
||||
seats,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
EventsLoopProxy {
|
||||
display: Arc::downgrade(&self.display),
|
||||
pending_wakeup: Arc::downgrade(&self.pending_wakeup),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where
|
||||
F: FnMut(::Event),
|
||||
{
|
||||
// send pending events to the server
|
||||
self.display.flush().expect("Wayland connection lost.");
|
||||
|
||||
// dispatch any pre-buffered events
|
||||
self.sink.lock().unwrap().empty_with(&mut callback);
|
||||
|
||||
// try to read pending events
|
||||
if let Some(h) = self.evq.get_mut().prepare_read() {
|
||||
h.read_events().expect("Wayland connection lost.");
|
||||
}
|
||||
// dispatch wayland events
|
||||
self.evq
|
||||
.get_mut()
|
||||
.dispatch_pending()
|
||||
.expect("Wayland connection lost.");
|
||||
self.post_dispatch_triggers();
|
||||
|
||||
// dispatch buffered events to client
|
||||
self.sink.lock().unwrap().empty_with(&mut callback);
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
||||
where
|
||||
F: FnMut(::Event) -> ControlFlow,
|
||||
{
|
||||
// send pending events to the server
|
||||
self.display.flush().expect("Wayland connection lost.");
|
||||
|
||||
// Check for control flow by wrapping the callback.
|
||||
let control_flow = ::std::cell::Cell::new(ControlFlow::Continue);
|
||||
let mut callback = |event| {
|
||||
if let ControlFlow::Break = callback(event) {
|
||||
control_flow.set(ControlFlow::Break);
|
||||
}
|
||||
};
|
||||
|
||||
// dispatch any pre-buffered events
|
||||
self.post_dispatch_triggers();
|
||||
self.sink.lock().unwrap().empty_with(&mut callback);
|
||||
|
||||
loop {
|
||||
// dispatch events blocking if needed
|
||||
self.evq
|
||||
.get_mut()
|
||||
.dispatch()
|
||||
.expect("Wayland connection lost.");
|
||||
self.post_dispatch_triggers();
|
||||
|
||||
// empty buffer of events
|
||||
self.sink.lock().unwrap().empty_with(&mut callback);
|
||||
|
||||
if let ControlFlow::Break = control_flow.get() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
get_primary_monitor(&self.env.outputs)
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
get_available_monitors(&self.env.outputs)
|
||||
}
|
||||
|
||||
pub fn get_display(&self) -> &Display {
|
||||
&*self.display
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Private EventsLoop Internals
|
||||
*/
|
||||
|
||||
impl EventsLoop {
|
||||
fn post_dispatch_triggers(&mut self) {
|
||||
let mut sink = self.sink.lock().unwrap();
|
||||
// process a possible pending wakeup call
|
||||
if self.pending_wakeup.load(Ordering::Relaxed) {
|
||||
sink.send_raw_event(::Event::Awakened);
|
||||
self.pending_wakeup.store(false, Ordering::Relaxed);
|
||||
}
|
||||
// prune possible dead windows
|
||||
{
|
||||
let mut cleanup_needed = self.cleanup_needed.lock().unwrap();
|
||||
if *cleanup_needed {
|
||||
let pruned = self.store.lock().unwrap().cleanup();
|
||||
*cleanup_needed = false;
|
||||
for wid in pruned {
|
||||
sink.send_event(::WindowEvent::Destroyed, wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
// process pending resize/refresh
|
||||
self.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 = ::LogicalSize::new(w as f64, h as f64);
|
||||
sink.send_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_event(::WindowEvent::HiDpiFactorChanged(dpi as f64), wid);
|
||||
}
|
||||
if refresh {
|
||||
sink.send_event(::WindowEvent::Refresh, wid);
|
||||
}
|
||||
if closed {
|
||||
sink.send_event(::WindowEvent::CloseRequested, wid);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Wayland protocol implementations
|
||||
*/
|
||||
|
||||
struct SeatManager {
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
seats: Arc<Mutex<Vec<(u32, Proxy<wl_seat::WlSeat>)>>>,
|
||||
events_loop_proxy: EventsLoopProxy,
|
||||
}
|
||||
|
||||
impl SeatManager {
|
||||
fn add_seat(&mut self, id: u32, version: u32, registry: Proxy<wl_registry::WlRegistry>) {
|
||||
use self::wl_registry::RequestsTrait as RegistryRequests;
|
||||
use std::cmp::min;
|
||||
|
||||
let mut seat_data = SeatData {
|
||||
sink: self.sink.clone(),
|
||||
store: self.store.clone(),
|
||||
pointer: None,
|
||||
keyboard: None,
|
||||
touch: None,
|
||||
events_loop_proxy: self.events_loop_proxy.clone(),
|
||||
modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())),
|
||||
};
|
||||
let seat = registry
|
||||
.bind(min(version, 5), id, move |seat| {
|
||||
seat.implement(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) {
|
||||
use self::wl_seat::RequestsTrait as SeatRequests;
|
||||
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.version() >= 5 {
|
||||
seat.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SeatData {
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
pointer: Option<Proxy<wl_pointer::WlPointer>>,
|
||||
keyboard: Option<Proxy<wl_keyboard::WlKeyboard>>,
|
||||
touch: Option<Proxy<wl_touch::WlTouch>>,
|
||||
events_loop_proxy: EventsLoopProxy,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
}
|
||||
|
||||
impl SeatData {
|
||||
fn receive(&mut self, evt: wl_seat::Event, seat: Proxy<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(),
|
||||
))
|
||||
}
|
||||
// destroy pointer if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Pointer) {
|
||||
if let Some(pointer) = self.pointer.take() {
|
||||
if pointer.version() >= 3 {
|
||||
use self::wl_pointer::RequestsTrait;
|
||||
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.sink.clone(),
|
||||
self.events_loop_proxy.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.version() >= 3 {
|
||||
use self::wl_keyboard::RequestsTrait;
|
||||
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.version() >= 3 {
|
||||
use self::wl_touch::RequestsTrait;
|
||||
touch.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SeatData {
|
||||
fn drop(&mut self) {
|
||||
if let Some(pointer) = self.pointer.take() {
|
||||
if pointer.version() >= 3 {
|
||||
use self::wl_pointer::RequestsTrait;
|
||||
pointer.release();
|
||||
}
|
||||
}
|
||||
if let Some(kbd) = self.keyboard.take() {
|
||||
if kbd.version() >= 3 {
|
||||
use self::wl_keyboard::RequestsTrait;
|
||||
kbd.release();
|
||||
}
|
||||
}
|
||||
if let Some(touch) = self.touch.take() {
|
||||
if touch.version() >= 3 {
|
||||
use self::wl_touch::RequestsTrait;
|
||||
touch.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Monitor stuff
|
||||
*/
|
||||
|
||||
pub struct MonitorId {
|
||||
pub(crate) proxy: Proxy<wl_output::WlOutput>,
|
||||
pub(crate) mgr: OutputMgr,
|
||||
}
|
||||
|
||||
impl Clone for MonitorId {
|
||||
fn clone(&self) -> MonitorId {
|
||||
MonitorId {
|
||||
proxy: self.proxy.clone(),
|
||||
mgr: self.mgr.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorId {
|
||||
name: Option<String>,
|
||||
native_identifier: u32,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: i32,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorId {
|
||||
name: self.get_name(),
|
||||
native_identifier: self.get_native_identifier(),
|
||||
dimensions: self.get_dimensions(),
|
||||
position: self.get_position(),
|
||||
hidpi_factor: self.get_hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
self.mgr.with_info(&self.proxy, |_, info| {
|
||||
format!("{} ({})", info.model, info.make)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> u32 {
|
||||
self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn get_dimensions(&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 get_position(&self) -> PhysicalPosition {
|
||||
self.mgr
|
||||
.with_info(&self.proxy, |_, info| info.location)
|
||||
.unwrap_or((0, 0))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> i32 {
|
||||
self.mgr
|
||||
.with_info(&self.proxy, |_, info| info.scale_factor)
|
||||
.unwrap_or(1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(outputs: &OutputMgr) -> MonitorId {
|
||||
outputs.with_all(|list| {
|
||||
if let Some(&(_, ref proxy, _)) = list.first() {
|
||||
MonitorId {
|
||||
proxy: proxy.clone(),
|
||||
mgr: outputs.clone(),
|
||||
}
|
||||
} else {
|
||||
panic!("No monitor is available.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(outputs: &OutputMgr) -> VecDeque<MonitorId> {
|
||||
outputs.with_all(|list| {
|
||||
list.iter()
|
||||
.map(|&(_, ref proxy, _)| MonitorId {
|
||||
proxy: proxy.clone(),
|
||||
mgr: outputs.clone(),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd",
|
||||
target_os = "netbsd", target_os = "openbsd"))]
|
||||
|
||||
pub use self::window::Window;
|
||||
pub use self::event_loop::{EventsLoop, EventsLoopProxy, EventsLoopSink, MonitorId};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_surface;
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
mod event_loop;
|
||||
mod pointer;
|
||||
mod touch;
|
||||
mod keyboard;
|
||||
mod window;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(usize);
|
||||
|
||||
#[inline]
|
||||
fn make_wid(s: &Proxy<wl_surface::WlSurface>) -> WindowId {
|
||||
WindowId(s.c_ptr() as usize)
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use {ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent};
|
||||
use events::ModifiersState;
|
||||
|
||||
use super::DeviceId;
|
||||
use super::event_loop::EventsLoopSink;
|
||||
use super::window::WindowStore;
|
||||
|
||||
use sctk::reexports::client::Proxy;
|
||||
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PtrEvent, WlPointer};
|
||||
use sctk::reexports::client::protocol::wl_seat;
|
||||
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
|
||||
|
||||
pub fn implement_pointer(
|
||||
seat: &Proxy<wl_seat::WlSeat>,
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
) -> Proxy<WlPointer> {
|
||||
let mut mouse_focus = None;
|
||||
let mut axis_buffer = None;
|
||||
let mut axis_discrete_buffer = None;
|
||||
let mut axis_state = TouchPhase::Ended;
|
||||
|
||||
seat.get_pointer(|pointer| {
|
||||
pointer.implement(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_event(
|
||||
WindowEvent::CursorEntered {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
sink.send_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: ::DeviceId(::platform::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_event(
|
||||
WindowEvent::CursorLeft {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Motion {
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
sink.send_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: ::DeviceId(::platform::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,
|
||||
};
|
||||
let button = match button {
|
||||
0x110 => MouseButton::Left,
|
||||
0x111 => MouseButton::Right,
|
||||
0x112 => MouseButton::Middle,
|
||||
// TODO figure out the translation ?
|
||||
_ => return,
|
||||
};
|
||||
sink.send_event(
|
||||
WindowEvent::MouseInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
state: state,
|
||||
button: button,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Axis { axis, value, .. } => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
if pointer.version() < 5 {
|
||||
let (mut x, mut y) = (0.0, 0.0);
|
||||
// old seat compatibility
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
||||
}
|
||||
sink.send_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::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,
|
||||
}
|
||||
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_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::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_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::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,
|
||||
}
|
||||
axis_discrete_buffer = Some((x, y));
|
||||
axis_state = match axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ())
|
||||
}).unwrap()
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use {TouchPhase, WindowEvent};
|
||||
|
||||
use super::{DeviceId, WindowId};
|
||||
use super::event_loop::EventsLoopSink;
|
||||
use super::window::WindowStore;
|
||||
|
||||
use sctk::reexports::client::Proxy;
|
||||
use sctk::reexports::client::protocol::wl_touch::{Event as TouchEvent, WlTouch};
|
||||
use sctk::reexports::client::protocol::wl_seat;
|
||||
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
|
||||
|
||||
struct TouchPoint {
|
||||
wid: WindowId,
|
||||
location: (f64, f64),
|
||||
id: i32,
|
||||
}
|
||||
|
||||
pub(crate) fn implement_touch(
|
||||
seat: &Proxy<wl_seat::WlSeat>,
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
) -> Proxy<WlTouch> {
|
||||
let mut pending_ids = Vec::new();
|
||||
seat.get_touch(|touch| {
|
||||
touch.implement(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_event(
|
||||
WindowEvent::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Started,
|
||||
location: (x, y).into(),
|
||||
id: id as u64,
|
||||
}),
|
||||
wid,
|
||||
);
|
||||
pending_ids.push(TouchPoint {
|
||||
wid: wid,
|
||||
location: (x, y),
|
||||
id: 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_event(
|
||||
WindowEvent::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::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_event(
|
||||
WindowEvent::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::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_event(
|
||||
WindowEvent::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location: pt.location.into(),
|
||||
id: pt.id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
},
|
||||
}
|
||||
}, ())
|
||||
}).unwrap()
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
pub use x11_dl::keysym::*;
|
||||
pub use x11_dl::xcursor::*;
|
||||
pub use x11_dl::xlib::*;
|
||||
pub use x11_dl::xinput::*;
|
||||
pub use x11_dl::xinput2::*;
|
||||
pub use x11_dl::xlib_xcb::*;
|
||||
pub use x11_dl::error::OpenError;
|
||||
pub use x11_dl::xrandr::*;
|
||||
@@ -1,274 +0,0 @@
|
||||
use std::os::raw::*;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use {PhysicalPosition, PhysicalSize};
|
||||
use super::{util, XConnection, XError};
|
||||
use super::ffi::{
|
||||
RRCrtcChangeNotifyMask,
|
||||
RROutputPropertyNotifyMask,
|
||||
RRScreenChangeNotifyMask,
|
||||
True,
|
||||
Window,
|
||||
XRRScreenResources,
|
||||
};
|
||||
|
||||
// Used to test XRandR < 1.5 code path. This should always be committed as false.
|
||||
const FORCE_RANDR_COMPAT: bool = false;
|
||||
// Also used for testing. This should always be committed as false.
|
||||
const DISABLE_MONITOR_LIST_CACHING: bool = false;
|
||||
|
||||
lazy_static! {
|
||||
static ref XRANDR_VERSION: Mutex<Option<(c_int, c_int)>> = Mutex::default();
|
||||
static ref MONITORS: Mutex<Option<Vec<MonitorId>>> = Mutex::default();
|
||||
}
|
||||
|
||||
fn version_is_at_least(major: c_int, minor: c_int) -> bool {
|
||||
if let Some((avail_major, avail_minor)) = *XRANDR_VERSION.lock() {
|
||||
if avail_major == major {
|
||||
avail_minor >= minor
|
||||
} else {
|
||||
avail_major > major
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorId>> {
|
||||
// We update this lazily.
|
||||
(*MONITORS.lock()).take()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorId {
|
||||
/// The actual id
|
||||
id: u32,
|
||||
/// The name of the monitor
|
||||
pub(crate) name: String,
|
||||
/// The size of the monitor
|
||||
dimensions: (u32, u32),
|
||||
/// The position of the monitor in the X screen
|
||||
position: (i32, i32),
|
||||
/// If the monitor is the primary one
|
||||
primary: bool,
|
||||
/// The DPI scale factor
|
||||
pub(crate) hidpi_factor: f64,
|
||||
/// Used to determine which windows are on this monitor
|
||||
pub(crate) rect: util::AaRect,
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
fn from_repr(
|
||||
xconn: &XConnection,
|
||||
resources: *mut XRRScreenResources,
|
||||
id: u32,
|
||||
repr: util::MonitorRepr,
|
||||
primary: bool,
|
||||
) -> Option<Self> {
|
||||
let (name, hidpi_factor) = unsafe { xconn.get_output_info(resources, &repr)? };
|
||||
let (dimensions, position) = unsafe { (repr.get_dimensions(), repr.get_position()) };
|
||||
let rect = util::AaRect::new(position, dimensions);
|
||||
Some(MonitorId {
|
||||
id,
|
||||
name,
|
||||
hidpi_factor,
|
||||
dimensions,
|
||||
position,
|
||||
primary,
|
||||
rect,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some(self.name.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> u32 {
|
||||
self.id as u32
|
||||
}
|
||||
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
self.dimensions.into()
|
||||
}
|
||||
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
self.position.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.hidpi_factor
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorId {
|
||||
let monitors = self.get_available_monitors();
|
||||
let default = monitors
|
||||
.get(0)
|
||||
.expect("[winit] Failed to find any monitors using XRandR.");
|
||||
|
||||
let window_rect = match window_rect {
|
||||
Some(rect) => rect,
|
||||
None => return default.to_owned(),
|
||||
};
|
||||
|
||||
let mut largest_overlap = 0;
|
||||
let mut matched_monitor = default;
|
||||
for monitor in &monitors {
|
||||
let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
|
||||
if overlapping_area > largest_overlap {
|
||||
largest_overlap = overlapping_area;
|
||||
matched_monitor = &monitor;
|
||||
}
|
||||
}
|
||||
|
||||
matched_monitor.to_owned()
|
||||
}
|
||||
|
||||
fn query_monitor_list(&self) -> Vec<MonitorId> {
|
||||
unsafe {
|
||||
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
||||
let resources = if version_is_at_least(1, 3) {
|
||||
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
||||
} else {
|
||||
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
|
||||
// Upon failure, `resources` will be null.
|
||||
(self.xrandr.XRRGetScreenResources)(self.display, root)
|
||||
};
|
||||
|
||||
if resources.is_null() {
|
||||
panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
|
||||
}
|
||||
|
||||
let mut available;
|
||||
let mut has_primary = false;
|
||||
|
||||
if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT {
|
||||
// We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and
|
||||
// videowalls.
|
||||
let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap();
|
||||
let mut monitor_count = 0;
|
||||
let monitors = (xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count);
|
||||
assert!(monitor_count >= 0);
|
||||
available = Vec::with_capacity(monitor_count as usize);
|
||||
for monitor_index in 0..monitor_count {
|
||||
let monitor = monitors.offset(monitor_index as isize);
|
||||
let is_primary = (*monitor).primary != 0;
|
||||
has_primary |= is_primary;
|
||||
MonitorId::from_repr(
|
||||
self,
|
||||
resources,
|
||||
monitor_index as u32,
|
||||
monitor.into(),
|
||||
is_primary,
|
||||
).map(|monitor_id| available.push(monitor_id));
|
||||
}
|
||||
(xrandr_1_5.XRRFreeMonitors)(monitors);
|
||||
} else {
|
||||
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and
|
||||
// videowall setups will also show monitors that aren't in the logical groups the user
|
||||
// cares about.
|
||||
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
|
||||
available = Vec::with_capacity((*resources).ncrtc as usize);
|
||||
for crtc_index in 0..(*resources).ncrtc {
|
||||
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
|
||||
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
||||
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
|
||||
if is_active {
|
||||
let crtc = util::MonitorRepr::from(crtc);
|
||||
let is_primary = crtc.get_output() == primary;
|
||||
has_primary |= is_primary;
|
||||
MonitorId::from_repr(
|
||||
self,
|
||||
resources,
|
||||
crtc_id as u32,
|
||||
crtc,
|
||||
is_primary,
|
||||
).map(|monitor_id| available.push(monitor_id));
|
||||
}
|
||||
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
||||
}
|
||||
}
|
||||
|
||||
// If no monitors were detected as being primary, we just pick one ourselves!
|
||||
if !has_primary {
|
||||
if let Some(ref mut fallback) = available.first_mut() {
|
||||
// Setting this here will come in handy if we ever add an `is_primary` method.
|
||||
fallback.primary = true;
|
||||
}
|
||||
}
|
||||
|
||||
(self.xrandr.XRRFreeScreenResources)(resources);
|
||||
available
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(&self) -> Vec<MonitorId> {
|
||||
let mut monitors_lock = MONITORS.lock();
|
||||
(*monitors_lock)
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
let monitors = Some(self.query_monitor_list());
|
||||
if !DISABLE_MONITOR_LIST_CACHING {
|
||||
(*monitors_lock) = monitors.clone();
|
||||
}
|
||||
monitors
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
self.get_available_monitors()
|
||||
.into_iter()
|
||||
.find(|monitor| monitor.primary)
|
||||
.expect("[winit] Failed to find any monitors using XRandR.")
|
||||
}
|
||||
|
||||
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
|
||||
{
|
||||
let mut version_lock = XRANDR_VERSION.lock();
|
||||
if version_lock.is_none() {
|
||||
let mut major = 0;
|
||||
let mut minor = 0;
|
||||
let has_extension = unsafe {
|
||||
(self.xrandr.XRRQueryVersion)(
|
||||
self.display,
|
||||
&mut major,
|
||||
&mut minor,
|
||||
)
|
||||
};
|
||||
if has_extension != True {
|
||||
panic!("[winit] XRandR extension not available.");
|
||||
}
|
||||
*version_lock = Some((major, minor));
|
||||
}
|
||||
}
|
||||
|
||||
let mut event_offset = 0;
|
||||
let mut error_offset = 0;
|
||||
let status = unsafe {
|
||||
(self.xrandr.XRRQueryExtension)(
|
||||
self.display,
|
||||
&mut event_offset,
|
||||
&mut error_offset,
|
||||
)
|
||||
};
|
||||
|
||||
if status != True {
|
||||
self.check_errors()?;
|
||||
unreachable!("[winit] `XRRQueryExtension` failed but no error was received.");
|
||||
}
|
||||
|
||||
let mask = RRCrtcChangeNotifyMask
|
||||
| RROutputPropertyNotifyMask
|
||||
| RRScreenChangeNotifyMask;
|
||||
unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) };
|
||||
|
||||
Ok(event_offset)
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
use std::str;
|
||||
|
||||
use super::*;
|
||||
use events::ModifiersState;
|
||||
|
||||
pub const VIRTUAL_CORE_POINTER: c_int = 2;
|
||||
pub const VIRTUAL_CORE_KEYBOARD: c_int = 3;
|
||||
|
||||
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
|
||||
// re-allocate (and make another round-trip) in the *vast* majority of cases.
|
||||
// To test if `lookup_utf8` works correctly, set this to 1.
|
||||
const TEXT_BUFFER_SIZE: usize = 1024;
|
||||
|
||||
impl From<ffi::XIModifierState> for ModifiersState {
|
||||
fn from(mods: ffi::XIModifierState) -> Self {
|
||||
let state = mods.effective as c_uint;
|
||||
ModifiersState {
|
||||
alt: state & ffi::Mod1Mask != 0,
|
||||
shift: state & ffi::ShiftMask != 0,
|
||||
ctrl: state & ffi::ControlMask != 0,
|
||||
logo: state & ffi::Mod4Mask != 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PointerState<'a> {
|
||||
xconn: &'a XConnection,
|
||||
root: ffi::Window,
|
||||
child: ffi::Window,
|
||||
pub root_x: c_double,
|
||||
pub root_y: c_double,
|
||||
win_x: c_double,
|
||||
win_y: c_double,
|
||||
buttons: ffi::XIButtonState,
|
||||
modifiers: ffi::XIModifierState,
|
||||
group: ffi::XIGroupState,
|
||||
relative_to_window: bool,
|
||||
}
|
||||
|
||||
impl<'a> PointerState<'a> {
|
||||
pub fn get_modifier_state(&self) -> ModifiersState {
|
||||
self.modifiers.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for PointerState<'a> {
|
||||
fn drop(&mut self) {
|
||||
if !self.buttons.mask.is_null() {
|
||||
unsafe {
|
||||
// This is why you need to read the docs carefully...
|
||||
(self.xconn.xlib.XFree)(self.buttons.mask as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn select_xinput_events(&self, window: c_ulong, device_id: c_int, mask: i32) -> Flusher {
|
||||
let mut event_mask = ffi::XIEventMask {
|
||||
deviceid: device_id,
|
||||
mask: &mask as *const _ as *mut c_uchar,
|
||||
mask_len: mem::size_of_val(&mask) as c_int,
|
||||
};
|
||||
unsafe {
|
||||
(self.xinput2.XISelectEvents)(
|
||||
self.display,
|
||||
window,
|
||||
&mut event_mask as *mut ffi::XIEventMask,
|
||||
1, // number of masks to read from pointer above
|
||||
);
|
||||
}
|
||||
Flusher::new(self)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn select_xkb_events(&self, device_id: c_uint, mask: c_ulong) -> Option<Flusher> {
|
||||
let status = unsafe {
|
||||
(self.xlib.XkbSelectEvents)(
|
||||
self.display,
|
||||
device_id,
|
||||
mask,
|
||||
mask,
|
||||
)
|
||||
};
|
||||
if status == ffi::True {
|
||||
Some(Flusher::new(self))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_pointer(&self, window: ffi::Window, device_id: c_int) -> Result<PointerState, XError> {
|
||||
unsafe {
|
||||
let mut pointer_state: PointerState = mem::uninitialized();
|
||||
pointer_state.xconn = self;
|
||||
pointer_state.relative_to_window = (self.xinput2.XIQueryPointer)(
|
||||
self.display,
|
||||
device_id,
|
||||
window,
|
||||
&mut pointer_state.root,
|
||||
&mut pointer_state.child,
|
||||
&mut pointer_state.root_x,
|
||||
&mut pointer_state.root_y,
|
||||
&mut pointer_state.win_x,
|
||||
&mut pointer_state.win_y,
|
||||
&mut pointer_state.buttons,
|
||||
&mut pointer_state.modifiers,
|
||||
&mut pointer_state.group,
|
||||
) == ffi::True;
|
||||
if let Err(err) = self.check_errors() {
|
||||
// Running the destrutor would be bad news for us...
|
||||
mem::forget(pointer_state);
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(pointer_state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_utf8_inner(
|
||||
&self,
|
||||
ic: ffi::XIC,
|
||||
key_event: &mut ffi::XKeyEvent,
|
||||
buffer: &mut [u8],
|
||||
) -> (ffi::KeySym, ffi::Status, c_int) {
|
||||
let mut keysym: ffi::KeySym = 0;
|
||||
let mut status: ffi::Status = 0;
|
||||
let count = unsafe {
|
||||
(self.xlib.Xutf8LookupString)(
|
||||
ic,
|
||||
key_event,
|
||||
buffer.as_mut_ptr() as *mut c_char,
|
||||
buffer.len() as c_int,
|
||||
&mut keysym,
|
||||
&mut status,
|
||||
)
|
||||
};
|
||||
(keysym, status, count)
|
||||
}
|
||||
|
||||
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
|
||||
let mut buffer: [u8; TEXT_BUFFER_SIZE] = unsafe { mem::uninitialized() };
|
||||
let (_, status, count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
|
||||
// The buffer overflowed, so we'll make a new one on the heap.
|
||||
if status == ffi::XBufferOverflow {
|
||||
let mut buffer = Vec::with_capacity(count as usize);
|
||||
unsafe { buffer.set_len(count as usize) };
|
||||
let (_, _, new_count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
|
||||
debug_assert_eq!(count, new_count);
|
||||
str::from_utf8(&buffer[..count as usize])
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
} else {
|
||||
str::from_utf8(&buffer[..count as usize])
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
use std::{env, slice};
|
||||
use std::str::FromStr;
|
||||
|
||||
use validate_hidpi_factor;
|
||||
use super::*;
|
||||
|
||||
pub fn calc_dpi_factor(
|
||||
(width_px, height_px): (u32, u32),
|
||||
(width_mm, height_mm): (u64, u64),
|
||||
) -> f64 {
|
||||
// Override DPI if `WINIT_HIDPI_FACTOR` variable is set
|
||||
let dpi_override = env::var("WINIT_HIDPI_FACTOR")
|
||||
.ok()
|
||||
.and_then(|var| f64::from_str(&var).ok());
|
||||
if let Some(dpi_override) = dpi_override {
|
||||
if !validate_hidpi_factor(dpi_override) {
|
||||
panic!(
|
||||
"`WINIT_HIDPI_FACTOR` invalid; DPI factors must be normal floats greater than 0. Got `{}`",
|
||||
dpi_override,
|
||||
);
|
||||
}
|
||||
return dpi_override;
|
||||
}
|
||||
|
||||
// See http://xpra.org/trac/ticket/728 for more information.
|
||||
if width_mm == 0 || height_mm == 0 {
|
||||
warn!("XRandR reported that the display's 0mm in size, which is certifiably insane");
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
let ppmm = (
|
||||
(width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)
|
||||
).sqrt();
|
||||
// Quantize 1/12 step size
|
||||
let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0);
|
||||
assert!(validate_hidpi_factor(dpi_factor));
|
||||
dpi_factor
|
||||
}
|
||||
|
||||
pub enum MonitorRepr {
|
||||
Monitor(*mut ffi::XRRMonitorInfo),
|
||||
Crtc(*mut ffi::XRRCrtcInfo),
|
||||
}
|
||||
|
||||
impl MonitorRepr {
|
||||
pub unsafe fn get_output(&self) -> ffi::RROutput {
|
||||
match *self {
|
||||
// Same member names, but different locations within the struct...
|
||||
MonitorRepr::Monitor(monitor) => *((*monitor).outputs.offset(0)),
|
||||
MonitorRepr::Crtc(crtc) => *((*crtc).outputs.offset(0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_dimensions(&self) -> (u32, u32) {
|
||||
match *self {
|
||||
MonitorRepr::Monitor(monitor) => ((*monitor).width as u32, (*monitor).height as u32),
|
||||
MonitorRepr::Crtc(crtc) => ((*crtc).width as u32, (*crtc).height as u32),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_position(&self) -> (i32, i32) {
|
||||
match *self {
|
||||
MonitorRepr::Monitor(monitor) => ((*monitor).x as i32, (*monitor).y as i32),
|
||||
MonitorRepr::Crtc(crtc) => ((*crtc).x as i32, (*crtc).y as i32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<*mut ffi::XRRMonitorInfo> for MonitorRepr {
|
||||
fn from(monitor: *mut ffi::XRRMonitorInfo) -> Self {
|
||||
MonitorRepr::Monitor(monitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr {
|
||||
fn from(crtc: *mut ffi::XRRCrtcInfo) -> Self {
|
||||
MonitorRepr::Crtc(crtc)
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
// Retrieve DPI from Xft.dpi property
|
||||
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
|
||||
(self.xlib.XrmInitialize)();
|
||||
let resource_manager_str = (self.xlib.XResourceManagerString)(self.display);
|
||||
if resource_manager_str == ptr::null_mut() {
|
||||
return None;
|
||||
}
|
||||
if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() {
|
||||
let name : &str = "Xft.dpi:\t";
|
||||
for pair in res.split("\n") {
|
||||
if pair.starts_with(&name) {
|
||||
let res = &pair[name.len()..];
|
||||
return f64::from_str(&res).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub unsafe fn get_output_info(
|
||||
&self,
|
||||
resources: *mut ffi::XRRScreenResources,
|
||||
repr: &MonitorRepr,
|
||||
) -> Option<(String, f64)> {
|
||||
let output_info = (self.xrandr.XRRGetOutputInfo)(
|
||||
self.display,
|
||||
resources,
|
||||
repr.get_output(),
|
||||
);
|
||||
if output_info.is_null() {
|
||||
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
|
||||
// it's possible for it to return null.
|
||||
// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816596
|
||||
let _ = self.check_errors(); // discard `BadRROutput` error
|
||||
return None;
|
||||
}
|
||||
let name_slice = slice::from_raw_parts(
|
||||
(*output_info).name as *mut u8,
|
||||
(*output_info).nameLen as usize,
|
||||
);
|
||||
let name = String::from_utf8_lossy(name_slice).into();
|
||||
let hidpi_factor = if let Some(dpi) = self.get_xft_dpi() {
|
||||
dpi / 96.
|
||||
} else {
|
||||
calc_dpi_factor(
|
||||
repr.get_dimensions(),
|
||||
((*output_info).mm_width as u64, (*output_info).mm_height as u64),
|
||||
)
|
||||
};
|
||||
|
||||
(self.xrandr.XRRFreeOutputInfo)(output_info);
|
||||
Some((name, hidpi_factor))
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,51 @@
|
||||
#![cfg(target_os = "macos")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
use {LogicalSize, MonitorId, Window, WindowBuilder};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
monitor::MonitorHandle,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Corresponds to `NSRequestUserAttentionType`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum RequestUserAttentionType {
|
||||
/// Corresponds to `NSCriticalRequest`.
|
||||
///
|
||||
/// Dock icon will bounce until the application is focused.
|
||||
Critical,
|
||||
|
||||
/// Corresponds to `NSInformationalRequest`.
|
||||
///
|
||||
/// Dock icon will bounce once.
|
||||
Informational,
|
||||
}
|
||||
|
||||
impl Default for RequestUserAttentionType {
|
||||
fn default() -> Self {
|
||||
RequestUserAttentionType::Critical
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to MacOS.
|
||||
pub trait WindowExt {
|
||||
pub trait WindowExtMacOS {
|
||||
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
fn get_nswindow(&self) -> *mut c_void;
|
||||
fn ns_window(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the cocoa `NSView` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
fn get_nsview(&self) -> *mut c_void;
|
||||
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.
|
||||
///
|
||||
/// The `is_critical` flag has the following effects:
|
||||
/// - `false`: the dock icon will only bounce once.
|
||||
/// - `true`: the dock icon will bounce until the application is focused.
|
||||
fn request_user_attention(&self, is_critical: bool);
|
||||
fn request_user_attention(&self, request_type: RequestUserAttentionType);
|
||||
|
||||
/// Returns whether or not the window is in simple fullscreen mode.
|
||||
fn get_simple_fullscreen(&self) -> bool;
|
||||
fn simple_fullscreen(&self) -> bool;
|
||||
|
||||
/// Toggles a fullscreen mode that doesn't require a new macOS space.
|
||||
/// Returns a boolean indicating whether the transition was successful (this
|
||||
@@ -36,25 +57,25 @@ pub trait WindowExt {
|
||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
impl WindowExtMacOS for Window {
|
||||
#[inline]
|
||||
fn get_nswindow(&self) -> *mut c_void {
|
||||
self.window.get_nswindow()
|
||||
fn ns_window(&self) -> *mut c_void {
|
||||
self.window.ns_window()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_nsview(&self) -> *mut c_void {
|
||||
self.window.get_nsview()
|
||||
fn ns_view(&self) -> *mut c_void {
|
||||
self.window.ns_view()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn request_user_attention(&self, is_critical: bool) {
|
||||
self.window.request_user_attention(is_critical)
|
||||
fn request_user_attention(&self, request_type: RequestUserAttentionType) {
|
||||
self.window.request_user_attention(request_type)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_simple_fullscreen(&self) -> bool {
|
||||
self.window.get_simple_fullscreen()
|
||||
fn simple_fullscreen(&self) -> bool {
|
||||
self.window.simple_fullscreen()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -90,11 +111,12 @@ impl Default for ActivationPolicy {
|
||||
/// - `with_titlebar_hidden`
|
||||
/// - `with_titlebar_buttons_hidden`
|
||||
/// - `with_fullsize_content_view`
|
||||
pub trait WindowBuilderExt {
|
||||
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;
|
||||
fn with_movable_by_window_background(self, movable_by_window_background: bool)
|
||||
-> WindowBuilder;
|
||||
/// Makes the titlebar transparent and allows the content to appear behind it.
|
||||
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder;
|
||||
/// Hides the window title.
|
||||
@@ -107,9 +129,10 @@ pub trait WindowBuilderExt {
|
||||
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_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder {
|
||||
self.platform_specific.activation_policy = activation_policy;
|
||||
@@ -117,7 +140,10 @@ impl WindowBuilderExt for WindowBuilder {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> WindowBuilder {
|
||||
fn with_movable_by_window_background(
|
||||
mut self,
|
||||
movable_by_window_background: bool,
|
||||
) -> WindowBuilder {
|
||||
self.platform_specific.movable_by_window_background = movable_by_window_background;
|
||||
self
|
||||
}
|
||||
@@ -157,23 +183,29 @@ impl WindowBuilderExt for WindowBuilder {
|
||||
self.platform_specific.resize_increments = Some(increments.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
|
||||
self.platform_specific.disallow_hidpi = disallow_hidpi;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorId` that are specific to MacOS.
|
||||
pub trait MonitorIdExt {
|
||||
/// Additional methods on `MonitorHandle` that are specific to MacOS.
|
||||
pub trait MonitorHandleExtMacOS {
|
||||
/// Returns the identifier of the monitor for Cocoa.
|
||||
fn native_id(&self) -> u32;
|
||||
/// Returns a pointer to the NSScreen representing this monitor.
|
||||
fn get_nsscreen(&self) -> Option<*mut c_void>;
|
||||
fn ns_screen(&self) -> Option<*mut c_void>;
|
||||
}
|
||||
|
||||
impl MonitorIdExt for MonitorId {
|
||||
impl MonitorHandleExtMacOS for MonitorHandle {
|
||||
#[inline]
|
||||
fn native_id(&self) -> u32 {
|
||||
self.inner.get_native_identifier()
|
||||
self.inner.native_identifier()
|
||||
}
|
||||
|
||||
fn get_nsscreen(&self) -> Option<*mut c_void> {
|
||||
self.inner.get_nsscreen().map(|s| s as *mut c_void)
|
||||
fn ns_screen(&self) -> Option<*mut c_void> {
|
||||
self.inner.ns_screen().map(|s| s as *mut c_void)
|
||||
}
|
||||
}
|
||||
@@ -1,812 +0,0 @@
|
||||
use {ControlFlow, EventsLoopClosed};
|
||||
use cocoa::{self, appkit, foundation};
|
||||
use cocoa::appkit::{NSApplication, NSEvent, NSEventMask, NSEventModifierFlags, NSEventPhase, NSView, NSWindow};
|
||||
use events::{self, ElementState, Event, TouchPhase, WindowEvent, DeviceEvent, ModifiersState, KeyboardInput};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use super::window::Window2;
|
||||
use std;
|
||||
use std::os::raw::*;
|
||||
use super::DeviceId;
|
||||
|
||||
pub struct EventsLoop {
|
||||
modifiers: Modifiers,
|
||||
pub shared: Arc<Shared>,
|
||||
}
|
||||
|
||||
// State shared between the `EventsLoop` and its registered windows.
|
||||
pub struct Shared {
|
||||
pub windows: Mutex<Vec<Weak<Window2>>>,
|
||||
pub pending_events: Mutex<VecDeque<Event>>,
|
||||
// The user event callback given via either of the `poll_events` or `run_forever` methods.
|
||||
//
|
||||
// We store the user's callback here so that it may be accessed by each of the window delegate
|
||||
// callbacks (e.g. resize, close, etc) for the duration of a call to either of the
|
||||
// `poll_events` or `run_forever` methods.
|
||||
//
|
||||
// This is *only* `Some` for the duration of a call to either of these methods and will be
|
||||
// `None` otherwise.
|
||||
user_callback: UserCallback,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Proxy {}
|
||||
|
||||
struct Modifiers {
|
||||
shift_pressed: bool,
|
||||
ctrl_pressed: bool,
|
||||
win_pressed: bool,
|
||||
alt_pressed: bool,
|
||||
}
|
||||
|
||||
// Wrapping the user callback in a type allows us to:
|
||||
//
|
||||
// - ensure the callback pointer is never accidentally cloned
|
||||
// - ensure that only the `EventsLoop` can `store` and `drop` the callback pointer
|
||||
// - Share access to the user callback with the NSWindow callbacks.
|
||||
pub struct UserCallback {
|
||||
mutex: Mutex<Option<*mut FnMut(Event)>>,
|
||||
}
|
||||
|
||||
|
||||
impl Shared {
|
||||
|
||||
pub fn new() -> Self {
|
||||
Shared {
|
||||
windows: Mutex::new(Vec::new()),
|
||||
pending_events: Mutex::new(VecDeque::new()),
|
||||
user_callback: UserCallback { mutex: Mutex::new(None) },
|
||||
}
|
||||
}
|
||||
|
||||
fn call_user_callback_with_pending_events(&self) {
|
||||
loop {
|
||||
let event = match self.pending_events.lock().unwrap().pop_front() {
|
||||
Some(event) => event,
|
||||
None => return,
|
||||
};
|
||||
unsafe {
|
||||
self.user_callback.call_with_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calls the user callback if one exists.
|
||||
//
|
||||
// Otherwise, stores the event in the `pending_events` queue.
|
||||
//
|
||||
// This is necessary for the case when `WindowDelegate` callbacks are triggered during a call
|
||||
// to the user's callback.
|
||||
pub fn call_user_callback_with_event_or_store_in_pending(&self, event: Event) {
|
||||
if self.user_callback.mutex.lock().unwrap().is_some() {
|
||||
unsafe {
|
||||
self.user_callback.call_with_event(event);
|
||||
}
|
||||
} else {
|
||||
self.pending_events.lock().unwrap().push_back(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Removes the window with the given `Id` from the `windows` list.
|
||||
//
|
||||
// This is called in response to `windowWillClose`.
|
||||
pub fn find_and_remove_window(&self, id: super::window::Id) {
|
||||
if let Ok(mut windows) = self.windows.lock() {
|
||||
windows.retain(|w| match w.upgrade() {
|
||||
Some(w) => w.id() != id,
|
||||
None => false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
impl Modifiers {
|
||||
pub fn new() -> Self {
|
||||
Modifiers {
|
||||
shift_pressed: false,
|
||||
ctrl_pressed: false,
|
||||
win_pressed: false,
|
||||
alt_pressed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl UserCallback {
|
||||
|
||||
// Here we store user's `callback` behind the mutex so that they may be safely shared between
|
||||
// each of the window delegates.
|
||||
//
|
||||
// In order to make sure that the pointer is always valid, we must manually guarantee that it
|
||||
// is dropped before the callback itself is dropped. Thus, this should *only* be called at the
|
||||
// beginning of a call to `poll_events` and `run_forever`, both of which *must* drop the
|
||||
// callback at the end of their scope using the `drop` method.
|
||||
fn store<F>(&self, callback: &mut F)
|
||||
where F: FnMut(Event)
|
||||
{
|
||||
let trait_object = callback as &mut FnMut(Event);
|
||||
let trait_object_ptr = trait_object as *const FnMut(Event) as *mut FnMut(Event);
|
||||
*self.mutex.lock().unwrap() = Some(trait_object_ptr);
|
||||
}
|
||||
|
||||
// Emits the given event via the user-given callback.
|
||||
//
|
||||
// This is unsafe as it requires dereferencing the pointer to the user-given callback. We
|
||||
// guarantee this is safe by ensuring the `UserCallback` never lives longer than the user-given
|
||||
// callback.
|
||||
//
|
||||
// Note that the callback may not always be `Some`. This is because some `NSWindowDelegate`
|
||||
// callbacks can be triggered by means other than `NSApp().sendEvent`. For example, if a window
|
||||
// is destroyed or created during a call to the user's callback, the `WindowDelegate` methods
|
||||
// may be called with `windowShouldClose` or `windowDidResignKey`.
|
||||
unsafe fn call_with_event(&self, event: Event) {
|
||||
let callback = match self.mutex.lock().unwrap().take() {
|
||||
Some(callback) => callback,
|
||||
None => return,
|
||||
};
|
||||
(*callback)(event);
|
||||
*self.mutex.lock().unwrap() = Some(callback);
|
||||
}
|
||||
|
||||
// Used to drop the user callback pointer at the end of the `poll_events` and `run_forever`
|
||||
// methods. This is done to enforce our guarantee that the top callback will never live longer
|
||||
// than the call to either `poll_events` or `run_forever` to which it was given.
|
||||
fn drop(&self) {
|
||||
self.mutex.lock().unwrap().take();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
impl EventsLoop {
|
||||
|
||||
pub fn new() -> Self {
|
||||
// Mark this thread as the main thread of the Cocoa event system.
|
||||
//
|
||||
// This must be done before any worker threads get a chance to call it
|
||||
// (e.g., via `EventsLoopProxy::wakeup()`), causing a wrong thread to be
|
||||
// marked as the main thread.
|
||||
unsafe { appkit::NSApp(); }
|
||||
|
||||
EventsLoop {
|
||||
shared: Arc::new(Shared::new()),
|
||||
modifiers: Modifiers::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(Event),
|
||||
{
|
||||
unsafe {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
panic!("Events can only be polled from the main thread on macOS");
|
||||
}
|
||||
}
|
||||
|
||||
self.shared.user_callback.store(&mut callback);
|
||||
|
||||
// Loop as long as we have pending events to return.
|
||||
loop {
|
||||
unsafe {
|
||||
// First, yield all pending events.
|
||||
self.shared.call_user_callback_with_pending_events();
|
||||
|
||||
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
|
||||
|
||||
// Poll for the next event, returning `nil` if there are none.
|
||||
let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_(
|
||||
NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(),
|
||||
foundation::NSDate::distantPast(cocoa::base::nil),
|
||||
foundation::NSDefaultRunLoopMode,
|
||||
cocoa::base::YES);
|
||||
|
||||
let event = self.ns_event_to_event(ns_event);
|
||||
|
||||
let _: () = msg_send![pool, release];
|
||||
|
||||
match event {
|
||||
// Call the user's callback.
|
||||
Some(event) => self.shared.user_callback.call_with_event(event),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.shared.user_callback.drop();
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(Event) -> ControlFlow
|
||||
{
|
||||
unsafe {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
panic!("Events can only be polled from the main thread on macOS");
|
||||
}
|
||||
}
|
||||
|
||||
// Track whether or not control flow has changed.
|
||||
let control_flow = std::cell::Cell::new(ControlFlow::Continue);
|
||||
|
||||
let mut callback = |event| {
|
||||
if let ControlFlow::Break = callback(event) {
|
||||
control_flow.set(ControlFlow::Break);
|
||||
}
|
||||
};
|
||||
|
||||
self.shared.user_callback.store(&mut callback);
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
// First, yield all pending events.
|
||||
self.shared.call_user_callback_with_pending_events();
|
||||
if let ControlFlow::Break = control_flow.get() {
|
||||
break;
|
||||
}
|
||||
|
||||
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
|
||||
|
||||
// Wait for the next event. Note that this function blocks during resize.
|
||||
let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_(
|
||||
NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(),
|
||||
foundation::NSDate::distantFuture(cocoa::base::nil),
|
||||
foundation::NSDefaultRunLoopMode,
|
||||
cocoa::base::YES);
|
||||
|
||||
let maybe_event = self.ns_event_to_event(ns_event);
|
||||
|
||||
// Release the pool before calling the top callback in case the user calls either
|
||||
// `run_forever` or `poll_events` within the callback.
|
||||
let _: () = msg_send![pool, release];
|
||||
|
||||
if let Some(event) = maybe_event {
|
||||
self.shared.user_callback.call_with_event(event);
|
||||
if let ControlFlow::Break = control_flow.get() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.shared.user_callback.drop();
|
||||
}
|
||||
|
||||
// Convert some given `NSEvent` into a winit `Event`.
|
||||
unsafe fn ns_event_to_event(&mut self, ns_event: cocoa::base::id) -> Option<Event> {
|
||||
if ns_event == cocoa::base::nil {
|
||||
return None;
|
||||
}
|
||||
|
||||
// FIXME: Despite not being documented anywhere, an `NSEvent` is produced when a user opens
|
||||
// Spotlight while the NSApplication is in focus. This `NSEvent` produces a `NSEventType`
|
||||
// with value `21`. This causes a SEGFAULT as soon as we try to match on the `NSEventType`
|
||||
// enum as there is no variant associated with the value. Thus, we return early if this
|
||||
// sneaky event occurs. If someone does find some documentation on this, please fix this by
|
||||
// adding an appropriate variant to the `NSEventType` enum in the cocoa-rs crate.
|
||||
if ns_event.eventType() as u64 == 21 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let event_type = ns_event.eventType();
|
||||
let ns_window = ns_event.window();
|
||||
let window_id = super::window::get_window_id(ns_window);
|
||||
|
||||
// FIXME: Document this. Why do we do this? Seems like it passes on events to window/app.
|
||||
// If we don't do this, window does not become main for some reason.
|
||||
appkit::NSApp().sendEvent_(ns_event);
|
||||
|
||||
let windows = self.shared.windows.lock().unwrap();
|
||||
let maybe_window = windows.iter()
|
||||
.filter_map(Weak::upgrade)
|
||||
.find(|window| window_id == window.id());
|
||||
|
||||
let into_event = |window_event| Event::WindowEvent {
|
||||
window_id: ::WindowId(window_id),
|
||||
event: window_event,
|
||||
};
|
||||
|
||||
// Returns `Some` window if one of our windows is the key window.
|
||||
let maybe_key_window = || windows.iter()
|
||||
.filter_map(Weak::upgrade)
|
||||
.find(|window| {
|
||||
let is_key_window: cocoa::base::BOOL = msg_send![*window.window, isKeyWindow];
|
||||
is_key_window == cocoa::base::YES
|
||||
});
|
||||
|
||||
match event_type {
|
||||
// https://github.com/glfw/glfw/blob/50eccd298a2bbc272b4977bd162d3e4b55f15394/src/cocoa_window.m#L881
|
||||
appkit::NSKeyUp => {
|
||||
if let Some(key_window) = maybe_key_window() {
|
||||
if event_mods(ns_event).logo {
|
||||
let _: () = msg_send![*key_window.window, sendEvent:ns_event];
|
||||
}
|
||||
}
|
||||
None
|
||||
},
|
||||
// similar to above, but for `<Cmd-.>`, the keyDown is suppressed instead of the
|
||||
// KeyUp, and the above trick does not appear to work.
|
||||
appkit::NSKeyDown => {
|
||||
let modifiers = event_mods(ns_event);
|
||||
let keycode = NSEvent::keyCode(ns_event);
|
||||
if modifiers.logo && keycode == 47 {
|
||||
modifier_event(ns_event, NSEventModifierFlags::NSCommandKeyMask, false)
|
||||
.map(into_event)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
appkit::NSFlagsChanged => {
|
||||
let mut events = std::collections::VecDeque::new();
|
||||
|
||||
if let Some(window_event) = modifier_event(
|
||||
ns_event,
|
||||
NSEventModifierFlags::NSShiftKeyMask,
|
||||
self.modifiers.shift_pressed,
|
||||
) {
|
||||
self.modifiers.shift_pressed = !self.modifiers.shift_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
if let Some(window_event) = modifier_event(
|
||||
ns_event,
|
||||
NSEventModifierFlags::NSControlKeyMask,
|
||||
self.modifiers.ctrl_pressed,
|
||||
) {
|
||||
self.modifiers.ctrl_pressed = !self.modifiers.ctrl_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
if let Some(window_event) = modifier_event(
|
||||
ns_event,
|
||||
NSEventModifierFlags::NSCommandKeyMask,
|
||||
self.modifiers.win_pressed,
|
||||
) {
|
||||
self.modifiers.win_pressed = !self.modifiers.win_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
if let Some(window_event) = modifier_event(
|
||||
ns_event,
|
||||
NSEventModifierFlags::NSAlternateKeyMask,
|
||||
self.modifiers.alt_pressed,
|
||||
) {
|
||||
self.modifiers.alt_pressed = !self.modifiers.alt_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
let event = events.pop_front();
|
||||
self.shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.extend(events.into_iter());
|
||||
event
|
||||
},
|
||||
|
||||
appkit::NSMouseEntered => {
|
||||
let window = match maybe_window.or_else(maybe_key_window) {
|
||||
Some(window) => window,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let window_point = ns_event.locationInWindow();
|
||||
let view_point = if ns_window == cocoa::base::nil {
|
||||
let ns_size = foundation::NSSize::new(0.0, 0.0);
|
||||
let ns_rect = foundation::NSRect::new(window_point, ns_size);
|
||||
let window_rect = window.window.convertRectFromScreen_(ns_rect);
|
||||
window.view.convertPoint_fromView_(window_rect.origin, cocoa::base::nil)
|
||||
} else {
|
||||
window.view.convertPoint_fromView_(window_point, cocoa::base::nil)
|
||||
};
|
||||
|
||||
let view_rect = NSView::frame(*window.view);
|
||||
let x = view_point.x as f64;
|
||||
let y = (view_rect.size.height - view_point.y) as f64;
|
||||
let window_event = WindowEvent::CursorMoved {
|
||||
device_id: DEVICE_ID,
|
||||
position: (x, y).into(),
|
||||
modifiers: event_mods(ns_event),
|
||||
};
|
||||
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
|
||||
self.shared.pending_events.lock().unwrap().push_back(event);
|
||||
Some(into_event(WindowEvent::CursorEntered { device_id: DEVICE_ID }))
|
||||
},
|
||||
appkit::NSMouseExited => { Some(into_event(WindowEvent::CursorLeft { device_id: DEVICE_ID })) },
|
||||
|
||||
appkit::NSMouseMoved |
|
||||
appkit::NSLeftMouseDragged |
|
||||
appkit::NSOtherMouseDragged |
|
||||
appkit::NSRightMouseDragged => {
|
||||
// If the mouse movement was on one of our windows, use it.
|
||||
// Otherwise, if one of our windows is the key window (receiving input), use it.
|
||||
// Otherwise, return `None`.
|
||||
match maybe_window.or_else(maybe_key_window) {
|
||||
Some(_window) => (),
|
||||
None => return None,
|
||||
}
|
||||
|
||||
let mut events = std::collections::VecDeque::with_capacity(3);
|
||||
|
||||
let delta_x = ns_event.deltaX() as f64;
|
||||
if delta_x != 0.0 {
|
||||
let motion_event = DeviceEvent::Motion { axis: 0, value: delta_x };
|
||||
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
let delta_y = ns_event.deltaY() as f64;
|
||||
if delta_y != 0.0 {
|
||||
let motion_event = DeviceEvent::Motion { axis: 1, value: delta_y };
|
||||
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
if delta_x != 0.0 || delta_y != 0.0 {
|
||||
let motion_event = DeviceEvent::MouseMotion { delta: (delta_x, delta_y) };
|
||||
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
let event = events.pop_front();
|
||||
self.shared.pending_events.lock().unwrap().extend(events.into_iter());
|
||||
event
|
||||
},
|
||||
|
||||
appkit::NSScrollWheel => {
|
||||
// If none of the windows received the scroll, return `None`.
|
||||
if maybe_window.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
use events::MouseScrollDelta::{LineDelta, PixelDelta};
|
||||
let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
|
||||
PixelDelta((
|
||||
ns_event.scrollingDeltaX() as f64,
|
||||
ns_event.scrollingDeltaY() as f64,
|
||||
).into())
|
||||
} else {
|
||||
// TODO: This is probably wrong
|
||||
LineDelta(
|
||||
ns_event.scrollingDeltaX() as f32,
|
||||
ns_event.scrollingDeltaY() as f32,
|
||||
)
|
||||
};
|
||||
let phase = match ns_event.phase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
|
||||
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
|
||||
_ => TouchPhase::Moved,
|
||||
};
|
||||
self.shared.pending_events.lock().unwrap().push_back(Event::DeviceEvent {
|
||||
device_id: DEVICE_ID,
|
||||
event: DeviceEvent::MouseWheel {
|
||||
delta: if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
|
||||
PixelDelta((
|
||||
ns_event.scrollingDeltaX() as f64,
|
||||
ns_event.scrollingDeltaY() as f64,
|
||||
).into())
|
||||
} else {
|
||||
LineDelta(
|
||||
ns_event.scrollingDeltaX() as f32,
|
||||
ns_event.scrollingDeltaY() as f32,
|
||||
)
|
||||
},
|
||||
}
|
||||
});
|
||||
let window_event = WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: delta, phase: phase, modifiers: event_mods(ns_event) };
|
||||
Some(into_event(window_event))
|
||||
},
|
||||
|
||||
appkit::NSEventTypePressure => {
|
||||
let pressure = ns_event.pressure();
|
||||
let stage = ns_event.stage();
|
||||
let window_event = WindowEvent::TouchpadPressure { device_id: DEVICE_ID, pressure: pressure, stage: stage };
|
||||
Some(into_event(window_event))
|
||||
},
|
||||
|
||||
appkit::NSApplicationDefined => match ns_event.subtype() {
|
||||
appkit::NSEventSubtype::NSApplicationActivatedEventType => {
|
||||
Some(Event::Awakened)
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> Proxy {
|
||||
Proxy {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Proxy {
|
||||
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
|
||||
// Awaken the event loop by triggering `NSApplicationActivatedEventType`.
|
||||
unsafe {
|
||||
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
|
||||
let event =
|
||||
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
|
||||
cocoa::base::nil,
|
||||
appkit::NSApplicationDefined,
|
||||
foundation::NSPoint::new(0.0, 0.0),
|
||||
appkit::NSEventModifierFlags::empty(),
|
||||
0.0,
|
||||
0,
|
||||
cocoa::base::nil,
|
||||
appkit::NSEventSubtype::NSApplicationActivatedEventType,
|
||||
0,
|
||||
0);
|
||||
appkit::NSApp().postEvent_atStart_(event, cocoa::base::NO);
|
||||
foundation::NSAutoreleasePool::drain(pool);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn char_to_keycode(c: char) -> Option<events::VirtualKeyCode> {
|
||||
// We only translate keys that are affected by keyboard layout.
|
||||
//
|
||||
// Note that since keys are translated in a somewhat "dumb" way (reading character)
|
||||
// there is a concern that some combination, i.e. Cmd+char, causes the wrong
|
||||
// letter to be received, and so we receive the wrong key.
|
||||
//
|
||||
// Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626
|
||||
Some(match c {
|
||||
'a' | 'A' => events::VirtualKeyCode::A,
|
||||
'b' | 'B' => events::VirtualKeyCode::B,
|
||||
'c' | 'C' => events::VirtualKeyCode::C,
|
||||
'd' | 'D' => events::VirtualKeyCode::D,
|
||||
'e' | 'E' => events::VirtualKeyCode::E,
|
||||
'f' | 'F' => events::VirtualKeyCode::F,
|
||||
'g' | 'G' => events::VirtualKeyCode::G,
|
||||
'h' | 'H' => events::VirtualKeyCode::H,
|
||||
'i' | 'I' => events::VirtualKeyCode::I,
|
||||
'j' | 'J' => events::VirtualKeyCode::J,
|
||||
'k' | 'K' => events::VirtualKeyCode::K,
|
||||
'l' | 'L' => events::VirtualKeyCode::L,
|
||||
'm' | 'M' => events::VirtualKeyCode::M,
|
||||
'n' | 'N' => events::VirtualKeyCode::N,
|
||||
'o' | 'O' => events::VirtualKeyCode::O,
|
||||
'p' | 'P' => events::VirtualKeyCode::P,
|
||||
'q' | 'Q' => events::VirtualKeyCode::Q,
|
||||
'r' | 'R' => events::VirtualKeyCode::R,
|
||||
's' | 'S' => events::VirtualKeyCode::S,
|
||||
't' | 'T' => events::VirtualKeyCode::T,
|
||||
'u' | 'U' => events::VirtualKeyCode::U,
|
||||
'v' | 'V' => events::VirtualKeyCode::V,
|
||||
'w' | 'W' => events::VirtualKeyCode::W,
|
||||
'x' | 'X' => events::VirtualKeyCode::X,
|
||||
'y' | 'Y' => events::VirtualKeyCode::Y,
|
||||
'z' | 'Z' => events::VirtualKeyCode::Z,
|
||||
'1' | '!' => events::VirtualKeyCode::Key1,
|
||||
'2' | '@' => events::VirtualKeyCode::Key2,
|
||||
'3' | '#' => events::VirtualKeyCode::Key3,
|
||||
'4' | '$' => events::VirtualKeyCode::Key4,
|
||||
'5' | '%' => events::VirtualKeyCode::Key5,
|
||||
'6' | '^' => events::VirtualKeyCode::Key6,
|
||||
'7' | '&' => events::VirtualKeyCode::Key7,
|
||||
'8' | '*' => events::VirtualKeyCode::Key8,
|
||||
'9' | '(' => events::VirtualKeyCode::Key9,
|
||||
'0' | ')' => events::VirtualKeyCode::Key0,
|
||||
'=' | '+' => events::VirtualKeyCode::Equals,
|
||||
'-' | '_' => events::VirtualKeyCode::Minus,
|
||||
']' | '}' => events::VirtualKeyCode::RBracket,
|
||||
'[' | '{' => events::VirtualKeyCode::LBracket,
|
||||
'\''| '"' => events::VirtualKeyCode::Apostrophe,
|
||||
';' | ':' => events::VirtualKeyCode::Semicolon,
|
||||
'\\'| '|' => events::VirtualKeyCode::Backslash,
|
||||
',' | '<' => events::VirtualKeyCode::Comma,
|
||||
'/' | '?' => events::VirtualKeyCode::Slash,
|
||||
'.' | '>' => events::VirtualKeyCode::Period,
|
||||
'`' | '~' => events::VirtualKeyCode::Grave,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn scancode_to_keycode(code: c_ushort) -> Option<events::VirtualKeyCode> {
|
||||
Some(match code {
|
||||
0x00 => events::VirtualKeyCode::A,
|
||||
0x01 => events::VirtualKeyCode::S,
|
||||
0x02 => events::VirtualKeyCode::D,
|
||||
0x03 => events::VirtualKeyCode::F,
|
||||
0x04 => events::VirtualKeyCode::H,
|
||||
0x05 => events::VirtualKeyCode::G,
|
||||
0x06 => events::VirtualKeyCode::Z,
|
||||
0x07 => events::VirtualKeyCode::X,
|
||||
0x08 => events::VirtualKeyCode::C,
|
||||
0x09 => events::VirtualKeyCode::V,
|
||||
//0x0a => World 1,
|
||||
0x0b => events::VirtualKeyCode::B,
|
||||
0x0c => events::VirtualKeyCode::Q,
|
||||
0x0d => events::VirtualKeyCode::W,
|
||||
0x0e => events::VirtualKeyCode::E,
|
||||
0x0f => events::VirtualKeyCode::R,
|
||||
0x10 => events::VirtualKeyCode::Y,
|
||||
0x11 => events::VirtualKeyCode::T,
|
||||
0x12 => events::VirtualKeyCode::Key1,
|
||||
0x13 => events::VirtualKeyCode::Key2,
|
||||
0x14 => events::VirtualKeyCode::Key3,
|
||||
0x15 => events::VirtualKeyCode::Key4,
|
||||
0x16 => events::VirtualKeyCode::Key6,
|
||||
0x17 => events::VirtualKeyCode::Key5,
|
||||
0x18 => events::VirtualKeyCode::Equals,
|
||||
0x19 => events::VirtualKeyCode::Key9,
|
||||
0x1a => events::VirtualKeyCode::Key7,
|
||||
0x1b => events::VirtualKeyCode::Minus,
|
||||
0x1c => events::VirtualKeyCode::Key8,
|
||||
0x1d => events::VirtualKeyCode::Key0,
|
||||
0x1e => events::VirtualKeyCode::RBracket,
|
||||
0x1f => events::VirtualKeyCode::O,
|
||||
0x20 => events::VirtualKeyCode::U,
|
||||
0x21 => events::VirtualKeyCode::LBracket,
|
||||
0x22 => events::VirtualKeyCode::I,
|
||||
0x23 => events::VirtualKeyCode::P,
|
||||
0x24 => events::VirtualKeyCode::Return,
|
||||
0x25 => events::VirtualKeyCode::L,
|
||||
0x26 => events::VirtualKeyCode::J,
|
||||
0x27 => events::VirtualKeyCode::Apostrophe,
|
||||
0x28 => events::VirtualKeyCode::K,
|
||||
0x29 => events::VirtualKeyCode::Semicolon,
|
||||
0x2a => events::VirtualKeyCode::Backslash,
|
||||
0x2b => events::VirtualKeyCode::Comma,
|
||||
0x2c => events::VirtualKeyCode::Slash,
|
||||
0x2d => events::VirtualKeyCode::N,
|
||||
0x2e => events::VirtualKeyCode::M,
|
||||
0x2f => events::VirtualKeyCode::Period,
|
||||
0x30 => events::VirtualKeyCode::Tab,
|
||||
0x31 => events::VirtualKeyCode::Space,
|
||||
0x32 => events::VirtualKeyCode::Grave,
|
||||
0x33 => events::VirtualKeyCode::Back,
|
||||
//0x34 => unkown,
|
||||
0x35 => events::VirtualKeyCode::Escape,
|
||||
0x36 => events::VirtualKeyCode::RWin,
|
||||
0x37 => events::VirtualKeyCode::LWin,
|
||||
0x38 => events::VirtualKeyCode::LShift,
|
||||
//0x39 => Caps lock,
|
||||
0x3a => events::VirtualKeyCode::LAlt,
|
||||
0x3b => events::VirtualKeyCode::LControl,
|
||||
0x3c => events::VirtualKeyCode::RShift,
|
||||
0x3d => events::VirtualKeyCode::RAlt,
|
||||
0x3e => events::VirtualKeyCode::RControl,
|
||||
//0x3f => Fn key,
|
||||
0x40 => events::VirtualKeyCode::F17,
|
||||
0x41 => events::VirtualKeyCode::Decimal,
|
||||
//0x42 -> unkown,
|
||||
0x43 => events::VirtualKeyCode::Multiply,
|
||||
//0x44 => unkown,
|
||||
0x45 => events::VirtualKeyCode::Add,
|
||||
//0x46 => unkown,
|
||||
0x47 => events::VirtualKeyCode::Numlock,
|
||||
//0x48 => KeypadClear,
|
||||
0x49 => events::VirtualKeyCode::VolumeUp,
|
||||
0x4a => events::VirtualKeyCode::VolumeDown,
|
||||
0x4b => events::VirtualKeyCode::Divide,
|
||||
0x4c => events::VirtualKeyCode::NumpadEnter,
|
||||
0x4e => events::VirtualKeyCode::Subtract,
|
||||
//0x4d => unkown,
|
||||
0x4e => events::VirtualKeyCode::Subtract,
|
||||
0x4f => events::VirtualKeyCode::F18,
|
||||
0x50 => events::VirtualKeyCode::F19,
|
||||
0x51 => events::VirtualKeyCode::NumpadEquals,
|
||||
0x52 => events::VirtualKeyCode::Numpad0,
|
||||
0x53 => events::VirtualKeyCode::Numpad1,
|
||||
0x54 => events::VirtualKeyCode::Numpad2,
|
||||
0x55 => events::VirtualKeyCode::Numpad3,
|
||||
0x56 => events::VirtualKeyCode::Numpad4,
|
||||
0x57 => events::VirtualKeyCode::Numpad5,
|
||||
0x58 => events::VirtualKeyCode::Numpad6,
|
||||
0x59 => events::VirtualKeyCode::Numpad7,
|
||||
0x5a => events::VirtualKeyCode::F20,
|
||||
0x5b => events::VirtualKeyCode::Numpad8,
|
||||
0x5c => events::VirtualKeyCode::Numpad9,
|
||||
0x5d => events::VirtualKeyCode::Yen,
|
||||
//0x5e => JIS Ro,
|
||||
//0x5f => unkown,
|
||||
0x60 => events::VirtualKeyCode::F5,
|
||||
0x61 => events::VirtualKeyCode::F6,
|
||||
0x62 => events::VirtualKeyCode::F7,
|
||||
0x63 => events::VirtualKeyCode::F3,
|
||||
0x64 => events::VirtualKeyCode::F8,
|
||||
0x65 => events::VirtualKeyCode::F9,
|
||||
//0x66 => JIS Eisuu (macOS),
|
||||
0x67 => events::VirtualKeyCode::F11,
|
||||
//0x68 => JIS Kana (macOS),
|
||||
0x69 => events::VirtualKeyCode::F13,
|
||||
0x6a => events::VirtualKeyCode::F16,
|
||||
0x6b => events::VirtualKeyCode::F14,
|
||||
//0x6c => unkown,
|
||||
0x6d => events::VirtualKeyCode::F10,
|
||||
//0x6e => unkown,
|
||||
0x6f => events::VirtualKeyCode::F12,
|
||||
//0x70 => unkown,
|
||||
0x71 => events::VirtualKeyCode::F15,
|
||||
0x72 => events::VirtualKeyCode::Insert,
|
||||
0x73 => events::VirtualKeyCode::Home,
|
||||
0x74 => events::VirtualKeyCode::PageUp,
|
||||
0x75 => events::VirtualKeyCode::Delete,
|
||||
0x76 => events::VirtualKeyCode::F4,
|
||||
0x77 => events::VirtualKeyCode::End,
|
||||
0x78 => events::VirtualKeyCode::F2,
|
||||
0x79 => events::VirtualKeyCode::PageDown,
|
||||
0x7a => events::VirtualKeyCode::F1,
|
||||
0x7b => events::VirtualKeyCode::Left,
|
||||
0x7c => events::VirtualKeyCode::Right,
|
||||
0x7d => events::VirtualKeyCode::Down,
|
||||
0x7e => events::VirtualKeyCode::Up,
|
||||
//0x7f => unkown,
|
||||
|
||||
0xa => events::VirtualKeyCode::Caret,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn check_function_keys(
|
||||
s: &String
|
||||
) -> Option<events::VirtualKeyCode> {
|
||||
if let Some(ch) = s.encode_utf16().next() {
|
||||
return Some(match ch {
|
||||
0xf718 => events::VirtualKeyCode::F21,
|
||||
0xf719 => events::VirtualKeyCode::F22,
|
||||
0xf71a => events::VirtualKeyCode::F23,
|
||||
0xf71b => events::VirtualKeyCode::F24,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn event_mods(event: cocoa::base::id) -> ModifiersState {
|
||||
let flags = unsafe {
|
||||
NSEvent::modifierFlags(event)
|
||||
};
|
||||
ModifiersState {
|
||||
shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask),
|
||||
ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask),
|
||||
alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
|
||||
logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
|
||||
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character,
|
||||
// and there is no easy way to navtively retrieve the layout-dependent character.
|
||||
// In winit, we use keycode to refer to the key's character, and so this function aligns
|
||||
// AppKit's terminology with ours.
|
||||
unsafe {
|
||||
msg_send![event, keyCode]
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn modifier_event(
|
||||
ns_event: cocoa::base::id,
|
||||
keymask: NSEventModifierFlags,
|
||||
was_key_pressed: bool,
|
||||
) -> Option<WindowEvent> {
|
||||
if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|
||||
|| was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) {
|
||||
let state = if was_key_pressed {
|
||||
ElementState::Released
|
||||
} else {
|
||||
ElementState::Pressed
|
||||
};
|
||||
|
||||
let scancode = get_scancode(ns_event);
|
||||
let virtual_keycode = scancode_to_keycode(scancode);
|
||||
Some(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: scancode as u32,
|
||||
virtual_keycode,
|
||||
modifiers: event_mods(ns_event),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
||||
pub const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
|
||||
@@ -1,107 +0,0 @@
|
||||
// TODO: Upstream these
|
||||
|
||||
#![allow(dead_code, non_snake_case, non_upper_case_globals)]
|
||||
|
||||
use cocoa::base::id;
|
||||
use cocoa::foundation::{NSInteger, NSUInteger};
|
||||
use objc;
|
||||
|
||||
pub const NSNotFound: NSInteger = NSInteger::max_value();
|
||||
|
||||
#[repr(C)]
|
||||
pub struct NSRange {
|
||||
pub location: NSUInteger,
|
||||
pub length: NSUInteger,
|
||||
}
|
||||
|
||||
impl NSRange {
|
||||
#[inline]
|
||||
pub fn new(location: NSUInteger, length: NSUInteger) -> NSRange {
|
||||
NSRange { location, length }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl objc::Encode for NSRange {
|
||||
fn encode() -> objc::Encoding {
|
||||
let encoding = format!(
|
||||
// TODO: Verify that this is correct
|
||||
"{{NSRange={}{}}}",
|
||||
NSUInteger::encode().as_str(),
|
||||
NSUInteger::encode().as_str(),
|
||||
);
|
||||
unsafe { objc::Encoding::from_str(&encoding) }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NSMutableAttributedString: Sized {
|
||||
unsafe fn alloc(_: Self) -> id {
|
||||
msg_send![class!(NSMutableAttributedString), alloc]
|
||||
}
|
||||
|
||||
unsafe fn init(self) -> id; // *mut NSMutableAttributedString
|
||||
unsafe fn initWithString(self, string: id) -> id;
|
||||
unsafe fn initWithAttributedString(self, string: id) -> id;
|
||||
|
||||
unsafe fn string(self) -> id; // *mut NSString
|
||||
unsafe fn mutableString(self) -> id; // *mut NSMutableString
|
||||
unsafe fn length(self) -> NSUInteger;
|
||||
}
|
||||
|
||||
impl NSMutableAttributedString for id {
|
||||
unsafe fn init(self) -> id {
|
||||
msg_send![self, init]
|
||||
}
|
||||
|
||||
unsafe fn initWithString(self, string: id) -> id {
|
||||
msg_send![self, initWithString:string]
|
||||
}
|
||||
|
||||
unsafe fn initWithAttributedString(self, string: id) -> id {
|
||||
msg_send![self, initWithAttributedString:string]
|
||||
}
|
||||
|
||||
unsafe fn string(self) -> id {
|
||||
msg_send![self, string]
|
||||
}
|
||||
|
||||
unsafe fn mutableString(self) -> id {
|
||||
msg_send![self, mutableString]
|
||||
}
|
||||
|
||||
unsafe fn length(self) -> NSUInteger {
|
||||
msg_send![self, length]
|
||||
}
|
||||
}
|
||||
|
||||
pub const kCGBaseWindowLevelKey: NSInteger = 0;
|
||||
pub const kCGMinimumWindowLevelKey: NSInteger = 1;
|
||||
pub const kCGDesktopWindowLevelKey: NSInteger = 2;
|
||||
pub const kCGBackstopMenuLevelKey: NSInteger = 3;
|
||||
pub const kCGNormalWindowLevelKey: NSInteger = 4;
|
||||
pub const kCGFloatingWindowLevelKey: NSInteger = 5;
|
||||
pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6;
|
||||
pub const kCGDockWindowLevelKey: NSInteger = 7;
|
||||
pub const kCGMainMenuWindowLevelKey: NSInteger = 8;
|
||||
pub const kCGStatusWindowLevelKey: NSInteger = 9;
|
||||
pub const kCGModalPanelWindowLevelKey: NSInteger = 10;
|
||||
pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11;
|
||||
pub const kCGDraggingWindowLevelKey: NSInteger = 12;
|
||||
pub const kCGScreenSaverWindowLevelKey: NSInteger = 13;
|
||||
pub const kCGMaximumWindowLevelKey: NSInteger = 14;
|
||||
pub const kCGOverlayWindowLevelKey: NSInteger = 15;
|
||||
pub const kCGHelpWindowLevelKey: NSInteger = 16;
|
||||
pub const kCGUtilityWindowLevelKey: NSInteger = 17;
|
||||
pub const kCGDesktopIconWindowLevelKey: NSInteger = 18;
|
||||
pub const kCGCursorWindowLevelKey: NSInteger = 19;
|
||||
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
|
||||
|
||||
pub enum NSWindowLevel {
|
||||
NSNormalWindowLevel = kCGBaseWindowLevelKey as _,
|
||||
NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _,
|
||||
NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _,
|
||||
NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _,
|
||||
NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _,
|
||||
NSStatusWindowLevel = kCGStatusWindowLevelKey as _,
|
||||
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
|
||||
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#![cfg(target_os = "macos")]
|
||||
|
||||
pub use self::events_loop::{EventsLoop, Proxy as EventsLoopProxy};
|
||||
pub use self::monitor::MonitorId;
|
||||
pub use self::window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, Window2};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
use {CreationError};
|
||||
|
||||
pub struct Window {
|
||||
pub window: Arc<Window2>,
|
||||
}
|
||||
|
||||
impl ::std::ops::Deref for Window {
|
||||
type Target = Window2;
|
||||
#[inline]
|
||||
fn deref(&self) -> &Window2 {
|
||||
&*self.window
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
|
||||
pub fn new(events_loop: &EventsLoop,
|
||||
attributes: ::WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes) -> Result<Self, CreationError>
|
||||
{
|
||||
let weak_shared = Arc::downgrade(&events_loop.shared);
|
||||
let window = Arc::new(try!(Window2::new(weak_shared, attributes, pl_attribs)));
|
||||
let weak_window = Arc::downgrade(&window);
|
||||
events_loop.shared.windows.lock().unwrap().push(weak_window);
|
||||
Ok(Window { window: window })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mod events_loop;
|
||||
mod ffi;
|
||||
mod monitor;
|
||||
mod util;
|
||||
mod view;
|
||||
mod window;
|
||||
@@ -1,147 +0,0 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
|
||||
use cocoa::appkit::NSScreen;
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::foundation::{NSString, NSUInteger};
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
|
||||
|
||||
use {PhysicalPosition, PhysicalSize};
|
||||
use super::EventsLoop;
|
||||
use super::window::{IdRef, Window2};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct MonitorId(CGDirectDisplayID);
|
||||
|
||||
fn get_available_monitors() -> VecDeque<MonitorId> {
|
||||
if let Ok(displays) = CGDisplay::active_displays() {
|
||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||
for d in displays {
|
||||
monitors.push_back(MonitorId(d));
|
||||
}
|
||||
monitors
|
||||
} else {
|
||||
VecDeque::with_capacity(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor() -> MonitorId {
|
||||
let id = MonitorId(CGDisplay::main().id);
|
||||
id
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
get_primary_monitor()
|
||||
}
|
||||
|
||||
pub fn make_monitor_from_display(id: CGDirectDisplayID) -> MonitorId {
|
||||
let id = MonitorId(id);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
impl Window2 {
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
get_primary_monitor()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorId {
|
||||
name: Option<String>,
|
||||
native_identifier: u32,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorId {
|
||||
name: self.get_name(),
|
||||
native_identifier: self.get_native_identifier(),
|
||||
dimensions: self.get_dimensions(),
|
||||
position: self.get_position(),
|
||||
hidpi_factor: self.get_hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
let MonitorId(display_id) = *self;
|
||||
let screen_num = CGDisplay::new(display_id).model_number();
|
||||
Some(format!("Monitor #{}", screen_num))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
let MonitorId(display_id) = *self;
|
||||
let display = CGDisplay::new(display_id);
|
||||
let height = display.pixels_high();
|
||||
let width = display.pixels_wide();
|
||||
PhysicalSize::from_logical(
|
||||
(width as f64, height as f64),
|
||||
self.get_hidpi_factor(),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
let bounds = unsafe { CGDisplayBounds(self.get_native_identifier()) };
|
||||
PhysicalPosition::from_logical(
|
||||
(bounds.origin.x as f64, bounds.origin.y as f64),
|
||||
self.get_hidpi_factor(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
let screen = match self.get_nsscreen() {
|
||||
Some(screen) => screen,
|
||||
None => return 1.0, // default to 1.0 when we can't find the screen
|
||||
};
|
||||
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
|
||||
}
|
||||
|
||||
pub(crate) fn get_nsscreen(&self) -> Option<id> {
|
||||
unsafe {
|
||||
let native_id = self.get_native_identifier();
|
||||
let screens = NSScreen::screens(nil);
|
||||
let count: NSUInteger = msg_send![screens, count];
|
||||
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
||||
let mut matching_screen: Option<id> = None;
|
||||
for i in 0..count {
|
||||
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
|
||||
let device_description = NSScreen::deviceDescription(screen);
|
||||
let value: id = msg_send![device_description, objectForKey:*key];
|
||||
if value != nil {
|
||||
let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue];
|
||||
if screen_number as u32 == native_id {
|
||||
matching_screen = Some(screen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
matching_screen
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
use cocoa::{
|
||||
appkit::NSImage, base::{id, nil, YES},
|
||||
foundation::{NSDictionary, NSPoint, NSString},
|
||||
};
|
||||
use objc::runtime::Sel;
|
||||
|
||||
use super::IntoOption;
|
||||
use MouseCursor;
|
||||
|
||||
pub enum Cursor {
|
||||
Native(&'static str),
|
||||
Undocumented(&'static str),
|
||||
WebKit(&'static str),
|
||||
}
|
||||
|
||||
impl From<MouseCursor> for Cursor {
|
||||
fn from(cursor: MouseCursor) -> Self {
|
||||
match cursor {
|
||||
MouseCursor::Arrow | MouseCursor::Default => Cursor::Native("arrowCursor"),
|
||||
MouseCursor::Hand => Cursor::Native("pointingHandCursor"),
|
||||
MouseCursor::Grabbing | MouseCursor::Grab => Cursor::Native("closedHandCursor"),
|
||||
MouseCursor::Text => Cursor::Native("IBeamCursor"),
|
||||
MouseCursor::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
|
||||
MouseCursor::Copy => Cursor::Native("dragCopyCursor"),
|
||||
MouseCursor::Alias => Cursor::Native("dragLinkCursor"),
|
||||
MouseCursor::NotAllowed | MouseCursor::NoDrop => Cursor::Native("operationNotAllowedCursor"),
|
||||
MouseCursor::ContextMenu => Cursor::Native("contextualMenuCursor"),
|
||||
MouseCursor::Crosshair => Cursor::Native("crosshairCursor"),
|
||||
MouseCursor::EResize => Cursor::Native("resizeRightCursor"),
|
||||
MouseCursor::NResize => Cursor::Native("resizeUpCursor"),
|
||||
MouseCursor::WResize => Cursor::Native("resizeLeftCursor"),
|
||||
MouseCursor::SResize => Cursor::Native("resizeDownCursor"),
|
||||
MouseCursor::EwResize | MouseCursor::ColResize => Cursor::Native("resizeLeftRightCursor"),
|
||||
MouseCursor::NsResize | MouseCursor::RowResize => Cursor::Native("resizeUpDownCursor"),
|
||||
|
||||
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
|
||||
MouseCursor::Help => Cursor::Undocumented("_helpCursor"),
|
||||
MouseCursor::ZoomIn => Cursor::Undocumented("_zoomInCursor"),
|
||||
MouseCursor::ZoomOut => Cursor::Undocumented("_zoomOutCursor"),
|
||||
MouseCursor::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"),
|
||||
MouseCursor::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"),
|
||||
MouseCursor::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"),
|
||||
MouseCursor::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"),
|
||||
MouseCursor::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"),
|
||||
MouseCursor::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"),
|
||||
|
||||
// While these are available, the former just loads a white arrow,
|
||||
// and the latter loads an ugly deflated beachball!
|
||||
// MouseCursor::Move => Cursor::Undocumented("_moveCursor"),
|
||||
// MouseCursor::Wait => Cursor::Undocumented("_waitCursor"),
|
||||
|
||||
// An even more undocumented cursor...
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
|
||||
// This is the wrong semantics for `Wait`, but it's the same as
|
||||
// what's used in Safari and Chrome.
|
||||
MouseCursor::Wait | MouseCursor::Progress => Cursor::Undocumented("busyButClickableCursor"),
|
||||
|
||||
// For the rest, we can just snatch the cursors from WebKit...
|
||||
// They fit the style of the native cursors, and will seem
|
||||
// completely standard to macOS users.
|
||||
// https://stackoverflow.com/a/21786835/5435443
|
||||
MouseCursor::Move | MouseCursor::AllScroll => Cursor::WebKit("move"),
|
||||
MouseCursor::Cell => Cursor::WebKit("cell"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Cursor {
|
||||
fn default() -> Self {
|
||||
Cursor::Native("arrowCursor")
|
||||
}
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub unsafe fn load(&self) -> id {
|
||||
match self {
|
||||
Cursor::Native(cursor_name) => {
|
||||
let sel = Sel::register(cursor_name);
|
||||
msg_send![class!(NSCursor), performSelector:sel]
|
||||
},
|
||||
Cursor::Undocumented(cursor_name) => {
|
||||
let class = class!(NSCursor);
|
||||
let sel = Sel::register(cursor_name);
|
||||
let sel = if msg_send![class, respondsToSelector:sel] {
|
||||
sel
|
||||
} else {
|
||||
warn!("Cursor `{}` appears to be invalid", cursor_name);
|
||||
sel!(arrowCursor)
|
||||
};
|
||||
msg_send![class, performSelector:sel]
|
||||
},
|
||||
Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name)
|
||||
.unwrap_or_else(|message| {
|
||||
warn!("{}", message);
|
||||
Self::default().load()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note that loading `busybutclickable` with this code won't animate the frames;
|
||||
// instead you'll just get them all in a column.
|
||||
unsafe fn load_webkit_cursor(cursor_name_str: &str) -> Result<id, String> {
|
||||
static CURSOR_ROOT: &'static str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors";
|
||||
let cursor_root = NSString::alloc(nil).init_str(CURSOR_ROOT);
|
||||
let cursor_name = NSString::alloc(nil).init_str(cursor_name_str);
|
||||
let cursor_pdf = NSString::alloc(nil).init_str("cursor.pdf");
|
||||
let cursor_plist = NSString::alloc(nil).init_str("info.plist");
|
||||
let key_x = NSString::alloc(nil).init_str("hotx");
|
||||
let key_y = NSString::alloc(nil).init_str("hoty");
|
||||
|
||||
let cursor_path: id = msg_send![cursor_root,
|
||||
stringByAppendingPathComponent:cursor_name
|
||||
];
|
||||
let pdf_path: id = msg_send![cursor_path,
|
||||
stringByAppendingPathComponent:cursor_pdf
|
||||
];
|
||||
let info_path: id = msg_send![cursor_path,
|
||||
stringByAppendingPathComponent:cursor_plist
|
||||
];
|
||||
|
||||
let image = NSImage::alloc(nil)
|
||||
.initByReferencingFile_(pdf_path)
|
||||
// This will probably never be `None`, since images are loaded lazily...
|
||||
.into_option()
|
||||
// because of that, we need to check for validity.
|
||||
.filter(|image| image.isValid() == YES)
|
||||
.ok_or_else(||
|
||||
format!("Failed to read image for `{}` cursor", cursor_name_str)
|
||||
)?;
|
||||
let info = NSDictionary::dictionaryWithContentsOfFile_(nil, info_path)
|
||||
.into_option()
|
||||
.ok_or_else(||
|
||||
format!("Failed to read info for `{}` cursor", cursor_name_str)
|
||||
)?;
|
||||
let x = info.valueForKey_(key_x);
|
||||
let y = info.valueForKey_(key_y);
|
||||
let point = NSPoint::new(
|
||||
msg_send![x, doubleValue],
|
||||
msg_send![y, doubleValue],
|
||||
);
|
||||
let cursor: id = msg_send![class!(NSCursor), alloc];
|
||||
let cursor: id = msg_send![cursor, initWithImage:image hotSpot:point];
|
||||
cursor
|
||||
.into_option()
|
||||
.ok_or_else(||
|
||||
format!("Failed to initialize `{}` cursor", cursor_name_str)
|
||||
)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
use cocoa::base::{id, nil};
|
||||
|
||||
pub trait IntoOption: Sized {
|
||||
fn into_option(self) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl IntoOption for id {
|
||||
fn into_option(self) -> Option<Self> {
|
||||
match self != nil {
|
||||
true => Some(self),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
mod cursor;
|
||||
mod into_option;
|
||||
|
||||
pub use self::{cursor::Cursor, into_option::IntoOption};
|
||||
|
||||
use cocoa::appkit::NSWindowStyleMask;
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::foundation::{NSRect, NSUInteger};
|
||||
use core_graphics::display::CGDisplay;
|
||||
use objc::runtime::{Class, Object};
|
||||
|
||||
use platform::platform::ffi;
|
||||
use platform::platform::window::IdRef;
|
||||
|
||||
pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange {
|
||||
location: ffi::NSNotFound as NSUInteger,
|
||||
length: 0,
|
||||
};
|
||||
|
||||
// For consistency with other platforms, this will...
|
||||
// 1. translate the bottom-left window corner into the top-left window corner
|
||||
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
|
||||
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
|
||||
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
|
||||
}
|
||||
|
||||
pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
|
||||
use cocoa::appkit::NSWindow;
|
||||
window.setStyleMask_(mask);
|
||||
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
|
||||
window.makeFirstResponder_(view);
|
||||
}
|
||||
|
||||
pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) {
|
||||
use cocoa::appkit::NSWindow;
|
||||
|
||||
let current_style_mask = window.styleMask();
|
||||
if on {
|
||||
window.setStyleMask_(current_style_mask | mask);
|
||||
} else {
|
||||
window.setStyleMask_(current_style_mask & (!mask));
|
||||
}
|
||||
|
||||
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
|
||||
window.makeFirstResponder_(view);
|
||||
}
|
||||
|
||||
pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class {
|
||||
let superclass: id = msg_send![this, superclass];
|
||||
&*(superclass as *const _)
|
||||
}
|
||||
|
||||
pub unsafe fn create_input_context(view: id) -> IdRef {
|
||||
let input_context: id = msg_send![class!(NSTextInputContext), alloc];
|
||||
let input_context: id = msg_send![input_context, initWithClient:view];
|
||||
IdRef::new(input_context)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub unsafe fn open_emoji_picker() {
|
||||
let app: id = msg_send![class!(NSApplication), sharedApplication];
|
||||
let _: () = msg_send![app, orderFrontCharacterPalette:nil];
|
||||
}
|
||||
@@ -1,663 +0,0 @@
|
||||
// This is a pretty close port of the implementation in GLFW:
|
||||
// https://github.com/glfw/glfw/blob/7ef34eb06de54dd9186d3d21a401b2ef819b59e7/src/cocoa_window.m
|
||||
|
||||
use std::{slice, str};
|
||||
use std::boxed::Box;
|
||||
use std::collections::VecDeque;
|
||||
use std::os::raw::*;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::appkit::{NSEvent, NSView, NSWindow};
|
||||
use cocoa::foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger};
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{Class, Object, Protocol, Sel, BOOL, YES};
|
||||
|
||||
use {ElementState, Event, KeyboardInput, MouseButton, WindowEvent, WindowId};
|
||||
use platform::platform::events_loop::{DEVICE_ID, event_mods, Shared, scancode_to_keycode, char_to_keycode, check_function_keys, get_scancode};
|
||||
use platform::platform::util;
|
||||
use platform::platform::ffi::*;
|
||||
use platform::platform::window::{get_window_id, IdRef};
|
||||
use events;
|
||||
|
||||
struct ViewState {
|
||||
window: id,
|
||||
shared: Weak<Shared>,
|
||||
cursor: Arc<Mutex<util::Cursor>>,
|
||||
ime_spot: Option<(f64, f64)>,
|
||||
raw_characters: Option<String>,
|
||||
is_key_down: bool,
|
||||
}
|
||||
|
||||
pub fn new_view(window: id, shared: Weak<Shared>) -> (IdRef, Weak<Mutex<util::Cursor>>) {
|
||||
let cursor = Default::default();
|
||||
let cursor_access = Arc::downgrade(&cursor);
|
||||
let state = ViewState {
|
||||
window,
|
||||
shared,
|
||||
cursor,
|
||||
ime_spot: None,
|
||||
raw_characters: None,
|
||||
is_key_down: false,
|
||||
};
|
||||
unsafe {
|
||||
// This is free'd in `dealloc`
|
||||
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
|
||||
let view: id = msg_send![VIEW_CLASS.0, alloc];
|
||||
(IdRef::new(msg_send![view, initWithWinit:state_ptr]), cursor_access)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_spot(view: id, input_context: id, x: f64, y: f64) {
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *(*view).get_mut_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
let content_rect = NSWindow::contentRectForFrameRect_(
|
||||
state.window,
|
||||
NSWindow::frame(state.window),
|
||||
);
|
||||
let base_x = content_rect.origin.x as f64;
|
||||
let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
|
||||
state.ime_spot = Some((base_x + x, base_y - y));
|
||||
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
|
||||
}
|
||||
}
|
||||
|
||||
struct ViewClass(*const Class);
|
||||
unsafe impl Send for ViewClass {}
|
||||
unsafe impl Sync for ViewClass {}
|
||||
|
||||
lazy_static! {
|
||||
static ref VIEW_CLASS: ViewClass = unsafe {
|
||||
let superclass = class!(NSView);
|
||||
let mut decl = ClassDecl::new("WinitView", superclass).unwrap();
|
||||
decl.add_method(sel!(dealloc), dealloc as extern fn(&Object, Sel));
|
||||
decl.add_method(
|
||||
sel!(initWithWinit:),
|
||||
init_with_winit as extern fn(&Object, Sel, *mut c_void) -> id,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(drawRect:),
|
||||
draw_rect as extern fn(&Object, Sel, NSRect),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(resetCursorRects),
|
||||
reset_cursor_rects as extern fn(&Object, Sel),
|
||||
);
|
||||
decl.add_method(sel!(hasMarkedText), has_marked_text as extern fn(&Object, Sel) -> BOOL);
|
||||
decl.add_method(
|
||||
sel!(markedRange),
|
||||
marked_range as extern fn(&Object, Sel) -> NSRange,
|
||||
);
|
||||
decl.add_method(sel!(selectedRange), selected_range as extern fn(&Object, Sel) -> NSRange);
|
||||
decl.add_method(
|
||||
sel!(setMarkedText:selectedRange:replacementRange:),
|
||||
set_marked_text as extern fn(&mut Object, Sel, id, NSRange, NSRange),
|
||||
);
|
||||
decl.add_method(sel!(unmarkText), unmark_text as extern fn(&Object, Sel));
|
||||
decl.add_method(
|
||||
sel!(validAttributesForMarkedText),
|
||||
valid_attributes_for_marked_text as extern fn(&Object, Sel) -> id,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(attributedSubstringForProposedRange:actualRange:),
|
||||
attributed_substring_for_proposed_range
|
||||
as extern fn(&Object, Sel, NSRange, *mut c_void) -> id,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(insertText:replacementRange:),
|
||||
insert_text as extern fn(&Object, Sel, id, NSRange),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(characterIndexForPoint:),
|
||||
character_index_for_point as extern fn(&Object, Sel, NSPoint) -> NSUInteger,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(firstRectForCharacterRange:actualRange:),
|
||||
first_rect_for_character_range
|
||||
as extern fn(&Object, Sel, NSRange, *mut c_void) -> NSRect,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(doCommandBySelector:),
|
||||
do_command_by_selector as extern fn(&Object, Sel, Sel),
|
||||
);
|
||||
decl.add_method(sel!(keyDown:), key_down as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(keyUp:), key_up as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(insertTab:), insert_tab as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(insertBackTab:), insert_back_tab as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(mouseDown:), mouse_down as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(mouseUp:), mouse_up as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(rightMouseDown:), right_mouse_down as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(rightMouseUp:), right_mouse_up as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(otherMouseDown:), other_mouse_down as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(otherMouseUp:), other_mouse_up as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(mouseMoved:), mouse_moved as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(mouseDragged:), mouse_dragged as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(rightMouseDragged:), right_mouse_dragged as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(otherMouseDragged:), other_mouse_dragged as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(_wantsKeyDownForEvent:), wants_key_down_for_event as extern fn(&Object, Sel, id) -> BOOL);
|
||||
decl.add_ivar::<*mut c_void>("winitState");
|
||||
decl.add_ivar::<id>("markedText");
|
||||
let protocol = Protocol::get("NSTextInputClient").unwrap();
|
||||
decl.add_protocol(&protocol);
|
||||
ViewClass(decl.register())
|
||||
};
|
||||
}
|
||||
|
||||
extern fn dealloc(this: &Object, _sel: Sel) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("winitState");
|
||||
let marked_text: id = *this.get_ivar("markedText");
|
||||
let _: () = msg_send![marked_text, release];
|
||||
Box::from_raw(state as *mut ViewState);
|
||||
}
|
||||
}
|
||||
|
||||
extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id {
|
||||
unsafe {
|
||||
let this: id = msg_send![this, init];
|
||||
if this != nil {
|
||||
(*this).set_ivar("winitState", state);
|
||||
let marked_text = <id as NSMutableAttributedString>::init(
|
||||
NSMutableAttributedString::alloc(nil),
|
||||
);
|
||||
(*this).set_ivar("markedText", marked_text);
|
||||
}
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
extern fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::Refresh,
|
||||
};
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
}
|
||||
|
||||
let superclass = util::superclass(this);
|
||||
let () = msg_send![super(this, superclass), drawRect:rect];
|
||||
}
|
||||
}
|
||||
|
||||
extern fn reset_cursor_rects(this: &Object, _sel: Sel) {
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
let bounds: NSRect = msg_send![this, bounds];
|
||||
let cursor = state.cursor.lock().unwrap().load();
|
||||
let _: () = msg_send![this,
|
||||
addCursorRect:bounds
|
||||
cursor:cursor
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
extern fn has_marked_text(this: &Object, _sel: Sel) -> BOOL {
|
||||
//println!("hasMarkedText");
|
||||
unsafe {
|
||||
let marked_text: id = *this.get_ivar("markedText");
|
||||
(marked_text.length() > 0) as i8
|
||||
}
|
||||
}
|
||||
|
||||
extern fn marked_range(this: &Object, _sel: Sel) -> NSRange {
|
||||
//println!("markedRange");
|
||||
unsafe {
|
||||
let marked_text: id = *this.get_ivar("markedText");
|
||||
let length = marked_text.length();
|
||||
if length > 0 {
|
||||
NSRange::new(0, length - 1)
|
||||
} else {
|
||||
util::EMPTY_RANGE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn selected_range(_this: &Object, _sel: Sel) -> NSRange {
|
||||
//println!("selectedRange");
|
||||
util::EMPTY_RANGE
|
||||
}
|
||||
|
||||
extern fn set_marked_text(
|
||||
this: &mut Object,
|
||||
_sel: Sel,
|
||||
string: id,
|
||||
_selected_range: NSRange,
|
||||
_replacement_range: NSRange,
|
||||
) {
|
||||
//println!("setMarkedText");
|
||||
unsafe {
|
||||
let marked_text_ref: &mut id = this.get_mut_ivar("markedText");
|
||||
let _: () = msg_send![(*marked_text_ref), release];
|
||||
let marked_text = NSMutableAttributedString::alloc(nil);
|
||||
let has_attr = msg_send![string, isKindOfClass:class!(NSAttributedString)];
|
||||
if has_attr {
|
||||
marked_text.initWithAttributedString(string);
|
||||
} else {
|
||||
marked_text.initWithString(string);
|
||||
};
|
||||
*marked_text_ref = marked_text;
|
||||
}
|
||||
}
|
||||
|
||||
extern fn unmark_text(this: &Object, _sel: Sel) {
|
||||
//println!("unmarkText");
|
||||
unsafe {
|
||||
let marked_text: id = *this.get_ivar("markedText");
|
||||
let mutable_string = marked_text.mutableString();
|
||||
let _: () = msg_send![mutable_string, setString:""];
|
||||
let input_context: id = msg_send![this, inputContext];
|
||||
let _: () = msg_send![input_context, discardMarkedText];
|
||||
}
|
||||
}
|
||||
|
||||
extern fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id {
|
||||
//println!("validAttributesForMarkedText");
|
||||
unsafe { msg_send![class!(NSArray), array] }
|
||||
}
|
||||
|
||||
extern fn attributed_substring_for_proposed_range(
|
||||
_this: &Object,
|
||||
_sel: Sel,
|
||||
_range: NSRange,
|
||||
_actual_range: *mut c_void, // *mut NSRange
|
||||
) -> id {
|
||||
//println!("attributedSubstringForProposedRange");
|
||||
nil
|
||||
}
|
||||
|
||||
extern fn character_index_for_point(_this: &Object, _sel: Sel, _point: NSPoint) -> NSUInteger {
|
||||
//println!("characterIndexForPoint");
|
||||
0
|
||||
}
|
||||
|
||||
extern fn first_rect_for_character_range(
|
||||
this: &Object,
|
||||
_sel: Sel,
|
||||
_range: NSRange,
|
||||
_actual_range: *mut c_void, // *mut NSRange
|
||||
) -> NSRect {
|
||||
//println!("firstRectForCharacterRange");
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
let (x, y) = state.ime_spot.unwrap_or_else(|| {
|
||||
let content_rect = NSWindow::contentRectForFrameRect_(
|
||||
state.window,
|
||||
NSWindow::frame(state.window),
|
||||
);
|
||||
let x = content_rect.origin.x;
|
||||
let y = util::bottom_left_to_top_left(content_rect);
|
||||
(x, y)
|
||||
});
|
||||
|
||||
NSRect::new(
|
||||
NSPoint::new(x as _, y as _),
|
||||
NSSize::new(0.0, 0.0),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: NSRange) {
|
||||
//println!("insertText");
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
let has_attr = msg_send![string, isKindOfClass:class!(NSAttributedString)];
|
||||
let characters = if has_attr {
|
||||
// This is a *mut NSAttributedString
|
||||
msg_send![string, string]
|
||||
} else {
|
||||
// This is already a *mut NSString
|
||||
string
|
||||
};
|
||||
|
||||
let slice = slice::from_raw_parts(
|
||||
characters.UTF8String() as *const c_uchar,
|
||||
characters.len(),
|
||||
);
|
||||
let string = str::from_utf8_unchecked(slice);
|
||||
state.is_key_down = true;
|
||||
|
||||
// We don't need this now, but it's here if that changes.
|
||||
//let event: id = msg_send![class!(NSApp), currentEvent];
|
||||
|
||||
let mut events = VecDeque::with_capacity(characters.len());
|
||||
for character in string.chars() {
|
||||
events.push_back(Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::ReceivedCharacter(character),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.append(&mut events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
|
||||
//println!("doCommandBySelector");
|
||||
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human readable" character
|
||||
// happens, i.e. newlines, tabs, and Ctrl+C.
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
let shared = if let Some(shared) = state.shared.upgrade() {
|
||||
shared
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut events = VecDeque::with_capacity(1);
|
||||
if command == sel!(insertNewline:) {
|
||||
// The `else` condition would emit the same character, but I'm keeping this here both...
|
||||
// 1) as a reminder for how `doCommandBySelector` works
|
||||
// 2) to make our use of carriage return explicit
|
||||
events.push_back(Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::ReceivedCharacter('\r'),
|
||||
});
|
||||
} else {
|
||||
let raw_characters = state.raw_characters.take();
|
||||
if let Some(raw_characters) = raw_characters {
|
||||
for character in raw_characters.chars() {
|
||||
events.push_back(Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::ReceivedCharacter(character),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.append(&mut events);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_characters(event: id, ignore_modifiers: bool) -> String {
|
||||
unsafe {
|
||||
let characters: id = if ignore_modifiers {
|
||||
msg_send![event, charactersIgnoringModifiers]
|
||||
} else {
|
||||
msg_send![event, characters]
|
||||
};
|
||||
|
||||
assert_ne!(characters, nil);
|
||||
let slice = slice::from_raw_parts(
|
||||
characters.UTF8String() as *const c_uchar,
|
||||
characters.len(),
|
||||
);
|
||||
|
||||
let string = str::from_utf8_unchecked(slice);
|
||||
|
||||
string.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieves a layout-independent keycode given an event.
|
||||
fn retrieve_keycode(event: id) -> Option<events::VirtualKeyCode> {
|
||||
#[inline]
|
||||
fn get_code(ev: id, raw: bool) -> Option<events::VirtualKeyCode> {
|
||||
let characters = get_characters(ev, raw);
|
||||
characters.chars().next().map_or(None, |c| char_to_keycode(c))
|
||||
}
|
||||
|
||||
// Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first.
|
||||
// If we don't get a match, then we fall back to unmodified characters.
|
||||
let code = get_code(event, false)
|
||||
.or_else(|| {
|
||||
get_code(event, true)
|
||||
});
|
||||
|
||||
// We've checked all layout related keys, so fall through to scancode.
|
||||
// Reaching this code means that the key is layout-independent (e.g. Backspace, Return).
|
||||
//
|
||||
// We're additionally checking here for F21-F24 keys, since their keycode
|
||||
// can vary, but we know that they are encoded
|
||||
// in characters property.
|
||||
code.or_else(|| {
|
||||
let scancode = get_scancode(event);
|
||||
scancode_to_keycode(scancode)
|
||||
.or_else(|| {
|
||||
check_function_keys(&get_characters(event, true))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
extern fn key_down(this: &Object, _sel: Sel, event: id) {
|
||||
//println!("keyDown");
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
let window_id = WindowId(get_window_id(state.window));
|
||||
let characters = get_characters(event, false);
|
||||
|
||||
state.raw_characters = Some(characters.clone());
|
||||
|
||||
let scancode = get_scancode(event) as u32;
|
||||
let virtual_keycode = retrieve_keycode(event);
|
||||
let is_repeat = msg_send![event, isARepeat];
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
scancode,
|
||||
virtual_keycode,
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
// Emit `ReceivedCharacter` for key repeats
|
||||
if is_repeat && state.is_key_down{
|
||||
for character in characters.chars() {
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ReceivedCharacter(character),
|
||||
};
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
}
|
||||
} else {
|
||||
// Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do...
|
||||
// So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some
|
||||
// keys to generate twice as many characters.
|
||||
let array: id = msg_send![class!(NSArray), arrayWithObject:event];
|
||||
let (): _ = msg_send![this, interpretKeyEvents:array];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn key_up(this: &Object, _sel: Sel, event: id) {
|
||||
//println!("keyUp");
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
state.is_key_down = false;
|
||||
|
||||
let scancode = get_scancode(event) as u32;
|
||||
let virtual_keycode = retrieve_keycode(event);
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
scancode,
|
||||
virtual_keycode,
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn insert_tab(this: &Object, _sel: Sel, _sender: id) {
|
||||
unsafe {
|
||||
let window: id = msg_send![this, window];
|
||||
let first_responder: id = msg_send![window, firstResponder];
|
||||
let this_ptr = this as *const _ as *mut _;
|
||||
if first_responder == this_ptr {
|
||||
let (): _ = msg_send![window, selectNextKeyView:this];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) {
|
||||
unsafe {
|
||||
let window: id = msg_send![this, window];
|
||||
let first_responder: id = msg_send![window, firstResponder];
|
||||
let this_ptr = this as *const _ as *mut _;
|
||||
if first_responder == this_ptr {
|
||||
let (): _ = msg_send![window, selectPreviousKeyView:this];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: ElementState) {
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::MouseInput {
|
||||
device_id: DEVICE_ID,
|
||||
state: button_state,
|
||||
button,
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn mouse_down(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Left, ElementState::Pressed);
|
||||
}
|
||||
|
||||
extern fn mouse_up(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Left, ElementState::Released);
|
||||
}
|
||||
|
||||
extern fn right_mouse_down(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Right, ElementState::Pressed);
|
||||
}
|
||||
|
||||
extern fn right_mouse_up(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Right, ElementState::Released);
|
||||
}
|
||||
|
||||
extern fn other_mouse_down(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Middle, ElementState::Pressed);
|
||||
}
|
||||
|
||||
extern fn other_mouse_up(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Middle, ElementState::Released);
|
||||
}
|
||||
|
||||
fn mouse_motion(this: &Object, event: id) {
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
// We have to do this to have access to the `NSView` trait...
|
||||
let view: id = this as *const _ as *mut _;
|
||||
|
||||
let window_point = event.locationInWindow();
|
||||
let view_point = view.convertPoint_fromView_(window_point, nil);
|
||||
let view_rect = NSView::frame(view);
|
||||
|
||||
if view_point.x.is_sign_negative()
|
||||
|| view_point.y.is_sign_negative()
|
||||
|| view_point.x > view_rect.size.width
|
||||
|| view_point.y > view_rect.size.height {
|
||||
// Point is outside of the client area (view)
|
||||
return;
|
||||
}
|
||||
|
||||
let x = view_point.x as f64;
|
||||
let y = view_rect.size.height as f64 - view_point.y as f64;
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::CursorMoved {
|
||||
device_id: DEVICE_ID,
|
||||
position: (x, y).into(),
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn mouse_moved(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
}
|
||||
|
||||
extern fn mouse_dragged(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
}
|
||||
|
||||
extern fn right_mouse_dragged(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
}
|
||||
|
||||
extern fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
}
|
||||
|
||||
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
|
||||
extern fn wants_key_down_for_event(_this: &Object, _se: Sel, _event: id) -> BOOL {
|
||||
YES
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,25 @@
|
||||
pub use self::platform::*;
|
||||
//! Contains traits with platform-specific methods in them.
|
||||
//!
|
||||
//! Contains the follow OS-specific modules:
|
||||
//!
|
||||
//! - `android`
|
||||
//! - `ios`
|
||||
//! - `macos`
|
||||
//! - `unix`
|
||||
//! - `windows`
|
||||
//! - `web`
|
||||
//!
|
||||
//! And the following platform-specific module:
|
||||
//!
|
||||
//! - `desktop` (available on `windows`, `unix`, and `macos`)
|
||||
//!
|
||||
//! However only the module corresponding to the platform you're compiling to will be available.
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[path="windows/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
#[path="linux/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(target_os = "macos")]
|
||||
#[path="macos/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(target_os = "android")]
|
||||
#[path="android/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(target_os = "ios")]
|
||||
#[path="ios/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(target_os = "emscripten")]
|
||||
#[path="emscripten/mod.rs"]
|
||||
mod platform;
|
||||
pub mod android;
|
||||
pub mod ios;
|
||||
pub mod macos;
|
||||
pub mod unix;
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(all(not(target_os = "ios"), not(target_os = "windows"), not(target_os = "linux"),
|
||||
not(target_os = "macos"), not(target_os = "android"), not(target_os = "dragonfly"),
|
||||
not(target_os = "freebsd"), not(target_os = "netbsd"), not(target_os = "openbsd"),
|
||||
not(target_os = "emscripten")))]
|
||||
compile_error!("The platform you're compiling for is not supported by winit");
|
||||
pub mod desktop;
|
||||
pub mod web;
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
|
||||
use std::os::raw;
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
use std::{os::raw, ptr, sync::Arc};
|
||||
|
||||
use sctk::window::{ButtonState, Theme};
|
||||
use smithay_client_toolkit::window::{ButtonState, Theme};
|
||||
|
||||
use {
|
||||
EventsLoop,
|
||||
LogicalSize,
|
||||
MonitorId,
|
||||
Window,
|
||||
WindowBuilder,
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
use platform::{
|
||||
EventsLoop as LinuxEventsLoop,
|
||||
|
||||
use crate::platform_impl::{
|
||||
x11::{ffi::XVisualInfo, XConnection},
|
||||
EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
|
||||
Window as LinuxWindow,
|
||||
};
|
||||
use platform::x11::XConnection;
|
||||
use platform::x11::ffi::XVisualInfo;
|
||||
|
||||
// TODO: stupid hack so that glutin can do its work
|
||||
#[doc(hidden)]
|
||||
pub use platform::x11;
|
||||
pub use crate::platform_impl::x11;
|
||||
|
||||
pub use platform::XNotSupported;
|
||||
pub use platform::x11::util::WindowType as XWindowType;
|
||||
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
|
||||
|
||||
/// Theme for wayland client side decorations
|
||||
///
|
||||
@@ -95,98 +91,164 @@ impl Theme for WaylandThemeObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventsLoop` that are specific to Linux.
|
||||
pub trait EventsLoopExt {
|
||||
/// Builds a new `EventsLoop` that is forced to use X11.
|
||||
fn new_x11() -> Result<Self, XNotSupported>
|
||||
where Self: Sized;
|
||||
|
||||
/// Builds a new `EventsLoop` that is forced to use Wayland.
|
||||
fn new_wayland() -> Self
|
||||
where Self: Sized;
|
||||
|
||||
/// True if the `EventsLoop` uses Wayland.
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
|
||||
pub trait EventLoopWindowTargetExtUnix {
|
||||
/// True if the `EventLoopWindowTarget` uses Wayland.
|
||||
fn is_wayland(&self) -> bool;
|
||||
|
||||
/// True if the `EventsLoop` uses X11.
|
||||
///
|
||||
/// True if the `EventLoopWindowTarget` uses X11.
|
||||
fn is_x11(&self) -> bool;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
|
||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this `EventsLoop`.
|
||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this
|
||||
/// `EventLoopWindowTarget`.
|
||||
///
|
||||
/// Returns `None` if the `EventsLoop` 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 `EventsLoop` is destroyed.
|
||||
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
/// The pointer will become invalid when the winit `EventLoop` is destroyed.
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
}
|
||||
|
||||
impl EventsLoopExt for EventsLoop {
|
||||
#[inline]
|
||||
fn new_x11() -> Result<Self, XNotSupported> {
|
||||
LinuxEventsLoop::new_x11().map(|ev|
|
||||
EventsLoop {
|
||||
events_loop: ev,
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_wayland() -> Self {
|
||||
EventsLoop {
|
||||
events_loop: match LinuxEventsLoop::new_wayland() {
|
||||
Ok(e) => e,
|
||||
Err(_) => panic!() // TODO: propagate
|
||||
},
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
|
||||
#[inline]
|
||||
fn is_wayland(&self) -> bool {
|
||||
self.events_loop.is_wayland()
|
||||
self.p.is_wayland()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_x11(&self) -> bool {
|
||||
!self.events_loop.is_wayland()
|
||||
!self.p.is_wayland()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
self.events_loop.x_connection().cloned()
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
match self.p {
|
||||
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.events_loop {
|
||||
LinuxEventsLoop::Wayland(ref e) => Some(e.get_display().c_ptr() as *mut _),
|
||||
_ => None
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.p {
|
||||
LinuxEventLoopWindowTarget::Wayland(ref p) => {
|
||||
Some(p.display().get_display_ptr() as *mut _)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Unix.
|
||||
pub trait EventLoopExtUnix {
|
||||
/// Builds a new `EventLoop` that is forced to use X11.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If called outside the main thread. To initialize an X11 event loop outside
|
||||
/// the main thread, use [`new_x11_any_thread`](#tymethod.new_x11_any_thread).
|
||||
fn new_x11() -> Result<Self, XNotSupported>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Builds a new `EventLoop` that is forced to use Wayland.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If called outside the main thread. To initialize a Wayland event loop outside
|
||||
/// the main thread, use [`new_wayland_any_thread`](#tymethod.new_wayland_any_thread).
|
||||
fn new_wayland() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Builds a new `EventLoop` on any thread.
|
||||
///
|
||||
/// This method bypasses the cross-platform compatibility requirement
|
||||
/// that `EventLoop` be created on the main thread.
|
||||
fn new_any_thread() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Builds a new X11 `EventLoop` on any thread.
|
||||
///
|
||||
/// This method bypasses the cross-platform compatibility requirement
|
||||
/// that `EventLoop` be created on the main thread.
|
||||
fn new_x11_any_thread() -> Result<Self, XNotSupported>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Builds a new Wayland `EventLoop` on any thread.
|
||||
///
|
||||
/// This method bypasses the cross-platform compatibility requirement
|
||||
/// that `EventLoop` be created on the main thread.
|
||||
fn new_wayland_any_thread() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
fn wrap_ev<T>(event_loop: LinuxEventLoop<T>) -> EventLoop<T> {
|
||||
EventLoop {
|
||||
event_loop,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtUnix for EventLoop<T> {
|
||||
#[inline]
|
||||
fn new_any_thread() -> Self {
|
||||
wrap_ev(LinuxEventLoop::new_any_thread())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_x11_any_thread() -> Result<Self, XNotSupported> {
|
||||
LinuxEventLoop::new_x11_any_thread().map(wrap_ev)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_wayland_any_thread() -> Self {
|
||||
wrap_ev(
|
||||
LinuxEventLoop::new_wayland_any_thread()
|
||||
// TODO: propagate
|
||||
.expect("failed to open Wayland connection"),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_x11() -> Result<Self, XNotSupported> {
|
||||
LinuxEventLoop::new_x11().map(wrap_ev)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_wayland() -> Self {
|
||||
wrap_ev(
|
||||
LinuxEventLoop::new_wayland()
|
||||
// TODO: propagate
|
||||
.expect("failed to open Wayland connection"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Unix.
|
||||
pub trait WindowExt {
|
||||
pub trait WindowExtUnix {
|
||||
/// Returns the ID of the `Window` xlib object that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
fn get_xlib_window(&self) -> Option<raw::c_ulong>;
|
||||
fn xlib_window(&self) -> Option<raw::c_ulong>;
|
||||
|
||||
/// Returns a pointer to the `Display` object of xlib that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn get_xlib_display(&self) -> Option<*mut raw::c_void>;
|
||||
fn xlib_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
fn get_xlib_screen_id(&self) -> Option<raw::c_int>;
|
||||
fn xlib_screen_id(&self) -> Option<raw::c_int>;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
|
||||
/// Set window urgency hint (`XUrgencyHint`). Only relevant on X.
|
||||
fn set_urgent(&self, is_urgent: bool);
|
||||
@@ -196,21 +258,21 @@ pub trait WindowExt {
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn get_xcb_connection(&self) -> Option<*mut raw::c_void>;
|
||||
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn get_wayland_surface(&self) -> Option<*mut raw::c_void>;
|
||||
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
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);
|
||||
@@ -225,45 +287,37 @@ pub trait WindowExt {
|
||||
fn is_ready(&self) -> bool;
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
impl WindowExtUnix for Window {
|
||||
#[inline]
|
||||
fn get_xlib_window(&self) -> Option<raw::c_ulong> {
|
||||
fn xlib_window(&self) -> Option<raw::c_ulong> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xlib_window()),
|
||||
_ => None
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_window()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_xlib_display(&self) -> Option<*mut raw::c_void> {
|
||||
fn xlib_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xlib_display()),
|
||||
_ => None
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_display()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_xlib_screen_id(&self) -> Option<raw::c_int> {
|
||||
fn xlib_screen_id(&self) -> Option<raw::c_int> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xlib_screen_id()),
|
||||
_ => None
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xlib_xconnection()),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_xcb_connection(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xcb_connection()),
|
||||
_ => None
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_xconnection()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,18 +329,26 @@ impl WindowExt for Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_wayland_surface(&self) -> Option<*mut raw::c_void> {
|
||||
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.get_surface().c_ptr() as *mut _),
|
||||
_ => None
|
||||
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.get_display().c_ptr() as *mut _),
|
||||
_ => None
|
||||
LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,98 +367,97 @@ impl WindowExt for Window {
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Unix.
|
||||
pub trait WindowBuilderExt {
|
||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> WindowBuilder;
|
||||
fn with_x11_screen(self, screen_id: i32) -> WindowBuilder;
|
||||
pub trait WindowBuilderExtUnix {
|
||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
|
||||
fn with_x11_screen(self, screen_id: i32) -> Self;
|
||||
|
||||
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
|
||||
fn with_class(self, class: String, instance: String) -> WindowBuilder;
|
||||
fn with_class(self, class: String, instance: String) -> Self;
|
||||
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
|
||||
fn with_override_redirect(self, override_redirect: bool) -> 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;
|
||||
fn with_override_redirect(self, override_redirect: bool) -> Self;
|
||||
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
|
||||
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
|
||||
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
|
||||
fn with_gtk_theme_variant(self, variant: String) -> WindowBuilder;
|
||||
fn with_gtk_theme_variant(self, variant: String) -> Self;
|
||||
/// Build window with resize increment hint. Only implemented on X11.
|
||||
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
|
||||
fn with_resize_increments(self, increments: LogicalSize) -> Self;
|
||||
/// Build window with base size hint. Only implemented on X11.
|
||||
fn with_base_size(self, base_size: LogicalSize) -> WindowBuilder;
|
||||
fn with_base_size(self, base_size: LogicalSize) -> Self;
|
||||
|
||||
/// Build window with a given application ID. It should match the `.desktop` file distributed with
|
||||
/// your program. Only relevant on Wayland.
|
||||
///
|
||||
/// For details about application ID conventions, see the
|
||||
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
|
||||
fn with_app_id(self, app_id: String) -> WindowBuilder;
|
||||
fn with_app_id(self, app_id: String) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
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) }
|
||||
);
|
||||
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 {
|
||||
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 {
|
||||
fn with_class(mut self, instance: String, class: String) -> Self {
|
||||
self.platform_specific.class = Some((instance, class));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_override_redirect(mut self, override_redirect: bool) -> WindowBuilder {
|
||||
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;
|
||||
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 {
|
||||
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 {
|
||||
fn with_resize_increments(mut self, increments: LogicalSize) -> Self {
|
||||
self.platform_specific.resize_increments = Some(increments.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_base_size(mut self, base_size: LogicalSize) -> Self {
|
||||
self.platform_specific.base_size = Some(base_size.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_app_id(mut self, app_id: String) -> Self {
|
||||
self.platform_specific.app_id = Some(app_id);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorId` that are specific to Linux.
|
||||
pub trait MonitorIdExt {
|
||||
/// Additional methods on `MonitorHandle` that are specific to Linux.
|
||||
pub trait MonitorHandleExtUnix {
|
||||
/// Returns the inner identifier of the monitor.
|
||||
fn native_id(&self) -> u32;
|
||||
}
|
||||
|
||||
impl MonitorIdExt for MonitorId {
|
||||
impl MonitorHandleExtUnix for MonitorHandle {
|
||||
#[inline]
|
||||
fn native_id(&self) -> u32 {
|
||||
self.inner.get_native_identifier()
|
||||
self.inner.native_identifier()
|
||||
}
|
||||
}
|
||||
22
src/platform/web.rs
Normal file
22
src/platform/web.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
//! The web target does not automatically insert the canvas element object into the web page, to
|
||||
//! allow end users to determine how the page should be laid out. Use the `WindowExtStdweb` or
|
||||
//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the
|
||||
//! Window.
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
use stdweb::web::html_element::CanvasElement;
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
pub trait WindowExtStdweb {
|
||||
fn canvas(&self) -> CanvasElement;
|
||||
}
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
pub trait WindowExtWebSys {
|
||||
fn canvas(&self) -> HtmlCanvasElement;
|
||||
}
|
||||
165
src/platform/windows.rs
Normal file
165
src/platform/windows.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use libc;
|
||||
use winapi::shared::windef::HWND;
|
||||
|
||||
use crate::{
|
||||
event::DeviceId,
|
||||
event_loop::EventLoop,
|
||||
monitor::MonitorHandle,
|
||||
platform_impl::EventLoop as WindowsEventLoop,
|
||||
window::{Icon, Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Windows.
|
||||
pub trait EventLoopExtWindows {
|
||||
/// Creates an event loop off of the main thread.
|
||||
///
|
||||
/// # `Window` caveats
|
||||
///
|
||||
/// Note that any `Window` created on the new thread will be destroyed when the thread
|
||||
/// terminates. Attempting to use a `Window` after its parent thread terminates has
|
||||
/// unspecified, although explicitly not undefined, behavior.
|
||||
fn new_any_thread() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's
|
||||
/// undesirable, you can create an `EventLoop` using this function instead.
|
||||
fn new_dpi_unaware() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Creates a DPI-unaware event loop off of the main thread.
|
||||
///
|
||||
/// The `Window` caveats in [`new_any_thread`](EventLoopExtWindows::new_any_thread) also apply here.
|
||||
fn new_dpi_unaware_any_thread() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtWindows for EventLoop<T> {
|
||||
#[inline]
|
||||
fn new_any_thread() -> Self {
|
||||
EventLoop {
|
||||
event_loop: WindowsEventLoop::new_any_thread(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_dpi_unaware() -> Self {
|
||||
EventLoop {
|
||||
event_loop: WindowsEventLoop::new_dpi_unaware(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_dpi_unaware_any_thread() -> Self {
|
||||
EventLoop {
|
||||
event_loop: WindowsEventLoop::new_dpi_unaware_any_thread(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Windows.
|
||||
pub trait WindowExtWindows {
|
||||
/// Returns the HINSTANCE of the window
|
||||
fn hinstance(&self) -> *mut libc::c_void;
|
||||
/// Returns the native handle that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the native window was destroyed.
|
||||
fn hwnd(&self) -> *mut libc::c_void;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
|
||||
}
|
||||
|
||||
impl WindowExtWindows for Window {
|
||||
#[inline]
|
||||
fn hinstance(&self) -> *mut libc::c_void {
|
||||
self.window.hinstance() as *mut _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hwnd(&self) -> *mut libc::c_void {
|
||||
self.window.hwnd() as *mut _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
|
||||
self.window.set_taskbar_icon(taskbar_icon)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Windows.
|
||||
pub trait WindowBuilderExtWindows {
|
||||
/// Sets a parent to the window to be created.
|
||||
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
|
||||
|
||||
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
|
||||
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtWindows for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
|
||||
self.platform_specific.parent = Some(parent);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> WindowBuilder {
|
||||
self.platform_specific.taskbar_icon = taskbar_icon;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder {
|
||||
self.platform_specific.no_redirection_bitmap = flag;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorHandle` that are specific to Windows.
|
||||
pub trait MonitorHandleExtWindows {
|
||||
/// Returns the name of the monitor adapter specific to the Win32 API.
|
||||
fn native_id(&self) -> String;
|
||||
|
||||
/// Returns the handle of the monitor - `HMONITOR`.
|
||||
fn hmonitor(&self) -> *mut c_void;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtWindows for MonitorHandle {
|
||||
#[inline]
|
||||
fn native_id(&self) -> String {
|
||||
self.inner.native_identifier()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hmonitor(&self) -> *mut c_void {
|
||||
self.inner.hmonitor() as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `DeviceId` that are specific to Windows.
|
||||
pub trait DeviceIdExtWindows {
|
||||
/// Returns an identifier that persistently refers to this specific device.
|
||||
///
|
||||
/// Will return `None` if the device is no longer available.
|
||||
fn persistent_identifier(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
impl DeviceIdExtWindows for DeviceId {
|
||||
#[inline]
|
||||
fn persistent_identifier(&self) -> Option<String> {
|
||||
self.0.persistent_identifier()
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
#![allow(non_snake_case, unused_unsafe)]
|
||||
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::{Once, ONCE_INIT};
|
||||
|
||||
use winapi::shared::minwindef::{BOOL, UINT, FALSE};
|
||||
use winapi::shared::windef::{
|
||||
DPI_AWARENESS_CONTEXT,
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE,
|
||||
HMONITOR,
|
||||
HWND,
|
||||
};
|
||||
use winapi::shared::winerror::S_OK;
|
||||
use winapi::um::libloaderapi::{GetProcAddress, LoadLibraryA};
|
||||
use winapi::um::shellscalingapi::{
|
||||
MDT_EFFECTIVE_DPI,
|
||||
MONITOR_DPI_TYPE,
|
||||
PROCESS_DPI_AWARENESS,
|
||||
PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
};
|
||||
use winapi::um::wingdi::{GetDeviceCaps, LOGPIXELSX};
|
||||
use winapi::um::winnt::{HRESULT, LPCSTR};
|
||||
use winapi::um::winuser::{self, MONITOR_DEFAULTTONEAREST};
|
||||
|
||||
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4isize as _;
|
||||
|
||||
type SetProcessDPIAware = unsafe extern "system" fn () -> BOOL;
|
||||
type SetProcessDpiAwareness = unsafe extern "system" fn (
|
||||
value: PROCESS_DPI_AWARENESS,
|
||||
) -> HRESULT;
|
||||
type SetProcessDpiAwarenessContext = unsafe extern "system" fn (
|
||||
value: DPI_AWARENESS_CONTEXT,
|
||||
) -> BOOL;
|
||||
type GetDpiForWindow = unsafe extern "system" fn (hwnd: HWND) -> UINT;
|
||||
type GetDpiForMonitor = unsafe extern "system" fn (
|
||||
hmonitor: HMONITOR,
|
||||
dpi_type: MONITOR_DPI_TYPE,
|
||||
dpi_x: *mut UINT,
|
||||
dpi_y: *mut UINT,
|
||||
) -> HRESULT;
|
||||
type EnableNonClientDpiScaling = unsafe extern "system" fn (hwnd: HWND) -> BOOL;
|
||||
|
||||
// Helper function to dynamically load function pointer.
|
||||
// `library` and `function` must be zero-terminated.
|
||||
fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
|
||||
assert_eq!(library.chars().last(), Some('\0'));
|
||||
assert_eq!(function.chars().last(), Some('\0'));
|
||||
|
||||
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
|
||||
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
|
||||
if module.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
|
||||
if function_ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(function_ptr as _)
|
||||
}
|
||||
|
||||
macro_rules! get_function {
|
||||
($lib:expr, $func:ident) => {
|
||||
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0'))
|
||||
.map(|f| unsafe { mem::transmute::<*const _, $func>(f) })
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> = get_function!(
|
||||
"user32.dll",
|
||||
GetDpiForWindow
|
||||
);
|
||||
static ref GET_DPI_FOR_MONITOR: Option<GetDpiForMonitor> = get_function!(
|
||||
"shcore.dll",
|
||||
GetDpiForMonitor
|
||||
);
|
||||
static ref ENABLE_NON_CLIENT_DPI_SCALING: Option<EnableNonClientDpiScaling> = get_function!(
|
||||
"user32.dll",
|
||||
EnableNonClientDpiScaling
|
||||
);
|
||||
}
|
||||
|
||||
pub fn become_dpi_aware(enable: bool) {
|
||||
if !enable { return; }
|
||||
static ENABLE_DPI_AWARENESS: Once = ONCE_INIT;
|
||||
ENABLE_DPI_AWARENESS.call_once(|| { unsafe {
|
||||
if let Some(SetProcessDpiAwarenessContext) = get_function!(
|
||||
"user32.dll",
|
||||
SetProcessDpiAwarenessContext
|
||||
) {
|
||||
// We are on Windows 10 Anniversary Update (1607) or later.
|
||||
if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
|
||||
== FALSE {
|
||||
// V2 only works with Windows 10 Creators Update (1703). Try using the older
|
||||
// V1 if we can't set V2.
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
|
||||
}
|
||||
} else if let Some(SetProcessDpiAwareness) = get_function!(
|
||||
"shcore.dll",
|
||||
SetProcessDpiAwareness
|
||||
) {
|
||||
// We are on Windows 8.1 or later.
|
||||
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
||||
} else if let Some(SetProcessDPIAware) = get_function!(
|
||||
"user32.dll",
|
||||
SetProcessDPIAware
|
||||
) {
|
||||
// We are on Vista or later.
|
||||
SetProcessDPIAware();
|
||||
}
|
||||
} });
|
||||
}
|
||||
|
||||
pub fn enable_non_client_dpi_scaling(hwnd: HWND) {
|
||||
unsafe {
|
||||
if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING {
|
||||
EnableNonClientDpiScaling(hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_monitor_dpi(hmonitor: HMONITOR) -> Option<u32> {
|
||||
unsafe {
|
||||
if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
|
||||
// We are on Windows 8.1 or later.
|
||||
let mut dpi_x = 0;
|
||||
let mut dpi_y = 0;
|
||||
if GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
|
||||
// MSDN says that "the values of *dpiX and *dpiY are identical. You only need to
|
||||
// record one of the values to determine the DPI and respond appropriately".
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
|
||||
return Some(dpi_x as u32)
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub const BASE_DPI: u32 = 96;
|
||||
pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
|
||||
dpi as f64 / BASE_DPI as f64
|
||||
}
|
||||
|
||||
pub unsafe fn get_hwnd_dpi(hwnd: HWND) -> u32 {
|
||||
let hdc = winuser::GetDC(hwnd);
|
||||
if hdc.is_null() {
|
||||
panic!("[winit] `GetDC` returned null!");
|
||||
}
|
||||
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
|
||||
// We are on Windows 10 Anniversary Update (1607) or later.
|
||||
match GetDpiForWindow(hwnd) {
|
||||
0 => BASE_DPI, // 0 is returned if hwnd is invalid
|
||||
dpi => dpi as u32,
|
||||
}
|
||||
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
|
||||
// We are on Windows 8.1 or later.
|
||||
let monitor = winuser::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
if monitor.is_null() {
|
||||
return BASE_DPI;
|
||||
}
|
||||
|
||||
let mut dpi_x = 0;
|
||||
let mut dpi_y = 0;
|
||||
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
|
||||
dpi_x as u32
|
||||
} else {
|
||||
BASE_DPI
|
||||
}
|
||||
} else {
|
||||
// We are on Vista or later.
|
||||
if winuser::IsProcessDPIAware() != FALSE {
|
||||
// If the process is DPI aware, then scaling must be handled by the application using
|
||||
// this DPI value.
|
||||
GetDeviceCaps(hdc, LOGPIXELSX) as u32
|
||||
} else {
|
||||
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
|
||||
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
|
||||
// application and the WM.
|
||||
BASE_DPI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_hwnd_scale_factor(hwnd: HWND) -> f64 {
|
||||
dpi_to_scale_factor(unsafe { get_hwnd_dpi(hwnd) })
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,173 +0,0 @@
|
||||
use winapi::shared::minwindef::{BOOL, DWORD, LPARAM, TRUE};
|
||||
use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, POINT};
|
||||
use winapi::um::winnt::LONG;
|
||||
use winapi::um::winuser;
|
||||
|
||||
use std::{mem, ptr};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::{EventsLoop, util};
|
||||
use dpi::{PhysicalPosition, PhysicalSize};
|
||||
use platform::platform::dpi::{dpi_to_scale_factor, get_monitor_dpi};
|
||||
use platform::platform::window::Window;
|
||||
|
||||
/// Win32 implementation of the main `MonitorId` object.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorId {
|
||||
/// Monitor handle.
|
||||
hmonitor: HMonitor,
|
||||
/// The system name of the monitor.
|
||||
monitor_name: String,
|
||||
/// True if this is the primary monitor.
|
||||
primary: bool,
|
||||
/// The position of the monitor in pixels on the desktop.
|
||||
///
|
||||
/// A window that is positioned at these coordinates will overlap the monitor.
|
||||
position: (i32, i32),
|
||||
/// The current resolution in pixels on the monitor.
|
||||
dimensions: (u32, u32),
|
||||
/// DPI scale factor.
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// https://github.com/retep998/winapi-rs/issues/396
|
||||
#[derive(Debug, Clone)]
|
||||
struct HMonitor(HMONITOR);
|
||||
|
||||
unsafe impl Send for HMonitor {}
|
||||
|
||||
unsafe extern "system" fn monitor_enum_proc(
|
||||
hmonitor: HMONITOR,
|
||||
_hdc: HDC,
|
||||
_place: LPRECT,
|
||||
data: LPARAM,
|
||||
) -> BOOL {
|
||||
let monitors = data as *mut VecDeque<MonitorId>;
|
||||
(*monitors).push_back(MonitorId::from_hmonitor(hmonitor));
|
||||
TRUE // continue enumeration
|
||||
}
|
||||
|
||||
pub fn get_available_monitors() -> VecDeque<MonitorId> {
|
||||
let mut monitors: VecDeque<MonitorId> = VecDeque::new();
|
||||
unsafe {
|
||||
winuser::EnumDisplayMonitors(
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(monitor_enum_proc),
|
||||
&mut monitors as *mut _ as LPARAM,
|
||||
);
|
||||
}
|
||||
monitors
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor() -> MonitorId {
|
||||
const ORIGIN: POINT = POINT { x: 0, y: 0 };
|
||||
let hmonitor = unsafe {
|
||||
winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY)
|
||||
};
|
||||
MonitorId::from_hmonitor(hmonitor)
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
// TODO: Investigate opportunities for caching
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
pub fn get_current_monitor(hwnd: HWND) -> MonitorId {
|
||||
let hmonitor = unsafe {
|
||||
winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST)
|
||||
};
|
||||
MonitorId::from_hmonitor(hmonitor)
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
get_primary_monitor()
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
get_primary_monitor()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINFOEXW, util::WinError> {
|
||||
let mut monitor_info: winuser::MONITORINFOEXW = unsafe { mem::uninitialized() };
|
||||
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
|
||||
let status = unsafe {
|
||||
winuser::GetMonitorInfoW(
|
||||
hmonitor,
|
||||
&mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO,
|
||||
)
|
||||
};
|
||||
if status == 0 {
|
||||
Err(util::WinError::from_last_error())
|
||||
} else {
|
||||
Ok(monitor_info)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self {
|
||||
let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed");
|
||||
let place = monitor_info.rcMonitor;
|
||||
let dimensions = (
|
||||
(place.right - place.left) as u32,
|
||||
(place.bottom - place.top) as u32,
|
||||
);
|
||||
MonitorId {
|
||||
hmonitor: HMonitor(hmonitor),
|
||||
monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()),
|
||||
primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY),
|
||||
position: (place.left as i32, place.top as i32),
|
||||
dimensions,
|
||||
hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
|
||||
let left = self.position.0 as LONG;
|
||||
let right = left + self.dimensions.0 as LONG;
|
||||
let top = self.position.1 as LONG;
|
||||
let bottom = top + self.dimensions.1 as LONG;
|
||||
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some(self.monitor_name.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> String {
|
||||
self.monitor_name.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hmonitor(&self) -> HMONITOR {
|
||||
self.hmonitor.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
self.dimensions.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
self.position.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.hidpi_factor
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
use std::{self, mem, ptr, slice, io};
|
||||
use std::ops::BitAnd;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use MouseCursor;
|
||||
use winapi::ctypes::wchar_t;
|
||||
use winapi::shared::minwindef::{BOOL, DWORD};
|
||||
use winapi::shared::windef::{HWND, POINT, RECT};
|
||||
use winapi::um::errhandlingapi::GetLastError;
|
||||
use winapi::um::winbase::{
|
||||
FormatMessageW,
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
||||
FORMAT_MESSAGE_FROM_SYSTEM,
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
lstrlenW,
|
||||
LocalFree,
|
||||
};
|
||||
use winapi::um::winnt::{
|
||||
LPCWSTR,
|
||||
MAKELANGID,
|
||||
LANG_NEUTRAL,
|
||||
SUBLANG_DEFAULT,
|
||||
};
|
||||
use winapi::um::winuser;
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where T:
|
||||
Copy + PartialEq + BitAnd<T, Output = T>
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
pub fn wchar_to_string(wchar: &[wchar_t]) -> String {
|
||||
String::from_utf16_lossy(wchar).to_string()
|
||||
}
|
||||
|
||||
pub fn wchar_ptr_to_string(wchar: *const wchar_t) -> String {
|
||||
let len = unsafe { lstrlenW(wchar) } as usize;
|
||||
let wchar_slice = unsafe { slice::from_raw_parts(wchar, len) };
|
||||
wchar_to_string(wchar_slice)
|
||||
}
|
||||
|
||||
pub unsafe fn status_map<T, F: FnMut(&mut T) -> BOOL>(mut fun: F) -> Option<T> {
|
||||
let mut data: T = mem::uninitialized();
|
||||
if fun(&mut data) != 0 {
|
||||
Some(data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn win_to_err<F: FnOnce() -> BOOL>(f: F) -> Result<(), io::Error> {
|
||||
if f() != 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cursor_pos() -> Option<POINT> {
|
||||
unsafe { status_map(|cursor_pos| winuser::GetCursorPos(cursor_pos)) }
|
||||
}
|
||||
|
||||
pub fn get_window_rect(hwnd: HWND) -> Option<RECT> {
|
||||
unsafe { status_map(|rect| winuser::GetWindowRect(hwnd, rect)) }
|
||||
}
|
||||
|
||||
pub fn get_client_rect(hwnd: HWND) -> Result<RECT, io::Error> {
|
||||
unsafe {
|
||||
let mut rect = mem::uninitialized();
|
||||
let mut top_left = mem::zeroed();
|
||||
|
||||
win_to_err(|| winuser::ClientToScreen(hwnd, &mut top_left))?;
|
||||
win_to_err(|| winuser::GetClientRect(hwnd, &mut rect))?;
|
||||
rect.left += top_left.x;
|
||||
rect.top += top_left.y;
|
||||
rect.right += top_left.x;
|
||||
rect.bottom += top_left.y;
|
||||
|
||||
Ok(rect)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option<RECT> {
|
||||
unsafe {
|
||||
let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE);
|
||||
let style_ex = winuser::GetWindowLongW(hwnd, winuser::GWL_EXSTYLE);
|
||||
adjust_window_rect_with_styles(hwnd, style as _, style_ex as _, rect)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_window_rect_with_styles(hwnd: HWND, style: DWORD, style_ex: DWORD, rect: RECT) -> Option<RECT> {
|
||||
unsafe { status_map(|r| {
|
||||
*r = rect;
|
||||
|
||||
let b_menu = !winuser::GetMenu(hwnd).is_null() as BOOL;
|
||||
winuser::AdjustWindowRectEx(r, style as _ , b_menu, style_ex as _)
|
||||
}) }
|
||||
}
|
||||
|
||||
pub fn set_cursor_hidden(hidden: bool) {
|
||||
static HIDDEN: AtomicBool = AtomicBool::new(false);
|
||||
let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden;
|
||||
if changed {
|
||||
unsafe{ winuser::ShowCursor(!hidden as BOOL) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
|
||||
unsafe {
|
||||
let rect_ptr = rect.as_ref().map(|r| r as *const RECT).unwrap_or(ptr::null());
|
||||
win_to_err(|| winuser::ClipCursor(rect_ptr))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_focused(window: HWND) -> bool {
|
||||
window == unsafe{ winuser::GetActiveWindow() }
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct WinError(Option<String>);
|
||||
|
||||
impl WinError {
|
||||
pub fn from_last_error() -> Self {
|
||||
WinError(unsafe { get_last_error() })
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_last_error() -> Option<String> {
|
||||
let err = GetLastError();
|
||||
if err != 0 {
|
||||
let buf_addr: LPCWSTR = {
|
||||
let mut buf_addr: LPCWSTR = mem::uninitialized();
|
||||
FormatMessageW(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER
|
||||
| FORMAT_MESSAGE_FROM_SYSTEM
|
||||
| FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
ptr::null(),
|
||||
err,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) as DWORD,
|
||||
// This is a pointer to a pointer
|
||||
&mut buf_addr as *mut LPCWSTR as *mut _,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
buf_addr
|
||||
};
|
||||
if !buf_addr.is_null() {
|
||||
let buf_len = lstrlenW(buf_addr) as usize;
|
||||
let buf_slice = std::slice::from_raw_parts(buf_addr, buf_len);
|
||||
let string = wchar_to_string(buf_slice);
|
||||
LocalFree(buf_addr as *mut _);
|
||||
return Some(string);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl MouseCursor {
|
||||
pub(crate) fn to_windows_cursor(self) -> *const wchar_t {
|
||||
match self {
|
||||
MouseCursor::Arrow | MouseCursor::Default => winuser::IDC_ARROW,
|
||||
MouseCursor::Hand => winuser::IDC_HAND,
|
||||
MouseCursor::Crosshair => winuser::IDC_CROSS,
|
||||
MouseCursor::Text | MouseCursor::VerticalText => winuser::IDC_IBEAM,
|
||||
MouseCursor::NotAllowed | MouseCursor::NoDrop => winuser::IDC_NO,
|
||||
MouseCursor::Grab | MouseCursor::Grabbing |
|
||||
MouseCursor::Move | MouseCursor::AllScroll => winuser::IDC_SIZEALL,
|
||||
MouseCursor::EResize | MouseCursor::WResize |
|
||||
MouseCursor::EwResize | MouseCursor::ColResize => winuser::IDC_SIZEWE,
|
||||
MouseCursor::NResize | MouseCursor::SResize |
|
||||
MouseCursor::NsResize | MouseCursor::RowResize => winuser::IDC_SIZENS,
|
||||
MouseCursor::NeResize | MouseCursor::SwResize |
|
||||
MouseCursor::NeswResize => winuser::IDC_SIZENESW,
|
||||
MouseCursor::NwResize | MouseCursor::SeResize |
|
||||
MouseCursor::NwseResize => winuser::IDC_SIZENWSE,
|
||||
MouseCursor::Wait => winuser::IDC_WAIT,
|
||||
MouseCursor::Progress => winuser::IDC_APPSTARTING,
|
||||
MouseCursor::Help => winuser::IDC_HELP,
|
||||
_ => winuser::IDC_ARROW, // use arrow for the missing cases.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,860 +0,0 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::{io, mem, ptr};
|
||||
use std::cell::Cell;
|
||||
use std::ffi::OsStr;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
use winapi::ctypes::c_int;
|
||||
use winapi::shared::minwindef::{DWORD, LPARAM, UINT, WORD, WPARAM};
|
||||
use winapi::shared::windef::{HWND, POINT, RECT};
|
||||
use winapi::um::{combaseapi, dwmapi, libloaderapi, winuser};
|
||||
use winapi::um::objbase::COINIT_MULTITHREADED;
|
||||
use winapi::um::shobjidl_core::{CLSID_TaskbarList, ITaskbarList2};
|
||||
use winapi::um::wingdi::{CreateRectRgn, DeleteObject};
|
||||
use winapi::um::winnt::{LONG, LPCWSTR};
|
||||
|
||||
use {
|
||||
CreationError,
|
||||
Icon,
|
||||
LogicalPosition,
|
||||
LogicalSize,
|
||||
MonitorId as RootMonitorId,
|
||||
MouseCursor,
|
||||
PhysicalSize,
|
||||
WindowAttributes,
|
||||
};
|
||||
use platform::platform::{PlatformSpecificWindowBuilderAttributes, WindowId};
|
||||
use platform::platform::dpi::{dpi_to_scale_factor, get_hwnd_dpi};
|
||||
use platform::platform::events_loop::{self, EventsLoop, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID};
|
||||
use platform::platform::icon::{self, IconType, WinIcon};
|
||||
use platform::platform::monitor::get_available_monitors;
|
||||
use platform::platform::raw_input::register_all_mice_and_keyboards_for_raw_input;
|
||||
use platform::platform::util;
|
||||
use platform::platform::window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState};
|
||||
|
||||
/// The Win32 implementation of the main `Window` object.
|
||||
pub struct Window {
|
||||
/// Main handle for the window.
|
||||
window: WindowWrapper,
|
||||
|
||||
/// The current window state.
|
||||
window_state: Arc<Mutex<WindowState>>,
|
||||
|
||||
// The events loop proxy.
|
||||
events_loop_proxy: events_loop::EventsLoopProxy,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(
|
||||
events_loop: &EventsLoop,
|
||||
w_attr: WindowAttributes,
|
||||
pl_attr: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, CreationError> {
|
||||
let (tx, rx) = channel();
|
||||
let proxy = events_loop.create_proxy();
|
||||
events_loop.execute_in_thread(move |inserter| {
|
||||
// We dispatch an `init` function because of code style.
|
||||
// First person to remove the need for cloning here gets a cookie!
|
||||
let win = unsafe { init(w_attr.clone(), pl_attr.clone(), inserter, proxy.clone()) };
|
||||
let _ = tx.send(win);
|
||||
});
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
|
||||
pub fn set_title(&self, text: &str) {
|
||||
let text = OsStr::new(text)
|
||||
.encode_wide()
|
||||
.chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
unsafe {
|
||||
winuser::SetWindowTextW(self.window.0, text.as_ptr() as LPCWSTR);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
unsafe {
|
||||
winuser::ShowWindow(self.window.0, winuser::SW_SHOW);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
unsafe {
|
||||
winuser::ShowWindow(self.window.0, winuser::SW_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_position_physical(&self) -> Option<(i32, i32)> {
|
||||
util::get_window_rect(self.window.0)
|
||||
.map(|rect| (rect.left as i32, rect.top as i32))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
self.get_position_physical()
|
||||
.map(|physical_position| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
LogicalPosition::from_physical(physical_position, dpi_factor)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_inner_position_physical(&self) -> Option<(i32, i32)> {
|
||||
let mut position: POINT = unsafe { mem::zeroed() };
|
||||
if unsafe { winuser::ClientToScreen(self.window.0, &mut position) } == 0 {
|
||||
return None;
|
||||
}
|
||||
Some((position.x, position.y))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
self.get_inner_position_physical()
|
||||
.map(|physical_position| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
LogicalPosition::from_physical(physical_position, dpi_factor)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn set_position_physical(&self, x: i32, y: i32) {
|
||||
unsafe {
|
||||
winuser::SetWindowPos(
|
||||
self.window.0,
|
||||
ptr::null_mut(),
|
||||
x as c_int,
|
||||
y as c_int,
|
||||
0,
|
||||
0,
|
||||
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOSIZE,
|
||||
);
|
||||
winuser::UpdateWindow(self.window.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, logical_position: LogicalPosition) {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
let (x, y) = logical_position.to_physical(dpi_factor).into();
|
||||
self.set_position_physical(x, y);
|
||||
}
|
||||
|
||||
pub(crate) fn get_inner_size_physical(&self) -> Option<(u32, u32)> {
|
||||
let mut rect: RECT = unsafe { mem::uninitialized() };
|
||||
if unsafe { winuser::GetClientRect(self.window.0, &mut rect) } == 0 {
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
(rect.right - rect.left) as u32,
|
||||
(rect.bottom - rect.top) as u32,
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
self.get_inner_size_physical()
|
||||
.map(|physical_size| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
LogicalSize::from_physical(physical_size, dpi_factor)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_outer_size_physical(&self) -> Option<(u32, u32)> {
|
||||
util::get_window_rect(self.window.0)
|
||||
.map(|rect| (
|
||||
(rect.right - rect.left) as u32,
|
||||
(rect.bottom - rect.top) as u32,
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
self.get_outer_size_physical()
|
||||
.map(|physical_size| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
LogicalSize::from_physical(physical_size, dpi_factor)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) {
|
||||
unsafe {
|
||||
let rect = util::adjust_window_rect(
|
||||
self.window.0,
|
||||
RECT {
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: y as LONG,
|
||||
right: x as LONG,
|
||||
}
|
||||
).expect("adjust_window_rect failed");
|
||||
|
||||
let outer_x = (rect.right - rect.left).abs() as c_int;
|
||||
let outer_y = (rect.top - rect.bottom).abs() as c_int;
|
||||
winuser::SetWindowPos(
|
||||
self.window.0,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
outer_x,
|
||||
outer_y,
|
||||
winuser::SWP_ASYNCWINDOWPOS
|
||||
| winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOREPOSITION
|
||||
| winuser::SWP_NOMOVE,
|
||||
);
|
||||
winuser::UpdateWindow(self.window.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, logical_size: LogicalSize) {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
let (width, height) = logical_size.to_physical(dpi_factor).into();
|
||||
self.set_inner_size_physical(width, height);
|
||||
}
|
||||
|
||||
pub(crate) fn set_min_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
|
||||
self.window_state.lock().unwrap().min_size = dimensions.map(Into::into);
|
||||
// Make windows re-check the window size bounds.
|
||||
self.get_inner_size_physical()
|
||||
.map(|(width, height)| self.set_inner_size_physical(width, height));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, logical_size: Option<LogicalSize>) {
|
||||
let physical_size = logical_size.map(|logical_size| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
logical_size.to_physical(dpi_factor).into()
|
||||
});
|
||||
self.set_min_dimensions_physical(physical_size);
|
||||
}
|
||||
|
||||
pub fn set_max_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
|
||||
self.window_state.lock().unwrap().max_size = dimensions.map(Into::into);
|
||||
// Make windows re-check the window size bounds.
|
||||
self.get_inner_size_physical()
|
||||
.map(|(width, height)| self.set_inner_size_physical(width, height));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, logical_size: Option<LogicalSize>) {
|
||||
let physical_size = logical_size.map(|logical_size| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
logical_size.to_physical(dpi_factor).into()
|
||||
});
|
||||
self.set_max_dimensions_physical(physical_size);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
WindowState::set_window_flags(
|
||||
window_state.lock().unwrap(),
|
||||
window.0,
|
||||
None,
|
||||
|f| f.set(WindowFlags::RESIZABLE, resizable),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the `hwnd` of this window.
|
||||
#[inline]
|
||||
pub fn hwnd(&self) -> HWND {
|
||||
self.window.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, cursor: MouseCursor) {
|
||||
self.window_state.lock().unwrap().mouse.cursor = cursor;
|
||||
self.events_loop_proxy.execute_in_thread(move |_| unsafe {
|
||||
let cursor = winuser::LoadCursorW(
|
||||
ptr::null_mut(),
|
||||
cursor.to_windows_cursor(),
|
||||
);
|
||||
winuser::SetCursor(cursor);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let (tx, rx) = channel();
|
||||
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
let result = window_state.lock().unwrap().mouse
|
||||
.set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, grab))
|
||||
.map_err(|e| e.to_string());
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide_cursor(&self, hide: bool) {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let (tx, rx) = channel();
|
||||
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
let result = window_state.lock().unwrap().mouse
|
||||
.set_cursor_flags(window.0, |f| f.set(CursorFlags::HIDDEN, hide))
|
||||
.map_err(|e| e.to_string());
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
rx.recv().unwrap().ok();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.window_state.lock().unwrap().dpi_factor
|
||||
}
|
||||
|
||||
fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), String> {
|
||||
let mut point = POINT { x, y };
|
||||
unsafe {
|
||||
if winuser::ClientToScreen(self.window.0, &mut point) == 0 {
|
||||
return Err("`ClientToScreen` failed".to_owned());
|
||||
}
|
||||
if winuser::SetCursorPos(point.x, point.y) == 0 {
|
||||
return Err("`SetCursorPos` failed".to_owned());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), String> {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
let (x, y) = logical_position.to_physical(dpi_factor).into();
|
||||
self.set_cursor_position_physical(x, y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId(self.window.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
WindowState::set_window_flags(
|
||||
window_state.lock().unwrap(),
|
||||
window.0,
|
||||
None,
|
||||
|f| f.set(WindowFlags::MAXIMIZED, maximized),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
window_state.fullscreen.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
||||
unsafe {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
match &monitor {
|
||||
&Some(RootMonitorId { ref inner }) => {
|
||||
let (x, y): (i32, i32) = inner.get_position().into();
|
||||
let (width, height): (u32, u32) = inner.get_dimensions().into();
|
||||
|
||||
let mut monitor = monitor.clone();
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
let mut window_state_lock = window_state.lock().unwrap();
|
||||
|
||||
let client_rect = util::get_client_rect(window.0).expect("get client rect failed!");
|
||||
window_state_lock.saved_window = Some(SavedWindow {
|
||||
client_rect,
|
||||
dpi_factor: window_state_lock.dpi_factor
|
||||
});
|
||||
|
||||
window_state_lock.fullscreen = monitor.take();
|
||||
WindowState::refresh_window_state(
|
||||
window_state_lock,
|
||||
window.0,
|
||||
Some(RECT {
|
||||
left: x,
|
||||
top: y,
|
||||
right: x + width as c_int,
|
||||
bottom: y + height as c_int,
|
||||
})
|
||||
);
|
||||
|
||||
mark_fullscreen(window.0, true);
|
||||
});
|
||||
}
|
||||
&None => {
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
let mut window_state_lock = window_state.lock().unwrap();
|
||||
window_state_lock.fullscreen = None;
|
||||
|
||||
if let Some(SavedWindow{client_rect, dpi_factor}) = window_state_lock.saved_window {
|
||||
window_state_lock.dpi_factor = dpi_factor;
|
||||
window_state_lock.saved_window = None;
|
||||
|
||||
WindowState::refresh_window_state(
|
||||
window_state_lock,
|
||||
window.0,
|
||||
Some(client_rect)
|
||||
);
|
||||
}
|
||||
|
||||
mark_fullscreen(window.0, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
let client_rect = util::get_client_rect(window.0).expect("get client rect failed!");
|
||||
WindowState::set_window_flags(
|
||||
window_state.lock().unwrap(),
|
||||
window.0,
|
||||
Some(client_rect),
|
||||
|f| f.set(WindowFlags::DECORATIONS, decorations),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, always_on_top: bool) {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
WindowState::set_window_flags(
|
||||
window_state.lock().unwrap(),
|
||||
window.0,
|
||||
None,
|
||||
|f| f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
RootMonitorId {
|
||||
inner: EventsLoop::get_current_monitor(self.window.0),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, mut window_icon: Option<Icon>) {
|
||||
let window_icon = window_icon
|
||||
.take()
|
||||
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_SMALL`"));
|
||||
if let Some(ref window_icon) = window_icon {
|
||||
window_icon.set_for_window(self.window.0, IconType::Small);
|
||||
} else {
|
||||
icon::unset_for_window(self.window.0, IconType::Small);
|
||||
}
|
||||
self.window_state.lock().unwrap().window_icon = window_icon;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_taskbar_icon(&self, mut taskbar_icon: Option<Icon>) {
|
||||
let taskbar_icon = taskbar_icon
|
||||
.take()
|
||||
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_BIG`"));
|
||||
if let Some(ref taskbar_icon) = taskbar_icon {
|
||||
taskbar_icon.set_for_window(self.window.0, IconType::Big);
|
||||
} else {
|
||||
icon::unset_for_window(self.window.0, IconType::Big);
|
||||
}
|
||||
self.window_state.lock().unwrap().taskbar_icon = taskbar_icon;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// The window must be destroyed from the same thread that created it, so we send a
|
||||
// custom message to be handled by our callback to do the actual work.
|
||||
winuser::PostMessageW(self.window.0, *DESTROY_MSG_ID, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple non-owning wrapper around a window.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
pub struct WindowWrapper(HWND);
|
||||
|
||||
// Send and Sync are not implemented for HWND and HDC, we have to wrap it and implement them manually.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// https://github.com/retep998/winapi-rs/issues/396
|
||||
unsafe impl Sync for WindowWrapper {}
|
||||
unsafe impl Send for WindowWrapper {}
|
||||
|
||||
pub unsafe fn adjust_size(
|
||||
physical_size: PhysicalSize,
|
||||
style: DWORD,
|
||||
ex_style: DWORD,
|
||||
) -> (LONG, LONG) {
|
||||
let (width, height): (u32, u32) = physical_size.into();
|
||||
let mut rect = RECT {
|
||||
left: 0,
|
||||
right: width as LONG,
|
||||
top: 0,
|
||||
bottom: height as LONG,
|
||||
};
|
||||
winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style);
|
||||
(rect.right - rect.left, rect.bottom - rect.top)
|
||||
}
|
||||
|
||||
unsafe fn init(
|
||||
mut attributes: WindowAttributes,
|
||||
mut pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
inserter: events_loop::Inserter,
|
||||
events_loop_proxy: events_loop::EventsLoopProxy,
|
||||
) -> Result<Window, CreationError> {
|
||||
let title = OsStr::new(&attributes.title)
|
||||
.encode_wide()
|
||||
.chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let window_icon = {
|
||||
let icon = attributes.window_icon
|
||||
.take()
|
||||
.map(WinIcon::from_icon);
|
||||
if icon.is_some() {
|
||||
Some(icon.unwrap().map_err(|err| {
|
||||
CreationError::OsError(format!("Failed to create `ICON_SMALL`: {:?}", err))
|
||||
})?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let taskbar_icon = {
|
||||
let icon = pl_attribs.taskbar_icon
|
||||
.take()
|
||||
.map(WinIcon::from_icon);
|
||||
if icon.is_some() {
|
||||
Some(icon.unwrap().map_err(|err| {
|
||||
CreationError::OsError(format!("Failed to create `ICON_BIG`: {:?}", err))
|
||||
})?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// registering the window class
|
||||
let class_name = register_window_class(&window_icon, &taskbar_icon);
|
||||
|
||||
let guessed_dpi_factor = {
|
||||
let monitors = get_available_monitors();
|
||||
let dpi_factor = if !monitors.is_empty() {
|
||||
let mut dpi_factor = Some(monitors[0].get_hidpi_factor());
|
||||
for monitor in &monitors {
|
||||
if Some(monitor.get_hidpi_factor()) != dpi_factor {
|
||||
dpi_factor = None;
|
||||
}
|
||||
}
|
||||
dpi_factor
|
||||
} else {
|
||||
return Err(CreationError::OsError(format!("No monitors were detected.")));
|
||||
};
|
||||
dpi_factor.unwrap_or_else(|| {
|
||||
util::get_cursor_pos()
|
||||
.and_then(|cursor_pos| {
|
||||
let mut dpi_factor = None;
|
||||
for monitor in &monitors {
|
||||
if monitor.contains_point(&cursor_pos) {
|
||||
dpi_factor = Some(monitor.get_hidpi_factor());
|
||||
break;
|
||||
}
|
||||
}
|
||||
dpi_factor
|
||||
})
|
||||
.unwrap_or(1.0)
|
||||
})
|
||||
};
|
||||
info!("Guessed window DPI factor: {}", guessed_dpi_factor);
|
||||
|
||||
let dimensions = attributes.dimensions.unwrap_or_else(|| (1024, 768).into());
|
||||
|
||||
let mut window_flags = WindowFlags::empty();
|
||||
window_flags.set(WindowFlags::DECORATIONS, attributes.decorations);
|
||||
window_flags.set(WindowFlags::ALWAYS_ON_TOP, attributes.always_on_top);
|
||||
window_flags.set(WindowFlags::NO_BACK_BUFFER, pl_attribs.no_redirection_bitmap);
|
||||
window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent);
|
||||
// WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured.
|
||||
window_flags.set(WindowFlags::RESIZABLE, attributes.resizable);
|
||||
window_flags.set(WindowFlags::CHILD, pl_attribs.parent.is_some());
|
||||
window_flags.set(WindowFlags::ON_TASKBAR, true);
|
||||
|
||||
// creating the real window this time, by using the functions in `extra_functions`
|
||||
let real_window = {
|
||||
let (style, ex_style) = window_flags.to_window_styles();
|
||||
let handle = winuser::CreateWindowExW(
|
||||
ex_style,
|
||||
class_name.as_ptr(),
|
||||
title.as_ptr() as LPCWSTR,
|
||||
style,
|
||||
winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT,
|
||||
winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT,
|
||||
pl_attribs.parent.unwrap_or(ptr::null_mut()),
|
||||
ptr::null_mut(),
|
||||
libloaderapi::GetModuleHandleW(ptr::null()),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
if handle.is_null() {
|
||||
return Err(CreationError::OsError(format!("CreateWindowEx function failed: {}",
|
||||
format!("{}", io::Error::last_os_error()))));
|
||||
}
|
||||
|
||||
WindowWrapper(handle)
|
||||
};
|
||||
|
||||
// Set up raw input
|
||||
register_all_mice_and_keyboards_for_raw_input(real_window.0);
|
||||
|
||||
// Register for touch events if applicable
|
||||
{
|
||||
let digitizer = winuser::GetSystemMetrics( winuser::SM_DIGITIZER ) as u32;
|
||||
if digitizer & winuser::NID_READY != 0 {
|
||||
winuser::RegisterTouchWindow( real_window.0, winuser::TWF_WANTPALM );
|
||||
}
|
||||
}
|
||||
|
||||
let dpi = get_hwnd_dpi(real_window.0);
|
||||
let dpi_factor = dpi_to_scale_factor(dpi);
|
||||
if dpi_factor != guessed_dpi_factor {
|
||||
let (width, height): (u32, u32) = dimensions.into();
|
||||
let mut packed_dimensions = 0;
|
||||
// MAKELPARAM isn't provided by winapi yet.
|
||||
let ptr = &mut packed_dimensions as *mut LPARAM as *mut WORD;
|
||||
*ptr.offset(0) = width as WORD;
|
||||
*ptr.offset(1) = height as WORD;
|
||||
winuser::PostMessageW(
|
||||
real_window.0,
|
||||
*INITIAL_DPI_MSG_ID,
|
||||
dpi as WPARAM,
|
||||
packed_dimensions,
|
||||
);
|
||||
}
|
||||
|
||||
// making the window transparent
|
||||
if attributes.transparent && !pl_attribs.no_redirection_bitmap {
|
||||
let region = CreateRectRgn(0, 0, -1, -1); // makes the window transparent
|
||||
|
||||
let bb = dwmapi::DWM_BLURBEHIND {
|
||||
dwFlags: dwmapi::DWM_BB_ENABLE | dwmapi::DWM_BB_BLURREGION,
|
||||
fEnable: 1,
|
||||
hRgnBlur: region,
|
||||
fTransitionOnMaximized: 0,
|
||||
};
|
||||
|
||||
dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb);
|
||||
DeleteObject(region as _);
|
||||
|
||||
if attributes.decorations {
|
||||
// HACK: When opaque (opacity 255), there is a trail whenever
|
||||
// the transparent window is moved. By reducing it to 254,
|
||||
// the window is rendered properly.
|
||||
let opacity = 254;
|
||||
|
||||
// The color key can be any value except for black (0x0).
|
||||
let color_key = 0x0030c100;
|
||||
|
||||
winuser::SetLayeredWindowAttributes(real_window.0, color_key, opacity, winuser::LWA_ALPHA);
|
||||
}
|
||||
}
|
||||
|
||||
window_flags.set(WindowFlags::VISIBLE, attributes.visible);
|
||||
window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized);
|
||||
|
||||
let window_state = {
|
||||
let mut window_state = WindowState::new(
|
||||
&attributes,
|
||||
window_icon,
|
||||
taskbar_icon,
|
||||
dpi_factor,
|
||||
);
|
||||
let window_state = Arc::new(Mutex::new(window_state));
|
||||
WindowState::set_window_flags(
|
||||
window_state.lock().unwrap(),
|
||||
real_window.0,
|
||||
None,
|
||||
|f| *f = window_flags,
|
||||
);
|
||||
window_state
|
||||
};
|
||||
|
||||
let win = Window {
|
||||
window: real_window,
|
||||
window_state,
|
||||
events_loop_proxy,
|
||||
};
|
||||
|
||||
if let Some(_) = attributes.fullscreen {
|
||||
win.set_fullscreen(attributes.fullscreen);
|
||||
force_window_active(win.window.0);
|
||||
}
|
||||
|
||||
if let Some(dimensions) = attributes.dimensions {
|
||||
win.set_inner_size(dimensions);
|
||||
}
|
||||
|
||||
inserter.insert(win.window.0, win.window_state.clone());
|
||||
|
||||
Ok(win)
|
||||
}
|
||||
|
||||
unsafe fn register_window_class(
|
||||
window_icon: &Option<WinIcon>,
|
||||
taskbar_icon: &Option<WinIcon>,
|
||||
) -> Vec<u16> {
|
||||
let class_name: Vec<_> = OsStr::new("Window Class")
|
||||
.encode_wide()
|
||||
.chain(Some(0).into_iter())
|
||||
.collect();
|
||||
|
||||
let h_icon = taskbar_icon
|
||||
.as_ref()
|
||||
.map(|icon| icon.handle)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
let h_icon_small = window_icon
|
||||
.as_ref()
|
||||
.map(|icon| icon.handle)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
let class = winuser::WNDCLASSEXW {
|
||||
cbSize: mem::size_of::<winuser::WNDCLASSEXW>() as UINT,
|
||||
style: winuser::CS_HREDRAW | winuser::CS_VREDRAW | winuser::CS_OWNDC,
|
||||
lpfnWndProc: Some(events_loop::callback),
|
||||
cbClsExtra: 0,
|
||||
cbWndExtra: 0,
|
||||
hInstance: libloaderapi::GetModuleHandleW(ptr::null()),
|
||||
hIcon: h_icon,
|
||||
hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly
|
||||
hbrBackground: ptr::null_mut(),
|
||||
lpszMenuName: ptr::null(),
|
||||
lpszClassName: class_name.as_ptr(),
|
||||
hIconSm: h_icon_small,
|
||||
};
|
||||
|
||||
// We ignore errors because registering the same window class twice would trigger
|
||||
// an error, and because errors here are detected during CreateWindowEx anyway.
|
||||
// Also since there is no weird element in the struct, there is no reason for this
|
||||
// call to fail.
|
||||
winuser::RegisterClassExW(&class);
|
||||
|
||||
class_name
|
||||
}
|
||||
|
||||
struct ComInitialized(*mut ());
|
||||
impl Drop for ComInitialized {
|
||||
fn drop(&mut self) {
|
||||
unsafe { combaseapi::CoUninitialize() };
|
||||
}
|
||||
}
|
||||
|
||||
thread_local!{
|
||||
static COM_INITIALIZED: ComInitialized = {
|
||||
unsafe {
|
||||
combaseapi::CoInitializeEx(ptr::null_mut(), COINIT_MULTITHREADED);
|
||||
ComInitialized(ptr::null_mut())
|
||||
}
|
||||
};
|
||||
|
||||
static TASKBAR_LIST: Cell<*mut ITaskbarList2> = Cell::new(ptr::null_mut());
|
||||
}
|
||||
|
||||
pub fn com_initialized() {
|
||||
COM_INITIALIZED.with(|_| {});
|
||||
}
|
||||
|
||||
// Reference Implementation:
|
||||
// https://github.com/chromium/chromium/blob/f18e79d901f56154f80eea1e2218544285e62623/ui/views/win/fullscreen_handler.cc
|
||||
//
|
||||
// As per MSDN marking the window as fullscreen should ensure that the
|
||||
// taskbar is moved to the bottom of the Z-order when the fullscreen window
|
||||
// is activated. If the window is not fullscreen, the Shell falls back to
|
||||
// heuristics to determine how the window should be treated, which means
|
||||
// that it could still consider the window as fullscreen. :(
|
||||
unsafe fn mark_fullscreen(handle: HWND, fullscreen: bool) {
|
||||
com_initialized();
|
||||
|
||||
TASKBAR_LIST.with(|task_bar_list_ptr| {
|
||||
let mut task_bar_list = task_bar_list_ptr.get();
|
||||
|
||||
if task_bar_list == ptr::null_mut() {
|
||||
use winapi::shared::winerror::S_OK;
|
||||
use winapi::Interface;
|
||||
|
||||
let hr = combaseapi::CoCreateInstance(
|
||||
&CLSID_TaskbarList,
|
||||
ptr::null_mut(),
|
||||
combaseapi::CLSCTX_ALL,
|
||||
&ITaskbarList2::uuidof(),
|
||||
&mut task_bar_list as *mut _ as *mut _,
|
||||
);
|
||||
|
||||
if hr != S_OK || (*task_bar_list).HrInit() != S_OK {
|
||||
// In some old windows, the taskbar object could not be created, we just ignore it
|
||||
return;
|
||||
}
|
||||
task_bar_list_ptr.set(task_bar_list)
|
||||
}
|
||||
|
||||
task_bar_list = task_bar_list_ptr.get();
|
||||
(*task_bar_list).MarkFullscreenWindow(handle, if fullscreen { 1 } else { 0 });
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn force_window_active(handle: HWND) {
|
||||
// In some situation, calling SetForegroundWindow could not bring up the window,
|
||||
// This is a little hack which can "steal" the foreground window permission
|
||||
// We only call this function in the window creation, so it should be fine.
|
||||
// See : https://stackoverflow.com/questions/10740346/setforegroundwindow-only-working-while-visual-studio-is-open
|
||||
let alt_sc = winuser::MapVirtualKeyW(winuser::VK_MENU as _, winuser::MAPVK_VK_TO_VSC);
|
||||
|
||||
let mut inputs: [winuser::INPUT; 2] = mem::zeroed();
|
||||
inputs[0].type_ = winuser::INPUT_KEYBOARD;
|
||||
inputs[0].u.ki_mut().wVk = winuser::VK_LMENU as _;
|
||||
inputs[0].u.ki_mut().wScan = alt_sc as _;
|
||||
inputs[0].u.ki_mut().dwFlags = winuser::KEYEVENTF_EXTENDEDKEY;
|
||||
|
||||
inputs[1].type_ = winuser::INPUT_KEYBOARD;
|
||||
inputs[1].u.ki_mut().wVk = winuser::VK_LMENU as _;
|
||||
inputs[1].u.ki_mut().wScan = alt_sc as _;
|
||||
inputs[1].u.ki_mut().dwFlags = winuser::KEYEVENTF_EXTENDEDKEY | winuser::KEYEVENTF_KEYUP;
|
||||
|
||||
// Simulate a key press and release
|
||||
winuser::SendInput(
|
||||
inputs.len() as _,
|
||||
inputs.as_mut_ptr(),
|
||||
mem::size_of::<winuser::INPUT>() as _,
|
||||
);
|
||||
|
||||
winuser::SetForegroundWindow(handle);
|
||||
}
|
||||
122
src/platform_impl/android/ffi.rs
Normal file
122
src/platform_impl/android/ffi.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
#![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;
|
||||
@@ -4,65 +4,64 @@ extern crate android_glue;
|
||||
|
||||
mod ffi;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::mpsc::{Receiver, channel};
|
||||
|
||||
use {
|
||||
CreationError,
|
||||
Event,
|
||||
LogicalPosition,
|
||||
LogicalSize,
|
||||
MouseCursor,
|
||||
PhysicalPosition,
|
||||
PhysicalSize,
|
||||
WindowAttributes,
|
||||
WindowEvent,
|
||||
WindowId as RootWindowId,
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::VecDeque,
|
||||
fmt,
|
||||
os::raw::c_void,
|
||||
sync::mpsc::{channel, Receiver},
|
||||
};
|
||||
use CreationError::OsError;
|
||||
use events::{Touch, TouchPhase};
|
||||
use window::MonitorId as RootMonitorId;
|
||||
|
||||
pub struct EventsLoop {
|
||||
use crate::{
|
||||
error::{ExternalError, NotSupportedError},
|
||||
events::{Touch, TouchPhase},
|
||||
window::MonitorHandle as RootMonitorHandle,
|
||||
CreationError, CursorIcon, Event, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize,
|
||||
WindowAttributes, WindowEvent, WindowId as RootWindowId,
|
||||
};
|
||||
use raw_window_handle::{android::AndroidHandle, RawWindowHandle};
|
||||
use CreationError::OsError;
|
||||
|
||||
pub type OsError = std::io::Error;
|
||||
|
||||
pub struct EventLoop {
|
||||
event_rx: Receiver<android_glue::Event>,
|
||||
suspend_callback: RefCell<Option<Box<Fn(bool) -> ()>>>,
|
||||
suspend_callback: RefCell<Option<Box<dyn Fn(bool) -> ()>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventsLoopProxy;
|
||||
pub struct EventLoopProxy;
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn new() -> EventsLoop {
|
||||
impl EventLoop {
|
||||
pub fn new() -> EventLoop {
|
||||
let (tx, rx) = channel();
|
||||
android_glue::add_sender(tx);
|
||||
EventsLoop {
|
||||
EventLoop {
|
||||
event_rx: rx,
|
||||
suspend_callback: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorId);
|
||||
rb.push_back(MonitorHandle);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event)
|
||||
where
|
||||
F: FnMut(::Event),
|
||||
{
|
||||
while let Ok(event) = self.event_rx.try_recv() {
|
||||
let e = match event{
|
||||
let e = match event {
|
||||
android_glue::Event::EventMotion(motion) => {
|
||||
let dpi_factor = MonitorId.get_hidpi_factor();
|
||||
let dpi_factor = MonitorHandle.hidpi_factor();
|
||||
let location = LogicalPosition::from_physical(
|
||||
(motion.x as f64, motion.y as f64),
|
||||
dpi_factor,
|
||||
@@ -77,68 +76,65 @@ impl EventsLoop {
|
||||
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
|
||||
},
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: motion.pointer_id as u64,
|
||||
device_id: DEVICE_ID,
|
||||
}),
|
||||
})
|
||||
},
|
||||
}
|
||||
android_glue::Event::InitWindow => {
|
||||
// The activity went to foreground.
|
||||
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
|
||||
(*cb)(false);
|
||||
}
|
||||
Some(Event::Suspended(false))
|
||||
},
|
||||
Some(Event::Resumed)
|
||||
}
|
||||
android_glue::Event::TermWindow => {
|
||||
// The activity went to background.
|
||||
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
|
||||
(*cb)(true);
|
||||
}
|
||||
Some(Event::Suspended(true))
|
||||
},
|
||||
android_glue::Event::WindowResized |
|
||||
android_glue::Event::ConfigChanged => {
|
||||
Some(Event::Suspended)
|
||||
}
|
||||
android_glue::Event::WindowResized | android_glue::Event::ConfigChanged => {
|
||||
// Activity Orientation changed or resized.
|
||||
let native_window = unsafe { android_glue::get_native_window() };
|
||||
let native_window = unsafe { android_glue::native_window() };
|
||||
if native_window.is_null() {
|
||||
None
|
||||
} else {
|
||||
let dpi_factor = MonitorId.get_hidpi_factor();
|
||||
let physical_size = MonitorId.get_dimensions();
|
||||
let dpi_factor = MonitorHandle.hidpi_factor();
|
||||
let physical_size = MonitorHandle.size();
|
||||
let size = LogicalSize::from_physical(physical_size, dpi_factor);
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Resized(size),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
android_glue::Event::WindowRedrawNeeded => {
|
||||
// The activity needs to be redrawn.
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Refresh,
|
||||
event: WindowEvent::Redraw,
|
||||
})
|
||||
}
|
||||
android_glue::Event::Wake => {
|
||||
Some(Event::Awakened)
|
||||
}
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
android_glue::Event::Wake => Some(Event::Awakened),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(event) = e {
|
||||
callback(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_suspend_callback(&self, cb: Option<Box<Fn(bool) -> ()>>) {
|
||||
pub fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>) {
|
||||
*self.suspend_callback.borrow_mut() = cb;
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event) -> ::ControlFlow,
|
||||
where
|
||||
F: FnMut(::Event) -> ::ControlFlow,
|
||||
{
|
||||
// Yeah that's a very bad implementation.
|
||||
loop {
|
||||
@@ -155,13 +151,13 @@ impl EventsLoop {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
EventsLoopProxy
|
||||
pub fn create_proxy(&self) -> EventLoopProxy {
|
||||
EventLoopProxy
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), ::EventsLoopClosed> {
|
||||
impl EventLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed<()>> {
|
||||
android_glue::wake_event_loop();
|
||||
Ok(())
|
||||
}
|
||||
@@ -190,54 +186,55 @@ pub struct Window {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId;
|
||||
pub struct MonitorHandle;
|
||||
|
||||
impl fmt::Debug for MonitorId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorId {
|
||||
struct MonitorHandle {
|
||||
name: Option<String>,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorId {
|
||||
name: self.get_name(),
|
||||
dimensions: self.get_dimensions(),
|
||||
position: self.get_position(),
|
||||
hidpi_factor: self.get_hidpi_factor(),
|
||||
let monitor_id_proxy = MonitorHandle {
|
||||
name: self.name(),
|
||||
dimensions: self.size(),
|
||||
position: self.outer_position(),
|
||||
hidpi_factor: self.hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
impl MonitorHandle {
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
Some("Primary".to_string())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
pub fn size(&self) -> PhysicalSize {
|
||||
unsafe {
|
||||
let window = android_glue::get_native_window();
|
||||
let window = android_glue::native_window();
|
||||
(
|
||||
ffi::ANativeWindow_getWidth(window) as f64,
|
||||
ffi::ANativeWindow_getHeight(window) as f64,
|
||||
).into()
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
pub fn outer_position(&self) -> PhysicalPosition {
|
||||
// Android assumes single screen
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
pub fn hidpi_factor(&self) -> f64 {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
@@ -248,16 +245,17 @@ pub struct PlatformSpecificWindowBuilderAttributes;
|
||||
pub struct PlatformSpecificHeadlessBuilderAttributes;
|
||||
|
||||
impl Window {
|
||||
pub fn new(_: &EventsLoop, win_attribs: WindowAttributes,
|
||||
_: PlatformSpecificWindowBuilderAttributes)
|
||||
-> Result<Window, CreationError>
|
||||
{
|
||||
let native_window = unsafe { android_glue::get_native_window() };
|
||||
pub fn new(
|
||||
_: &EventLoop,
|
||||
win_attribs: WindowAttributes,
|
||||
_: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, CreationError> {
|
||||
let native_window = unsafe { android_glue::native_window() };
|
||||
if native_window.is_null() {
|
||||
return Err(OsError(format!("Android's native window is null")));
|
||||
}
|
||||
|
||||
android_glue::set_multitouch(win_attribs.multitouch);
|
||||
android_glue::set_multitouch(true);
|
||||
|
||||
Ok(Window {
|
||||
native_window: native_window as *const _,
|
||||
@@ -265,7 +263,7 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_window(&self) -> *const c_void {
|
||||
pub fn native_window(&self) -> *const c_void {
|
||||
self.native_window
|
||||
}
|
||||
|
||||
@@ -285,29 +283,29 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
pub fn outer_position(&self) -> Option<LogicalPosition> {
|
||||
// N/A
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
pub fn inner_position(&self) -> Option<LogicalPosition> {
|
||||
// N/A
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, _position: LogicalPosition) {
|
||||
pub fn set_outer_position(&self, _position: LogicalPosition) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
|
||||
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
|
||||
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
@@ -317,19 +315,19 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
pub fn inner_size(&self) -> Option<LogicalSize> {
|
||||
if self.native_window.is_null() {
|
||||
None
|
||||
} else {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
let physical_size = self.get_current_monitor().get_dimensions();
|
||||
let dpi_factor = self.hidpi_factor();
|
||||
let physical_size = self.current_monitor().size();
|
||||
Some(LogicalSize::from_physical(physical_size, dpi_factor))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
self.get_inner_size()
|
||||
pub fn outer_size(&self) -> Option<LogicalSize> {
|
||||
self.inner_size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -338,18 +336,18 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.get_current_monitor().get_hidpi_factor()
|
||||
pub fn hidpi_factor(&self) -> f64 {
|
||||
self.current_monitor().hidpi_factor()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, _: MouseCursor) {
|
||||
pub fn set_cursor_icon(&self, _: CursorIcon) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
|
||||
Err("Cursor grabbing is not possible on Android.".to_owned())
|
||||
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -358,8 +356,8 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
|
||||
Err("Setting cursor position is not possible on Android.".to_owned())
|
||||
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -369,14 +367,14 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
|
||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
||||
// N/A
|
||||
// Android has single screen maximized apps so nothing to do
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
|
||||
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorHandle>) {
|
||||
// N/A
|
||||
// Android has single screen maximized apps so nothing to do
|
||||
}
|
||||
@@ -397,31 +395,42 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_spot(&self, _spot: LogicalPosition) {
|
||||
pub fn set_ime_position(&self, _spot: LogicalPosition) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
RootMonitorId { inner: MonitorId }
|
||||
pub fn current_monitor(&self) -> RootMonitorHandle {
|
||||
RootMonitorHandle {
|
||||
inner: MonitorHandle,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorId);
|
||||
rb.push_back(MonitorHandle);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let handle = AndroidHandle {
|
||||
a_native_window: self.native_window,
|
||||
..WindowsHandle::empty()
|
||||
};
|
||||
RawWindowHandle::Android(handle)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Window {}
|
||||
958
src/platform_impl/ios/app_state.rs
Normal file
958
src/platform_impl/ios/app_state.rs
Normal file
@@ -0,0 +1,958 @@
|
||||
#![deny(unused_results)]
|
||||
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
collections::HashSet,
|
||||
mem,
|
||||
os::raw::c_void,
|
||||
ptr,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use objc::runtime::{BOOL, YES};
|
||||
|
||||
use crate::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::ControlFlow,
|
||||
platform_impl::platform::{
|
||||
event_loop::{EventHandler, Never},
|
||||
ffi::{
|
||||
id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer,
|
||||
CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
|
||||
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, NSInteger, NSOperatingSystemVersion,
|
||||
NSUInteger,
|
||||
},
|
||||
},
|
||||
window::WindowId as RootWindowId,
|
||||
};
|
||||
|
||||
macro_rules! bug {
|
||||
($($msg:tt)*) => {
|
||||
panic!("winit iOS bug, file an issue: {}", format!($($msg)*))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! bug_assert {
|
||||
($test:expr, $($msg:tt)*) => {
|
||||
assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*))
|
||||
};
|
||||
}
|
||||
|
||||
enum UserCallbackTransitionResult<'a> {
|
||||
Success {
|
||||
event_handler: Box<dyn EventHandler>,
|
||||
active_control_flow: ControlFlow,
|
||||
processing_redraws: bool,
|
||||
},
|
||||
ReentrancyPrevented {
|
||||
queued_events: &'a mut Vec<Event<Never>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Event<Never> {
|
||||
fn is_redraw(&self) -> bool {
|
||||
if let Event::WindowEvent {
|
||||
window_id: _,
|
||||
event: WindowEvent::RedrawRequested,
|
||||
} = self
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is the state machine for the app lifecycle
|
||||
#[derive(Debug)]
|
||||
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
|
||||
enum AppStateImpl {
|
||||
NotLaunched {
|
||||
queued_windows: Vec<id>,
|
||||
queued_events: Vec<Event<Never>>,
|
||||
queued_gpu_redraws: HashSet<id>,
|
||||
},
|
||||
Launching {
|
||||
queued_windows: Vec<id>,
|
||||
queued_events: Vec<Event<Never>>,
|
||||
queued_event_handler: Box<dyn EventHandler>,
|
||||
queued_gpu_redraws: HashSet<id>,
|
||||
},
|
||||
ProcessingEvents {
|
||||
event_handler: Box<dyn EventHandler>,
|
||||
queued_gpu_redraws: HashSet<id>,
|
||||
active_control_flow: ControlFlow,
|
||||
},
|
||||
// special state to deal with reentrancy and prevent mutable aliasing.
|
||||
InUserCallback {
|
||||
queued_events: Vec<Event<Never>>,
|
||||
queued_gpu_redraws: HashSet<id>,
|
||||
},
|
||||
ProcessingRedraws {
|
||||
event_handler: Box<dyn EventHandler>,
|
||||
active_control_flow: ControlFlow,
|
||||
},
|
||||
Waiting {
|
||||
waiting_event_handler: Box<dyn EventHandler>,
|
||||
start: Instant,
|
||||
},
|
||||
PollFinished {
|
||||
waiting_event_handler: Box<dyn EventHandler>,
|
||||
},
|
||||
Terminated,
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
// This should never be `None`, except for briefly during a state transition.
|
||||
app_state: Option<AppStateImpl>,
|
||||
control_flow: ControlFlow,
|
||||
waker: EventLoopWaker,
|
||||
}
|
||||
|
||||
impl Drop for AppState {
|
||||
fn drop(&mut self) {
|
||||
match self.state_mut() {
|
||||
&mut AppStateImpl::NotLaunched {
|
||||
ref mut queued_windows,
|
||||
..
|
||||
}
|
||||
| &mut AppStateImpl::Launching {
|
||||
ref mut queued_windows,
|
||||
..
|
||||
} => {
|
||||
for &mut window in queued_windows {
|
||||
unsafe {
|
||||
let () = msg_send![window, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
// requires main thread
|
||||
unsafe fn get_mut() -> RefMut<'static, AppState> {
|
||||
// basically everything in UIKit requires the main thread, so it's pointless to use the
|
||||
// std::sync APIs.
|
||||
// must be mut because plain `static` requires `Sync`
|
||||
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
assert_main_thread!(
|
||||
"bug in winit: `AppState::get_mut()` can only be called on the main thread"
|
||||
);
|
||||
}
|
||||
let mut guard = APP_STATE.borrow_mut();
|
||||
if guard.is_none() {
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
unsafe fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
|
||||
let waker = EventLoopWaker::new(CFRunLoopGetMain());
|
||||
**guard = Some(AppState {
|
||||
app_state: Some(AppStateImpl::NotLaunched {
|
||||
queued_windows: Vec::new(),
|
||||
queued_events: Vec::new(),
|
||||
queued_gpu_redraws: HashSet::new(),
|
||||
}),
|
||||
control_flow: ControlFlow::default(),
|
||||
waker,
|
||||
});
|
||||
}
|
||||
init_guard(&mut guard)
|
||||
}
|
||||
RefMut::map(guard, |state| state.as_mut().unwrap())
|
||||
}
|
||||
|
||||
fn state(&self) -> &AppStateImpl {
|
||||
match &self.app_state {
|
||||
Some(ref state) => state,
|
||||
None => bug!("`AppState` previously failed a state transition"),
|
||||
}
|
||||
}
|
||||
|
||||
fn state_mut(&mut self) -> &mut AppStateImpl {
|
||||
match &mut self.app_state {
|
||||
Some(ref mut state) => state,
|
||||
None => bug!("`AppState` previously failed a state transition"),
|
||||
}
|
||||
}
|
||||
|
||||
fn take_state(&mut self) -> AppStateImpl {
|
||||
match self.app_state.take() {
|
||||
Some(state) => state,
|
||||
None => bug!("`AppState` previously failed a state transition"),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, new_state: AppStateImpl) {
|
||||
bug_assert!(
|
||||
self.app_state.is_none(),
|
||||
"attempted to set an `AppState` without calling `take_state` first {:?}",
|
||||
self.app_state
|
||||
);
|
||||
self.app_state = Some(new_state)
|
||||
}
|
||||
|
||||
fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl {
|
||||
match &mut self.app_state {
|
||||
Some(ref mut state) => mem::replace(state, new_state),
|
||||
None => bug!("`AppState` previously failed a state transition"),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_launched(&self) -> bool {
|
||||
match self.state() {
|
||||
&AppStateImpl::NotLaunched { .. } | &AppStateImpl::Launching { .. } => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
|
||||
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
|
||||
AppStateImpl::NotLaunched {
|
||||
queued_windows,
|
||||
queued_events,
|
||||
queued_gpu_redraws,
|
||||
} => (queued_windows, queued_events, queued_gpu_redraws),
|
||||
s => bug!("unexpected state {:?}", s),
|
||||
};
|
||||
self.set_state(AppStateImpl::Launching {
|
||||
queued_windows,
|
||||
queued_events,
|
||||
queued_event_handler,
|
||||
queued_gpu_redraws,
|
||||
});
|
||||
}
|
||||
|
||||
fn did_finish_launching_transition(&mut self) -> (Vec<id>, Vec<Event<Never>>) {
|
||||
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
|
||||
AppStateImpl::Launching {
|
||||
queued_windows,
|
||||
queued_events,
|
||||
queued_event_handler,
|
||||
queued_gpu_redraws,
|
||||
} => (
|
||||
queued_windows,
|
||||
queued_events,
|
||||
queued_event_handler,
|
||||
queued_gpu_redraws,
|
||||
),
|
||||
s => bug!("unexpected state {:?}", s),
|
||||
};
|
||||
self.set_state(AppStateImpl::ProcessingEvents {
|
||||
event_handler,
|
||||
active_control_flow: ControlFlow::Poll,
|
||||
queued_gpu_redraws,
|
||||
});
|
||||
(windows, events)
|
||||
}
|
||||
|
||||
fn wakeup_transition(&mut self) -> Option<Event<Never>> {
|
||||
// before `AppState::did_finish_launching` is called, pretend there is no running
|
||||
// event loop.
|
||||
if !self.has_launched() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (event_handler, event) = match (self.control_flow, self.take_state()) {
|
||||
(
|
||||
ControlFlow::Poll,
|
||||
AppStateImpl::PollFinished {
|
||||
waiting_event_handler,
|
||||
},
|
||||
) => (waiting_event_handler, Event::NewEvents(StartCause::Poll)),
|
||||
(
|
||||
ControlFlow::Wait,
|
||||
AppStateImpl::Waiting {
|
||||
waiting_event_handler,
|
||||
start,
|
||||
},
|
||||
) => (
|
||||
waiting_event_handler,
|
||||
Event::NewEvents(StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
}),
|
||||
),
|
||||
(
|
||||
ControlFlow::WaitUntil(requested_resume),
|
||||
AppStateImpl::Waiting {
|
||||
waiting_event_handler,
|
||||
start,
|
||||
},
|
||||
) => {
|
||||
let event = if Instant::now() >= requested_resume {
|
||||
Event::NewEvents(StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume,
|
||||
})
|
||||
} else {
|
||||
Event::NewEvents(StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(requested_resume),
|
||||
})
|
||||
};
|
||||
(waiting_event_handler, event)
|
||||
}
|
||||
(ControlFlow::Exit, _) => bug!("unexpected `ControlFlow` `Exit`"),
|
||||
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
|
||||
};
|
||||
|
||||
self.set_state(AppStateImpl::ProcessingEvents {
|
||||
event_handler,
|
||||
queued_gpu_redraws: Default::default(),
|
||||
active_control_flow: self.control_flow,
|
||||
});
|
||||
Some(event)
|
||||
}
|
||||
|
||||
fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> {
|
||||
// If we're not able to process an event due to recursion or `Init` not having been sent out
|
||||
// yet, then queue the events up.
|
||||
match self.state_mut() {
|
||||
&mut AppStateImpl::Launching {
|
||||
ref mut queued_events,
|
||||
..
|
||||
}
|
||||
| &mut AppStateImpl::NotLaunched {
|
||||
ref mut queued_events,
|
||||
..
|
||||
}
|
||||
| &mut AppStateImpl::InUserCallback {
|
||||
ref mut queued_events,
|
||||
..
|
||||
} => {
|
||||
// A lifetime cast: early returns are not currently handled well with NLL, but
|
||||
// polonius handles them well. This transmute is a safe workaround.
|
||||
return unsafe {
|
||||
mem::transmute::<
|
||||
UserCallbackTransitionResult<'_>,
|
||||
UserCallbackTransitionResult<'_>,
|
||||
>(UserCallbackTransitionResult::ReentrancyPrevented {
|
||||
queued_events,
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
&mut AppStateImpl::ProcessingEvents { .. }
|
||||
| &mut AppStateImpl::ProcessingRedraws { .. } => {}
|
||||
|
||||
s @ &mut AppStateImpl::PollFinished { .. }
|
||||
| s @ &mut AppStateImpl::Waiting { .. }
|
||||
| s @ &mut AppStateImpl::Terminated => {
|
||||
bug!("unexpected attempted to process an event {:?}", s)
|
||||
}
|
||||
}
|
||||
|
||||
let (event_handler, queued_gpu_redraws, active_control_flow, processing_redraws) =
|
||||
match self.take_state() {
|
||||
AppStateImpl::Launching { .. }
|
||||
| AppStateImpl::NotLaunched { .. }
|
||||
| AppStateImpl::InUserCallback { .. } => unreachable!(),
|
||||
AppStateImpl::ProcessingEvents {
|
||||
event_handler,
|
||||
queued_gpu_redraws,
|
||||
active_control_flow,
|
||||
} => (
|
||||
event_handler,
|
||||
queued_gpu_redraws,
|
||||
active_control_flow,
|
||||
false,
|
||||
),
|
||||
AppStateImpl::ProcessingRedraws {
|
||||
event_handler,
|
||||
active_control_flow,
|
||||
} => (event_handler, Default::default(), active_control_flow, true),
|
||||
AppStateImpl::PollFinished { .. }
|
||||
| AppStateImpl::Waiting { .. }
|
||||
| AppStateImpl::Terminated => unreachable!(),
|
||||
};
|
||||
self.set_state(AppStateImpl::InUserCallback {
|
||||
queued_events: Vec::new(),
|
||||
queued_gpu_redraws,
|
||||
});
|
||||
UserCallbackTransitionResult::Success {
|
||||
event_handler,
|
||||
active_control_flow,
|
||||
processing_redraws,
|
||||
}
|
||||
}
|
||||
|
||||
fn main_events_cleared_transition(&mut self) -> HashSet<id> {
|
||||
let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
|
||||
AppStateImpl::ProcessingEvents {
|
||||
event_handler,
|
||||
queued_gpu_redraws,
|
||||
active_control_flow,
|
||||
} => (event_handler, queued_gpu_redraws, active_control_flow),
|
||||
s => bug!("unexpected state {:?}", s),
|
||||
};
|
||||
self.set_state(AppStateImpl::ProcessingRedraws {
|
||||
event_handler,
|
||||
active_control_flow,
|
||||
});
|
||||
queued_gpu_redraws
|
||||
}
|
||||
|
||||
fn events_cleared_transition(&mut self) {
|
||||
if !self.has_launched() {
|
||||
return;
|
||||
}
|
||||
let (waiting_event_handler, old) = match self.take_state() {
|
||||
AppStateImpl::ProcessingRedraws {
|
||||
event_handler,
|
||||
active_control_flow,
|
||||
} => (event_handler, active_control_flow),
|
||||
s => bug!("unexpected state {:?}", s),
|
||||
};
|
||||
|
||||
let new = self.control_flow;
|
||||
match (old, new) {
|
||||
(ControlFlow::Poll, ControlFlow::Poll) => self.set_state(AppStateImpl::PollFinished {
|
||||
waiting_event_handler,
|
||||
}),
|
||||
(ControlFlow::Wait, ControlFlow::Wait) => {
|
||||
let start = Instant::now();
|
||||
self.set_state(AppStateImpl::Waiting {
|
||||
waiting_event_handler,
|
||||
start,
|
||||
});
|
||||
}
|
||||
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
|
||||
if old_instant == new_instant =>
|
||||
{
|
||||
let start = Instant::now();
|
||||
self.set_state(AppStateImpl::Waiting {
|
||||
waiting_event_handler,
|
||||
start,
|
||||
});
|
||||
}
|
||||
(_, ControlFlow::Wait) => {
|
||||
let start = Instant::now();
|
||||
self.set_state(AppStateImpl::Waiting {
|
||||
waiting_event_handler,
|
||||
start,
|
||||
});
|
||||
self.waker.stop()
|
||||
}
|
||||
(_, ControlFlow::WaitUntil(new_instant)) => {
|
||||
let start = Instant::now();
|
||||
self.set_state(AppStateImpl::Waiting {
|
||||
waiting_event_handler,
|
||||
start,
|
||||
});
|
||||
self.waker.start_at(new_instant)
|
||||
}
|
||||
(_, ControlFlow::Poll) => {
|
||||
self.set_state(AppStateImpl::PollFinished {
|
||||
waiting_event_handler,
|
||||
});
|
||||
self.waker.start()
|
||||
}
|
||||
(_, ControlFlow::Exit) => {
|
||||
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
|
||||
// it is not possible to quit an iOS app gracefully and programatically
|
||||
warn!("`ControlFlow::Exit` ignored on iOS");
|
||||
self.control_flow = old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn terminated_transition(&mut self) -> Box<dyn EventHandler> {
|
||||
match self.replace_state(AppStateImpl::Terminated) {
|
||||
AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler,
|
||||
s => bug!(
|
||||
"`LoopDestroyed` happened while not processing events {:?}",
|
||||
s
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread and window is a UIWindow
|
||||
// retains window
|
||||
pub unsafe fn set_key_window(window: id) {
|
||||
bug_assert!(
|
||||
{
|
||||
let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)];
|
||||
is_window == YES
|
||||
},
|
||||
"set_key_window called with an incorrect type"
|
||||
);
|
||||
let mut this = AppState::get_mut();
|
||||
match this.state_mut() {
|
||||
&mut AppStateImpl::NotLaunched {
|
||||
ref mut queued_windows,
|
||||
..
|
||||
} => return queued_windows.push(msg_send![window, retain]),
|
||||
&mut AppStateImpl::ProcessingEvents { .. }
|
||||
| &mut AppStateImpl::InUserCallback { .. }
|
||||
| &mut AppStateImpl::ProcessingRedraws { .. } => {}
|
||||
s @ &mut AppStateImpl::Launching { .. }
|
||||
| s @ &mut AppStateImpl::Waiting { .. }
|
||||
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
|
||||
&mut AppStateImpl::Terminated => {
|
||||
panic!("Attempt to create a `Window` after the app has terminated")
|
||||
}
|
||||
}
|
||||
drop(this);
|
||||
msg_send![window, makeKeyAndVisible]
|
||||
}
|
||||
|
||||
// requires main thread and window is a UIWindow
|
||||
// retains window
|
||||
pub unsafe fn queue_gl_or_metal_redraw(window: id) {
|
||||
bug_assert!(
|
||||
{
|
||||
let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)];
|
||||
is_window == YES
|
||||
},
|
||||
"set_key_window called with an incorrect type"
|
||||
);
|
||||
let mut this = AppState::get_mut();
|
||||
match this.state_mut() {
|
||||
&mut AppStateImpl::NotLaunched {
|
||||
ref mut queued_gpu_redraws,
|
||||
..
|
||||
}
|
||||
| &mut AppStateImpl::Launching {
|
||||
ref mut queued_gpu_redraws,
|
||||
..
|
||||
}
|
||||
| &mut AppStateImpl::ProcessingEvents {
|
||||
ref mut queued_gpu_redraws,
|
||||
..
|
||||
}
|
||||
| &mut AppStateImpl::InUserCallback {
|
||||
ref mut queued_gpu_redraws,
|
||||
..
|
||||
} => drop(queued_gpu_redraws.insert(window)),
|
||||
s @ &mut AppStateImpl::ProcessingRedraws { .. }
|
||||
| s @ &mut AppStateImpl::Waiting { .. }
|
||||
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
|
||||
&mut AppStateImpl::Terminated => {
|
||||
panic!("Attempt to create a `Window` after the app has terminated")
|
||||
}
|
||||
}
|
||||
drop(this);
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn will_launch(queued_event_handler: Box<dyn EventHandler>) {
|
||||
AppState::get_mut().will_launch_transition(queued_event_handler)
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn did_finish_launching() {
|
||||
let mut this = AppState::get_mut();
|
||||
let windows = match this.state_mut() {
|
||||
AppStateImpl::Launching { queued_windows, .. } => mem::replace(queued_windows, Vec::new()),
|
||||
s => bug!("unexpected state {:?}", s),
|
||||
};
|
||||
|
||||
// start waking up the event loop now!
|
||||
bug_assert!(
|
||||
this.control_flow == ControlFlow::Poll,
|
||||
"unexpectedly not setup to `Poll` on launch!"
|
||||
);
|
||||
this.waker.start();
|
||||
|
||||
// have to drop RefMut because the window setup code below can trigger new events
|
||||
drop(this);
|
||||
|
||||
for window in windows {
|
||||
let count: NSUInteger = msg_send![window, retainCount];
|
||||
// make sure the window is still referenced
|
||||
if count > 1 {
|
||||
// Do a little screen dance here to account for windows being created before
|
||||
// `UIApplicationMain` is called. This fixes visual issues such as being
|
||||
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we
|
||||
// gotta reset the `rootViewController`.
|
||||
//
|
||||
// relevant iOS log:
|
||||
// ```
|
||||
// [ApplicationLifecycle] Windows were created before application initialzation
|
||||
// completed. This may result in incorrect visual appearance.
|
||||
// ```
|
||||
let screen: id = msg_send![window, screen];
|
||||
let _: id = msg_send![screen, retain];
|
||||
let () = msg_send![window, setScreen:0 as id];
|
||||
let () = msg_send![window, setScreen: screen];
|
||||
let () = msg_send![screen, release];
|
||||
let controller: id = msg_send![window, rootViewController];
|
||||
let () = msg_send![window, setRootViewController:ptr::null::<()>()];
|
||||
let () = msg_send![window, setRootViewController: controller];
|
||||
let () = msg_send![window, makeKeyAndVisible];
|
||||
}
|
||||
let () = msg_send![window, release];
|
||||
}
|
||||
|
||||
let (windows, events) = AppState::get_mut().did_finish_launching_transition();
|
||||
|
||||
let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events);
|
||||
handle_nonuser_events(events);
|
||||
|
||||
// the above window dance hack, could possibly trigger new windows to be created.
|
||||
// we can just set those windows up normally, as they were created after didFinishLaunching
|
||||
for window in windows {
|
||||
let count: NSUInteger = msg_send![window, retainCount];
|
||||
// make sure the window is still referenced
|
||||
if count > 1 {
|
||||
let () = msg_send![window, makeKeyAndVisible];
|
||||
}
|
||||
let () = msg_send![window, release];
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
// AppState::did_finish_launching handles the special transition `Init`
|
||||
pub unsafe fn handle_wakeup_transition() {
|
||||
let mut this = AppState::get_mut();
|
||||
let wakeup_event = match this.wakeup_transition() {
|
||||
None => return,
|
||||
Some(wakeup_event) => wakeup_event,
|
||||
};
|
||||
drop(this);
|
||||
|
||||
handle_nonuser_event(wakeup_event)
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn handle_nonuser_event(event: Event<Never>) {
|
||||
handle_nonuser_events(std::iter::once(event))
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = Event<Never>>>(events: I) {
|
||||
let mut this = AppState::get_mut();
|
||||
let (mut event_handler, active_control_flow, processing_redraws) =
|
||||
match this.try_user_callback_transition() {
|
||||
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
|
||||
queued_events.extend(events);
|
||||
return;
|
||||
}
|
||||
UserCallbackTransitionResult::Success {
|
||||
event_handler,
|
||||
active_control_flow,
|
||||
processing_redraws,
|
||||
} => (event_handler, active_control_flow, processing_redraws),
|
||||
};
|
||||
let mut control_flow = this.control_flow;
|
||||
drop(this);
|
||||
|
||||
for event in events {
|
||||
if !processing_redraws && event.is_redraw() {
|
||||
log::info!("processing `RedrawRequested` during the main event loop");
|
||||
} else if processing_redraws && !event.is_redraw() {
|
||||
log::warn!(
|
||||
"processing non `RedrawRequested` event after the main event loop: {:#?}",
|
||||
event
|
||||
);
|
||||
}
|
||||
event_handler.handle_nonuser_event(event, &mut control_flow)
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut this = AppState::get_mut();
|
||||
let queued_events = match this.state_mut() {
|
||||
&mut AppStateImpl::InUserCallback {
|
||||
ref mut queued_events,
|
||||
queued_gpu_redraws: _,
|
||||
} => mem::replace(queued_events, Vec::new()),
|
||||
s => bug!("unexpected state {:?}", s),
|
||||
};
|
||||
if queued_events.is_empty() {
|
||||
let queued_gpu_redraws = match this.take_state() {
|
||||
AppStateImpl::InUserCallback {
|
||||
queued_events: _,
|
||||
queued_gpu_redraws,
|
||||
} => queued_gpu_redraws,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
this.app_state = Some(if processing_redraws {
|
||||
bug_assert!(
|
||||
queued_gpu_redraws.is_empty(),
|
||||
"redraw queued while processing redraws"
|
||||
);
|
||||
AppStateImpl::ProcessingRedraws {
|
||||
event_handler,
|
||||
active_control_flow,
|
||||
}
|
||||
} else {
|
||||
AppStateImpl::ProcessingEvents {
|
||||
event_handler,
|
||||
queued_gpu_redraws,
|
||||
active_control_flow,
|
||||
}
|
||||
});
|
||||
this.control_flow = control_flow;
|
||||
break;
|
||||
}
|
||||
drop(this);
|
||||
|
||||
for event in queued_events {
|
||||
if !processing_redraws && event.is_redraw() {
|
||||
log::info!("processing `RedrawRequested` during the main event loop");
|
||||
} else if processing_redraws && !event.is_redraw() {
|
||||
log::warn!(
|
||||
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
|
||||
event
|
||||
);
|
||||
}
|
||||
event_handler.handle_nonuser_event(event, &mut control_flow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn handle_user_events() {
|
||||
let mut this = AppState::get_mut();
|
||||
let mut control_flow = this.control_flow;
|
||||
let (mut event_handler, active_control_flow, processing_redraws) =
|
||||
match this.try_user_callback_transition() {
|
||||
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
|
||||
bug!("unexpected attempted to process an event")
|
||||
}
|
||||
UserCallbackTransitionResult::Success {
|
||||
event_handler,
|
||||
active_control_flow,
|
||||
processing_redraws,
|
||||
} => (event_handler, active_control_flow, processing_redraws),
|
||||
};
|
||||
if processing_redraws {
|
||||
bug!("user events attempted to be sent out while `ProcessingRedraws`");
|
||||
}
|
||||
drop(this);
|
||||
|
||||
event_handler.handle_user_events(&mut control_flow);
|
||||
|
||||
loop {
|
||||
let mut this = AppState::get_mut();
|
||||
let queued_events = match this.state_mut() {
|
||||
&mut AppStateImpl::InUserCallback {
|
||||
ref mut queued_events,
|
||||
queued_gpu_redraws: _,
|
||||
} => mem::replace(queued_events, Vec::new()),
|
||||
s => bug!("unexpected state {:?}", s),
|
||||
};
|
||||
if queued_events.is_empty() {
|
||||
let queued_gpu_redraws = match this.take_state() {
|
||||
AppStateImpl::InUserCallback {
|
||||
queued_events: _,
|
||||
queued_gpu_redraws,
|
||||
} => queued_gpu_redraws,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
this.app_state = Some(AppStateImpl::ProcessingEvents {
|
||||
event_handler,
|
||||
queued_gpu_redraws,
|
||||
active_control_flow,
|
||||
});
|
||||
this.control_flow = control_flow;
|
||||
break;
|
||||
}
|
||||
drop(this);
|
||||
|
||||
for event in queued_events {
|
||||
event_handler.handle_nonuser_event(event, &mut control_flow)
|
||||
}
|
||||
event_handler.handle_user_events(&mut control_flow);
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn handle_main_events_cleared() {
|
||||
let mut this = AppState::get_mut();
|
||||
if !this.has_launched() {
|
||||
return;
|
||||
}
|
||||
match this.state_mut() {
|
||||
&mut AppStateImpl::ProcessingEvents { .. } => {}
|
||||
_ => bug!("`ProcessingRedraws` happened unexpectedly"),
|
||||
};
|
||||
drop(this);
|
||||
|
||||
// User events are always sent out at the end of the "MainEventLoop"
|
||||
handle_user_events();
|
||||
handle_nonuser_event(Event::EventsCleared);
|
||||
|
||||
let mut this = AppState::get_mut();
|
||||
let redraw_events = this
|
||||
.main_events_cleared_transition()
|
||||
.into_iter()
|
||||
.map(|window| Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
});
|
||||
drop(this);
|
||||
|
||||
handle_nonuser_events(redraw_events);
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn handle_events_cleared() {
|
||||
AppState::get_mut().events_cleared_transition();
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn terminated() {
|
||||
let mut this = AppState::get_mut();
|
||||
let mut event_handler = this.terminated_transition();
|
||||
let mut control_flow = this.control_flow;
|
||||
drop(this);
|
||||
|
||||
event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow)
|
||||
}
|
||||
|
||||
struct EventLoopWaker {
|
||||
timer: CFRunLoopTimerRef,
|
||||
}
|
||||
|
||||
impl Drop for EventLoopWaker {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRunLoopTimerInvalidate(self.timer);
|
||||
CFRelease(self.timer as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker {
|
||||
fn new(rl: CFRunLoopRef) -> EventLoopWaker {
|
||||
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
||||
unsafe {
|
||||
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
|
||||
// It is initially setup with a first fire time really far into the
|
||||
// future, but that gets changed to fire immediately in did_finish_launching
|
||||
let timer = CFRunLoopTimerCreate(
|
||||
ptr::null_mut(),
|
||||
std::f64::MAX,
|
||||
0.000_000_1,
|
||||
0,
|
||||
0,
|
||||
wakeup_main_loop,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
|
||||
|
||||
EventLoopWaker { timer }
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
|
||||
}
|
||||
|
||||
fn start(&mut self) {
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
|
||||
}
|
||||
|
||||
fn start_at(&mut self, instant: Instant) {
|
||||
let now = Instant::now();
|
||||
if now >= instant {
|
||||
self.start();
|
||||
} else {
|
||||
unsafe {
|
||||
let current = CFAbsoluteTimeGetCurrent();
|
||||
let duration = instant - now;
|
||||
let fsecs =
|
||||
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
|
||||
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! os_capabilities {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$error_name:ident: $objc_call:literal,
|
||||
$name:ident: $major:literal-$minor:literal
|
||||
),*
|
||||
$(,)*
|
||||
) => {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OSCapabilities {
|
||||
$(
|
||||
pub $name: bool,
|
||||
)*
|
||||
|
||||
os_version: NSOperatingSystemVersion,
|
||||
}
|
||||
|
||||
impl From<NSOperatingSystemVersion> for OSCapabilities {
|
||||
fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities {
|
||||
$(let $name = os_version.meets_requirements($major, $minor);)*
|
||||
OSCapabilities { $($name,)* os_version, }
|
||||
}
|
||||
}
|
||||
|
||||
impl OSCapabilities {$(
|
||||
$(#[$attr])*
|
||||
pub fn $error_name(&self, extra_msg: &str) {
|
||||
log::warn!(
|
||||
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
|
||||
$major, $minor, self.os_version.major, self.os_version.minor, self.os_version.patch,
|
||||
extra_msg
|
||||
)
|
||||
}
|
||||
)*}
|
||||
};
|
||||
}
|
||||
|
||||
os_capabilities! {
|
||||
/// https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
|
||||
#[allow(unused)] // error message unused
|
||||
safe_area_err_msg: "-[UIView safeAreaInsets]",
|
||||
safe_area: 11-0,
|
||||
/// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc
|
||||
home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
|
||||
home_indicator_hidden: 11-0,
|
||||
/// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc
|
||||
defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
|
||||
defer_system_gestures: 11-0,
|
||||
/// https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc
|
||||
maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
|
||||
maximum_frames_per_second: 10-3,
|
||||
/// https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc
|
||||
#[allow(unused)] // error message unused
|
||||
force_touch_err_msg: "-[UITouch force]",
|
||||
force_touch: 9-0,
|
||||
}
|
||||
|
||||
impl NSOperatingSystemVersion {
|
||||
fn meets_requirements(&self, required_major: NSInteger, required_minor: NSInteger) -> bool {
|
||||
(self.major, self.minor) >= (required_major, required_minor)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn os_capabilities() -> OSCapabilities {
|
||||
lazy_static! {
|
||||
static ref OS_CAPABILITIES: OSCapabilities = {
|
||||
let version: NSOperatingSystemVersion = unsafe {
|
||||
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
|
||||
let atleast_ios_8: BOOL = msg_send![
|
||||
process_info,
|
||||
respondsToSelector: sel!(operatingSystemVersion)
|
||||
];
|
||||
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
|
||||
// Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
|
||||
// debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
|
||||
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
|
||||
// has been tested to not even run on macOS 10.15 - Xcode 8 might?
|
||||
//
|
||||
// The minimum required iOS version is likely to grow in the future.
|
||||
assert!(
|
||||
atleast_ios_8 == YES,
|
||||
"`winit` requires iOS version 8 or greater"
|
||||
);
|
||||
msg_send![process_info, operatingSystemVersion]
|
||||
};
|
||||
version.into()
|
||||
};
|
||||
}
|
||||
OS_CAPABILITIES.clone()
|
||||
}
|
||||
322
src/platform_impl/ios/event_loop.rs
Normal file
322
src/platform_impl/ios/event_loop.rs
Normal file
@@ -0,0 +1,322 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
ffi::c_void,
|
||||
fmt::{self, Debug},
|
||||
marker::PhantomData,
|
||||
mem, ptr,
|
||||
sync::mpsc::{self, Receiver, Sender},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
event::Event,
|
||||
event_loop::{
|
||||
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
|
||||
},
|
||||
platform::ios::Idiom,
|
||||
};
|
||||
|
||||
use crate::platform_impl::platform::{
|
||||
app_state,
|
||||
ffi::{
|
||||
id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes,
|
||||
kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease,
|
||||
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
|
||||
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext,
|
||||
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
|
||||
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSString, UIApplicationMain, UIUserInterfaceIdiom,
|
||||
},
|
||||
monitor, view, MonitorHandle,
|
||||
};
|
||||
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
receiver: Receiver<T>,
|
||||
sender_to_clone: Sender<T>,
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
window_target: RootEventLoopWindowTarget<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> EventLoop<T> {
|
||||
static mut SINGLETON_INIT: bool = false;
|
||||
unsafe {
|
||||
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
|
||||
assert!(
|
||||
!SINGLETON_INIT,
|
||||
"Only one `EventLoop` is supported on iOS. \
|
||||
`EventLoopProxy` might be helpful"
|
||||
);
|
||||
SINGLETON_INIT = true;
|
||||
view::create_delegate_class();
|
||||
}
|
||||
|
||||
let (sender_to_clone, receiver) = mpsc::channel();
|
||||
|
||||
// this line sets up the main run loop before `UIApplicationMain`
|
||||
setup_control_flow_observers();
|
||||
|
||||
EventLoop {
|
||||
window_target: RootEventLoopWindowTarget {
|
||||
p: EventLoopWindowTarget {
|
||||
receiver,
|
||||
sender_to_clone,
|
||||
},
|
||||
_marker: PhantomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run<F>(self, event_handler: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
unsafe {
|
||||
let application: *mut c_void = msg_send![class!(UIApplication), sharedApplication];
|
||||
assert_eq!(
|
||||
application,
|
||||
ptr::null_mut(),
|
||||
"\
|
||||
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
|
||||
Note: `EventLoop::run` calls `UIApplicationMain` on iOS"
|
||||
);
|
||||
app_state::will_launch(Box::new(EventLoopHandler {
|
||||
f: event_handler,
|
||||
event_loop: self.window_target,
|
||||
}));
|
||||
|
||||
UIApplicationMain(
|
||||
0,
|
||||
ptr::null(),
|
||||
nil,
|
||||
NSString::alloc(nil).init_str("AppDelegate"),
|
||||
);
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
// guaranteed to be on main thread
|
||||
unsafe { monitor::uiscreens() }
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
// guaranteed to be on main thread
|
||||
unsafe { monitor::main_uiscreen() }
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
|
||||
&self.window_target
|
||||
}
|
||||
}
|
||||
|
||||
// EventLoopExtIOS
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn idiom(&self) -> Idiom {
|
||||
// guaranteed to be on main thread
|
||||
unsafe { self::get_idiom() }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopProxy<T> {
|
||||
sender: Sender<T>,
|
||||
source: CFRunLoopSourceRef,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
|
||||
|
||||
impl<T> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy::new(self.sender.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for EventLoopProxy<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRunLoopSourceInvalidate(self.source);
|
||||
CFRelease(self.source as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopProxy<T> {
|
||||
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
|
||||
unsafe {
|
||||
// just wake up the eventloop
|
||||
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
|
||||
|
||||
// adding a Source to the main CFRunLoop lets us wake it up and
|
||||
// process user events through the normal OS EventLoop mechanisms.
|
||||
let rl = CFRunLoopGetMain();
|
||||
// we want all the members of context to be zero/null, except one
|
||||
let mut context: CFRunLoopSourceContext = mem::zeroed();
|
||||
context.perform = Some(event_loop_proxy_handler);
|
||||
let source =
|
||||
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(rl);
|
||||
|
||||
EventLoopProxy { sender, source }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
self.sender
|
||||
.send(event)
|
||||
.map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
|
||||
unsafe {
|
||||
// let the main thread know there's a new event
|
||||
CFRunLoopSourceSignal(self.source);
|
||||
let rl = CFRunLoopGetMain();
|
||||
CFRunLoopWakeUp(rl);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_control_flow_observers() {
|
||||
unsafe {
|
||||
// begin is queued with the highest priority to ensure it is processed before other observers
|
||||
extern "C" fn control_flow_begin_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
|
||||
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
||||
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
|
||||
// priority to be 0, in order to send EventsCleared 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 `EventsCleared`.
|
||||
//
|
||||
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
|
||||
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
||||
//
|
||||
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
|
||||
extern "C" fn control_flow_main_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(),
|
||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||
extern "C" fn control_flow_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
|
||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let main_loop = CFRunLoopGetMain();
|
||||
|
||||
let begin_observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
|
||||
1, // repeat = true
|
||||
CFIndex::min_value(),
|
||||
control_flow_begin_handler,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
|
||||
|
||||
let main_end_observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
1, // repeat = true
|
||||
0, // see comment on `control_flow_main_end_handler`
|
||||
control_flow_main_end_handler,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
|
||||
|
||||
let end_observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
1, // repeat = true
|
||||
CFIndex::max_value(),
|
||||
control_flow_end_handler,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Never {}
|
||||
|
||||
pub trait EventHandler: Debug {
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
|
||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
|
||||
}
|
||||
|
||||
struct EventLoopHandler<F, T: 'static> {
|
||||
f: F,
|
||||
event_loop: RootEventLoopWindowTarget<T>,
|
||||
}
|
||||
|
||||
impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("EventLoopHandler")
|
||||
.field("event_loop", &self.event_loop)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T> EventHandler for EventLoopHandler<F, T>
|
||||
where
|
||||
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
T: 'static,
|
||||
{
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
|
||||
(self.f)(
|
||||
event.map_nonuser_event().unwrap(),
|
||||
&self.event_loop,
|
||||
control_flow,
|
||||
);
|
||||
}
|
||||
|
||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
|
||||
for event in self.event_loop.p.receiver.try_iter() {
|
||||
(self.f)(Event::UserEvent(event), &self.event_loop, control_flow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// must be called on main thread
|
||||
pub unsafe fn get_idiom() -> Idiom {
|
||||
let device: id = msg_send![class!(UIDevice), currentDevice];
|
||||
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
|
||||
raw_idiom.into()
|
||||
}
|
||||
372
src/platform_impl/ios/ffi.rs
Normal file
372
src/platform_impl/ios/ffi.rs
Normal file
@@ -0,0 +1,372 @@
|
||||
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||
|
||||
use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
|
||||
|
||||
use objc::{runtime::Object, Encode, Encoding};
|
||||
|
||||
use crate::platform::ios::{Idiom, ScreenEdge, ValidOrientations};
|
||||
|
||||
pub type id = *mut Object;
|
||||
pub const nil: id = 0 as id;
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
pub type CGFloat = f32;
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub type CGFloat = f64;
|
||||
|
||||
pub type NSInteger = isize;
|
||||
pub type NSUInteger = usize;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NSOperatingSystemVersion {
|
||||
pub major: NSInteger,
|
||||
pub minor: NSInteger,
|
||||
pub patch: NSInteger,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGPoint {
|
||||
pub x: CGFloat,
|
||||
pub y: CGFloat,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGSize {
|
||||
pub width: CGFloat,
|
||||
pub height: CGFloat,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGRect {
|
||||
pub origin: CGPoint,
|
||||
pub size: CGSize,
|
||||
}
|
||||
|
||||
unsafe impl Encode for CGRect {
|
||||
fn encode() -> Encoding {
|
||||
unsafe {
|
||||
if cfg!(target_pointer_width = "32") {
|
||||
Encoding::from_str("{CGRect={CGPoint=ff}{CGSize=ff}}")
|
||||
} else if cfg!(target_pointer_width = "64") {
|
||||
Encoding::from_str("{CGRect={CGPoint=dd}{CGSize=dd}}")
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UITouchPhase {
|
||||
Began = 0,
|
||||
Moved,
|
||||
Stationary,
|
||||
Ended,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UIForceTouchCapability {
|
||||
Unknown = 0,
|
||||
Unavailable,
|
||||
Available,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UITouchType {
|
||||
Direct = 0,
|
||||
Indirect,
|
||||
Pencil,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UIEdgeInsets {
|
||||
pub top: CGFloat,
|
||||
pub left: CGFloat,
|
||||
pub bottom: CGFloat,
|
||||
pub right: CGFloat,
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIUserInterfaceIdiom(NSInteger);
|
||||
|
||||
unsafe impl Encode for UIUserInterfaceIdiom {
|
||||
fn encode() -> Encoding {
|
||||
NSInteger::encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl UIUserInterfaceIdiom {
|
||||
pub const Unspecified: UIUserInterfaceIdiom = UIUserInterfaceIdiom(-1);
|
||||
pub const Phone: UIUserInterfaceIdiom = UIUserInterfaceIdiom(0);
|
||||
pub const Pad: UIUserInterfaceIdiom = UIUserInterfaceIdiom(1);
|
||||
pub const TV: UIUserInterfaceIdiom = UIUserInterfaceIdiom(2);
|
||||
pub const CarPlay: UIUserInterfaceIdiom = UIUserInterfaceIdiom(3);
|
||||
}
|
||||
|
||||
impl From<Idiom> for UIUserInterfaceIdiom {
|
||||
fn from(idiom: Idiom) -> UIUserInterfaceIdiom {
|
||||
match idiom {
|
||||
Idiom::Unspecified => UIUserInterfaceIdiom::Unspecified,
|
||||
Idiom::Phone => UIUserInterfaceIdiom::Phone,
|
||||
Idiom::Pad => UIUserInterfaceIdiom::Pad,
|
||||
Idiom::TV => UIUserInterfaceIdiom::TV,
|
||||
Idiom::CarPlay => UIUserInterfaceIdiom::CarPlay,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Idiom> for UIUserInterfaceIdiom {
|
||||
fn into(self) -> Idiom {
|
||||
match self {
|
||||
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
|
||||
UIUserInterfaceIdiom::Phone => Idiom::Phone,
|
||||
UIUserInterfaceIdiom::Pad => Idiom::Pad,
|
||||
UIUserInterfaceIdiom::TV => Idiom::TV,
|
||||
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UIInterfaceOrientationMask(NSUInteger);
|
||||
|
||||
unsafe impl Encode for UIInterfaceOrientationMask {
|
||||
fn encode() -> Encoding {
|
||||
NSUInteger::encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl UIInterfaceOrientationMask {
|
||||
pub const Portrait: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 1);
|
||||
pub const PortraitUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 2);
|
||||
pub const LandscapeLeft: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 4);
|
||||
pub const LandscapeRight: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 3);
|
||||
pub const Landscape: UIInterfaceOrientationMask =
|
||||
UIInterfaceOrientationMask(Self::LandscapeLeft.0 | Self::LandscapeRight.0);
|
||||
pub const AllButUpsideDown: UIInterfaceOrientationMask =
|
||||
UIInterfaceOrientationMask(Self::Landscape.0 | Self::Portrait.0);
|
||||
pub const All: UIInterfaceOrientationMask =
|
||||
UIInterfaceOrientationMask(Self::AllButUpsideDown.0 | Self::PortraitUpsideDown.0);
|
||||
}
|
||||
|
||||
impl BitOr for UIInterfaceOrientationMask {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
UIInterfaceOrientationMask(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl UIInterfaceOrientationMask {
|
||||
pub fn from_valid_orientations_idiom(
|
||||
valid_orientations: ValidOrientations,
|
||||
idiom: Idiom,
|
||||
) -> UIInterfaceOrientationMask {
|
||||
match (valid_orientations, idiom) {
|
||||
(ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => {
|
||||
UIInterfaceOrientationMask::AllButUpsideDown
|
||||
}
|
||||
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
|
||||
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
|
||||
(ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait,
|
||||
(ValidOrientations::Portrait, _) => {
|
||||
UIInterfaceOrientationMask::Portrait
|
||||
| UIInterfaceOrientationMask::PortraitUpsideDown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIRectEdge(NSUInteger);
|
||||
|
||||
unsafe impl Encode for UIRectEdge {
|
||||
fn encode() -> Encoding {
|
||||
NSUInteger::encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScreenEdge> for UIRectEdge {
|
||||
fn from(screen_edge: ScreenEdge) -> UIRectEdge {
|
||||
assert_eq!(
|
||||
screen_edge.bits() & !ScreenEdge::ALL.bits(),
|
||||
0,
|
||||
"invalid `ScreenEdge`"
|
||||
);
|
||||
UIRectEdge(screen_edge.bits().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ScreenEdge> for UIRectEdge {
|
||||
fn into(self) -> ScreenEdge {
|
||||
let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`");
|
||||
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIScreenOverscanCompensation(NSInteger);
|
||||
|
||||
unsafe impl Encode for UIScreenOverscanCompensation {
|
||||
fn encode() -> Encoding {
|
||||
NSInteger::encode()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl UIScreenOverscanCompensation {
|
||||
pub const Scale: UIScreenOverscanCompensation = UIScreenOverscanCompensation(0);
|
||||
pub const InsetBounds: UIScreenOverscanCompensation = UIScreenOverscanCompensation(1);
|
||||
pub const None: UIScreenOverscanCompensation = UIScreenOverscanCompensation(2);
|
||||
}
|
||||
|
||||
#[link(name = "UIKit", kind = "framework")]
|
||||
#[link(name = "CoreFoundation", kind = "framework")]
|
||||
extern "C" {
|
||||
pub static kCFRunLoopDefaultMode: CFRunLoopMode;
|
||||
pub static kCFRunLoopCommonModes: CFRunLoopMode;
|
||||
|
||||
pub fn UIApplicationMain(
|
||||
argc: c_int,
|
||||
argv: *const c_char,
|
||||
principalClassName: id,
|
||||
delegateClassName: id,
|
||||
) -> c_int;
|
||||
|
||||
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
|
||||
pub fn CFRunLoopWakeUp(rl: CFRunLoopRef);
|
||||
|
||||
pub fn CFRunLoopObserverCreate(
|
||||
allocator: CFAllocatorRef,
|
||||
activities: CFOptionFlags,
|
||||
repeats: Boolean,
|
||||
order: CFIndex,
|
||||
callout: CFRunLoopObserverCallBack,
|
||||
context: *mut CFRunLoopObserverContext,
|
||||
) -> CFRunLoopObserverRef;
|
||||
pub fn CFRunLoopAddObserver(
|
||||
rl: CFRunLoopRef,
|
||||
observer: CFRunLoopObserverRef,
|
||||
mode: CFRunLoopMode,
|
||||
);
|
||||
|
||||
pub fn CFRunLoopTimerCreate(
|
||||
allocator: CFAllocatorRef,
|
||||
fireDate: CFAbsoluteTime,
|
||||
interval: CFTimeInterval,
|
||||
flags: CFOptionFlags,
|
||||
order: CFIndex,
|
||||
callout: CFRunLoopTimerCallBack,
|
||||
context: *mut CFRunLoopTimerContext,
|
||||
) -> CFRunLoopTimerRef;
|
||||
pub fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFRunLoopMode);
|
||||
pub fn CFRunLoopTimerSetNextFireDate(timer: CFRunLoopTimerRef, fireDate: CFAbsoluteTime);
|
||||
pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef);
|
||||
|
||||
pub fn CFRunLoopSourceCreate(
|
||||
allocator: CFAllocatorRef,
|
||||
order: CFIndex,
|
||||
context: *mut CFRunLoopSourceContext,
|
||||
) -> CFRunLoopSourceRef;
|
||||
pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode);
|
||||
pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef);
|
||||
pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef);
|
||||
|
||||
pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime;
|
||||
pub fn CFRelease(cftype: *const c_void);
|
||||
}
|
||||
|
||||
pub type Boolean = u8;
|
||||
pub enum CFAllocator {}
|
||||
pub type CFAllocatorRef = *mut CFAllocator;
|
||||
pub enum CFRunLoop {}
|
||||
pub type CFRunLoopRef = *mut CFRunLoop;
|
||||
pub type CFRunLoopMode = CFStringRef;
|
||||
pub enum CFRunLoopObserver {}
|
||||
pub type CFRunLoopObserverRef = *mut CFRunLoopObserver;
|
||||
pub enum CFRunLoopTimer {}
|
||||
pub type CFRunLoopTimerRef = *mut CFRunLoopTimer;
|
||||
pub enum CFRunLoopSource {}
|
||||
pub type CFRunLoopSourceRef = *mut CFRunLoopSource;
|
||||
pub enum CFString {}
|
||||
pub type CFStringRef = *const CFString;
|
||||
|
||||
pub type CFHashCode = c_ulong;
|
||||
pub type CFIndex = c_long;
|
||||
pub type CFOptionFlags = c_ulong;
|
||||
pub type CFRunLoopActivity = CFOptionFlags;
|
||||
|
||||
pub type CFAbsoluteTime = CFTimeInterval;
|
||||
pub type CFTimeInterval = f64;
|
||||
|
||||
pub const kCFRunLoopEntry: CFRunLoopActivity = 0;
|
||||
pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5;
|
||||
pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6;
|
||||
pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7;
|
||||
|
||||
pub type CFRunLoopObserverCallBack =
|
||||
extern "C" fn(observer: CFRunLoopObserverRef, activity: CFRunLoopActivity, info: *mut c_void);
|
||||
pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void);
|
||||
|
||||
pub enum CFRunLoopObserverContext {}
|
||||
pub enum CFRunLoopTimerContext {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct CFRunLoopSourceContext {
|
||||
pub version: CFIndex,
|
||||
pub info: *mut c_void,
|
||||
pub retain: Option<extern "C" fn(*const c_void) -> *const c_void>,
|
||||
pub release: Option<extern "C" fn(*const c_void)>,
|
||||
pub copyDescription: Option<extern "C" fn(*const c_void) -> CFStringRef>,
|
||||
pub equal: Option<extern "C" fn(*const c_void, *const c_void) -> Boolean>,
|
||||
pub hash: Option<extern "C" fn(*const c_void) -> CFHashCode>,
|
||||
pub schedule: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
|
||||
pub cancel: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
|
||||
pub perform: Option<extern "C" fn(*mut c_void)>,
|
||||
}
|
||||
|
||||
pub trait NSString: Sized {
|
||||
unsafe fn alloc(_: Self) -> id {
|
||||
msg_send![class!(NSString), alloc]
|
||||
}
|
||||
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id;
|
||||
unsafe fn stringByAppendingString_(self, other: id) -> id;
|
||||
unsafe fn init_str(self, string: &str) -> Self;
|
||||
unsafe fn UTF8String(self) -> *const c_char;
|
||||
}
|
||||
|
||||
impl NSString for id {
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
|
||||
msg_send![self, initWithUTF8String: c_string as id]
|
||||
}
|
||||
|
||||
unsafe fn stringByAppendingString_(self, other: id) -> id {
|
||||
msg_send![self, stringByAppendingString: other]
|
||||
}
|
||||
|
||||
unsafe fn init_str(self, string: &str) -> id {
|
||||
let cstring = CString::new(string).unwrap();
|
||||
self.initWithUTF8String_(cstring.as_ptr())
|
||||
}
|
||||
|
||||
unsafe fn UTF8String(self) -> *const c_char {
|
||||
msg_send![self, UTF8String]
|
||||
}
|
||||
}
|
||||
111
src/platform_impl/ios/mod.rs
Normal file
111
src/platform_impl/ios/mod.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
//! iOS support
|
||||
//!
|
||||
//! # Building app
|
||||
//! To build ios app you will need rustc built for this targets:
|
||||
//!
|
||||
//! - armv7-apple-ios
|
||||
//! - armv7s-apple-ios
|
||||
//! - i386-apple-ios
|
||||
//! - aarch64-apple-ios
|
||||
//! - x86_64-apple-ios
|
||||
//!
|
||||
//! Then
|
||||
//!
|
||||
//! ```
|
||||
//! cargo build --target=...
|
||||
//! ```
|
||||
//! The simplest way to integrate your app into xcode environment is to build it
|
||||
//! as a static library. Wrap your main function and export it.
|
||||
//!
|
||||
//! ```rust, ignore
|
||||
//! #[no_mangle]
|
||||
//! pub extern fn start_winit_app() {
|
||||
//! start_inner()
|
||||
//! }
|
||||
//!
|
||||
//! fn start_inner() {
|
||||
//! ...
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! void start_winit_app();
|
||||
//! ```
|
||||
//!
|
||||
//! Use start_winit_app inside your xcode's main function.
|
||||
//!
|
||||
//!
|
||||
//! # App lifecycle and events
|
||||
//!
|
||||
//! iOS environment is very different from other platforms and you must be very
|
||||
//! careful with it's events. Familiarize yourself with
|
||||
//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/).
|
||||
//!
|
||||
//!
|
||||
//! This is how those event are represented in winit:
|
||||
//!
|
||||
//! - applicationDidBecomeActive is Resumed
|
||||
//! - applicationWillResignActive is Suspended
|
||||
//! - applicationWillTerminate is LoopDestroyed
|
||||
//!
|
||||
//! Keep in mind that after LoopDestroyed event is received every attempt to draw with
|
||||
//! opengl will result in segfault.
|
||||
//!
|
||||
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
|
||||
|
||||
#![cfg(target_os = "ios")]
|
||||
|
||||
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
|
||||
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
|
||||
// window size/position.
|
||||
macro_rules! assert_main_thread {
|
||||
($($t:tt)*) => {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
panic!($($t)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod app_state;
|
||||
mod event_loop;
|
||||
mod ffi;
|
||||
mod monitor;
|
||||
mod view;
|
||||
mod window;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
pub use self::{
|
||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId {
|
||||
uiscreen: ffi::id,
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId {
|
||||
uiscreen: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for DeviceId {}
|
||||
unsafe impl Sync for DeviceId {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OsError {}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
284
src/platform_impl/ios/monitor.rs
Normal file
284
src/platform_impl/ios/monitor.rs
Normal file
@@ -0,0 +1,284 @@
|
||||
use std::{
|
||||
collections::{BTreeSet, VecDeque},
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
platform_impl::platform::{
|
||||
app_state,
|
||||
ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: (u32, u32),
|
||||
pub(crate) bit_depth: u16,
|
||||
pub(crate) refresh_rate: u16,
|
||||
pub(crate) screen_mode: id,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
impl Clone for VideoMode {
|
||||
fn clone(&self) -> VideoMode {
|
||||
VideoMode {
|
||||
size: self.size,
|
||||
bit_depth: self.bit_depth,
|
||||
refresh_rate: self.refresh_rate,
|
||||
screen_mode: unsafe { msg_send![self.screen_mode, retain] },
|
||||
monitor: self.monitor.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VideoMode {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
assert_main_thread!("`VideoMode` can only be dropped on the main thread on iOS");
|
||||
let () = msg_send![self.screen_mode, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
|
||||
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second {
|
||||
msg_send![uiscreen, maximumFramesPerSecond]
|
||||
} else {
|
||||
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
|
||||
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
|
||||
//
|
||||
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
|
||||
// supported, they are all guaranteed to have 60hz refresh rates. This does not
|
||||
// correctly handle external displays. ProMotion displays support 120fps, but they were
|
||||
// introduced at the same time as the `maximumFramesPerSecond` API.
|
||||
//
|
||||
// FIXME: earlier OSs could calculate the refresh rate using
|
||||
// `-[CADisplayLink duration]`.
|
||||
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
|
||||
60
|
||||
};
|
||||
let size: CGSize = msg_send![screen_mode, size];
|
||||
VideoMode {
|
||||
size: (size.width as u32, size.height as u32),
|
||||
bit_depth: 32,
|
||||
refresh_rate: refresh_rate as u16,
|
||||
screen_mode: msg_send![screen_mode, retain],
|
||||
monitor: MonitorHandle::retained_new(uiscreen),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize {
|
||||
self.size.into()
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> u16 {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.refresh_rate
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> RootMonitorHandle {
|
||||
RootMonitorHandle {
|
||||
inner: self.monitor.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Inner {
|
||||
uiscreen: id,
|
||||
}
|
||||
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let () = msg_send![self.uiscreen, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct MonitorHandle {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
impl Deref for MonitorHandle {
|
||||
type Target = Inner;
|
||||
|
||||
fn deref(&self) -> &Inner {
|
||||
unsafe {
|
||||
assert_main_thread!(
|
||||
"`MonitorHandle` methods can only be run on the main thread on iOS"
|
||||
);
|
||||
}
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for MonitorHandle {
|
||||
fn deref_mut(&mut self) -> &mut Inner {
|
||||
unsafe {
|
||||
assert_main_thread!(
|
||||
"`MonitorHandle` methods can only be run on the main thread on iOS"
|
||||
);
|
||||
}
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for MonitorHandle {}
|
||||
unsafe impl Sync for MonitorHandle {}
|
||||
|
||||
impl Clone for MonitorHandle {
|
||||
fn clone(&self) -> MonitorHandle {
|
||||
MonitorHandle::retained_new(self.uiscreen)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MonitorHandle {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorHandle {
|
||||
name: Option<String>,
|
||||
size: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorHandle {
|
||||
name: self.name(),
|
||||
size: self.size(),
|
||||
position: self.position(),
|
||||
hidpi_factor: self.hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
pub fn retained_new(uiscreen: id) -> MonitorHandle {
|
||||
unsafe {
|
||||
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS");
|
||||
let () = msg_send![uiscreen, retain];
|
||||
}
|
||||
MonitorHandle {
|
||||
inner: Inner { uiscreen },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
unsafe {
|
||||
let main = main_uiscreen();
|
||||
if self.uiscreen == main.uiscreen {
|
||||
Some("Primary".to_string())
|
||||
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
|
||||
Some("Mirrored".to_string())
|
||||
} else {
|
||||
uiscreens()
|
||||
.iter()
|
||||
.position(|rhs| rhs.uiscreen == self.uiscreen)
|
||||
.map(|idx| idx.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize {
|
||||
unsafe {
|
||||
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
|
||||
(bounds.size.width as f64, bounds.size.height as f64).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> PhysicalPosition {
|
||||
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 {
|
||||
unsafe {
|
||||
let scale: CGFloat = msg_send![self.ui_screen(), nativeScale];
|
||||
scale as f64
|
||||
}
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||
let mut modes = BTreeSet::new();
|
||||
unsafe {
|
||||
let available_modes: id = msg_send![self.uiscreen, availableModes];
|
||||
let available_mode_count: NSUInteger = msg_send![available_modes, count];
|
||||
|
||||
for i in 0..available_mode_count {
|
||||
let mode: id = msg_send![available_modes, objectAtIndex: i];
|
||||
modes.insert(RootVideoMode {
|
||||
video_mode: VideoMode::retained_new(self.uiscreen, mode),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
modes.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
// MonitorHandleExtIOS
|
||||
impl Inner {
|
||||
pub fn ui_screen(&self) -> id {
|
||||
self.uiscreen
|
||||
}
|
||||
|
||||
pub fn preferred_video_mode(&self) -> RootVideoMode {
|
||||
unsafe {
|
||||
let mode: id = msg_send![self.uiscreen, preferredMode];
|
||||
RootVideoMode {
|
||||
video_mode: VideoMode::retained_new(self.uiscreen, mode),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// requires being run on main thread
|
||||
pub unsafe fn main_uiscreen() -> MonitorHandle {
|
||||
let uiscreen: id = msg_send![class!(UIScreen), mainScreen];
|
||||
MonitorHandle::retained_new(uiscreen)
|
||||
}
|
||||
|
||||
// requires being run on main thread
|
||||
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
|
||||
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
|
||||
MonitorHandle::retained_new(uiscreen)
|
||||
}
|
||||
|
||||
// requires being run on main thread
|
||||
pub unsafe fn uiscreens() -> VecDeque<MonitorHandle> {
|
||||
let screens: id = msg_send![class!(UIScreen), screens];
|
||||
let count: NSUInteger = msg_send![screens, count];
|
||||
let mut result = VecDeque::with_capacity(count as _);
|
||||
let screens_enum: id = msg_send![screens, objectEnumerator];
|
||||
loop {
|
||||
let screen: id = msg_send![screens_enum, nextObject];
|
||||
if screen == nil {
|
||||
break result;
|
||||
}
|
||||
result.push_back(MonitorHandle::retained_new(screen));
|
||||
}
|
||||
}
|
||||
589
src/platform_impl/ios/view.rs
Normal file
589
src/platform_impl/ios/view.rs
Normal file
@@ -0,0 +1,589 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
runtime::{Class, Object, Sel, BOOL, NO, YES},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
|
||||
platform::ios::MonitorHandleExtIOS,
|
||||
platform_impl::platform::{
|
||||
app_state::{self, OSCapabilities},
|
||||
event_loop,
|
||||
ffi::{
|
||||
id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask,
|
||||
UIRectEdge, UITouchPhase, UITouchType,
|
||||
},
|
||||
window::PlatformSpecificWindowBuilderAttributes,
|
||||
DeviceId,
|
||||
},
|
||||
window::{Fullscreen, WindowAttributes, WindowId as RootWindowId},
|
||||
};
|
||||
|
||||
macro_rules! add_property {
|
||||
(
|
||||
$decl:ident,
|
||||
$name:ident: $t:ty,
|
||||
$setter_name:ident: |$object:ident| $after_set:expr,
|
||||
$getter_name:ident,
|
||||
) => {
|
||||
add_property!(
|
||||
$decl,
|
||||
$name: $t,
|
||||
$setter_name: true, |_, _|{}; |$object| $after_set,
|
||||
$getter_name,
|
||||
)
|
||||
};
|
||||
(
|
||||
$decl:ident,
|
||||
$name:ident: $t:ty,
|
||||
$setter_name:ident: $capability:expr, $err:expr; |$object:ident| $after_set:expr,
|
||||
$getter_name:ident,
|
||||
) => {
|
||||
{
|
||||
const VAR_NAME: &'static str = concat!("_", stringify!($name));
|
||||
$decl.add_ivar::<$t>(VAR_NAME);
|
||||
let setter = if $capability {
|
||||
#[allow(non_snake_case)]
|
||||
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
|
||||
unsafe {
|
||||
$object.set_ivar::<$t>(VAR_NAME, value);
|
||||
}
|
||||
$after_set
|
||||
}
|
||||
$setter_name
|
||||
} else {
|
||||
#[allow(non_snake_case)]
|
||||
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
|
||||
unsafe {
|
||||
$object.set_ivar::<$t>(VAR_NAME, value);
|
||||
}
|
||||
$err(&app_state::os_capabilities(), "ignoring")
|
||||
}
|
||||
$setter_name
|
||||
};
|
||||
#[allow(non_snake_case)]
|
||||
extern "C" fn $getter_name($object: &Object, _: Sel) -> $t {
|
||||
unsafe { *$object.get_ivar::<$t>(VAR_NAME) }
|
||||
}
|
||||
$decl.add_method(
|
||||
sel!($setter_name:),
|
||||
setter as extern "C" fn(&mut Object, Sel, $t),
|
||||
);
|
||||
$decl.add_method(
|
||||
sel!($getter_name),
|
||||
$getter_name as extern "C" fn(&Object, Sel) -> $t,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
static mut CLASSES: Option<HashMap<*const Class, &'static Class>> = None;
|
||||
static mut ID: usize = 0;
|
||||
|
||||
if CLASSES.is_none() {
|
||||
CLASSES = Some(HashMap::default());
|
||||
}
|
||||
|
||||
let classes = CLASSES.as_mut().unwrap();
|
||||
|
||||
classes.entry(root_view_class).or_insert_with(move || {
|
||||
let uiview_class = class!(UIView);
|
||||
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass: uiview_class];
|
||||
assert_eq!(
|
||||
is_uiview, YES,
|
||||
"`root_view_class` must inherit from `UIView`"
|
||||
);
|
||||
|
||||
extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) {
|
||||
unsafe {
|
||||
let window: id = msg_send![object, window];
|
||||
assert!(!window.is_null());
|
||||
app_state::handle_nonuser_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
});
|
||||
let superclass: &'static Class = msg_send![object, superclass];
|
||||
let () = msg_send![super(object, superclass), drawRect: rect];
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn layout_subviews(object: &Object, _: Sel) {
|
||||
unsafe {
|
||||
let superclass: &'static Class = msg_send![object, superclass];
|
||||
let () = msg_send![super(object, superclass), layoutSubviews];
|
||||
|
||||
let window: id = msg_send![object, window];
|
||||
assert!(!window.is_null());
|
||||
let bounds: CGRect = msg_send![window, bounds];
|
||||
let screen: id = msg_send![window, screen];
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
let screen_frame: CGRect =
|
||||
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
|
||||
let size = crate::dpi::LogicalSize {
|
||||
width: screen_frame.size.width as _,
|
||||
height: screen_frame.size.height as _,
|
||||
};
|
||||
app_state::handle_nonuser_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Resized(size),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn set_content_scale_factor(
|
||||
object: &mut Object,
|
||||
_: Sel,
|
||||
untrusted_hidpi_factor: CGFloat,
|
||||
) {
|
||||
unsafe {
|
||||
let superclass: &'static Class = msg_send![object, superclass];
|
||||
let () = msg_send![
|
||||
super(object, superclass),
|
||||
setContentScaleFactor: untrusted_hidpi_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 hidpi_factor: CGFloat = msg_send![object, contentScaleFactor];
|
||||
assert!(
|
||||
!hidpi_factor.is_nan()
|
||||
&& hidpi_factor.is_finite()
|
||||
&& hidpi_factor.is_sign_positive()
|
||||
&& hidpi_factor > 0.0,
|
||||
"invalid hidpi_factor set on UIView",
|
||||
);
|
||||
let bounds: CGRect = msg_send![object, bounds];
|
||||
let screen: id = msg_send![window, screen];
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
let screen_frame: CGRect =
|
||||
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
|
||||
let size = crate::dpi::LogicalSize {
|
||||
width: screen_frame.size.width as _,
|
||||
height: screen_frame.size.height as _,
|
||||
};
|
||||
app_state::handle_nonuser_events(
|
||||
std::iter::once(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
|
||||
})
|
||||
.chain(std::iter::once(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Resized(size),
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 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),
|
||||
};
|
||||
|
||||
touch_events.push(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
device_id: RootDeviceId(DeviceId { uiscreen }),
|
||||
id: touch_id,
|
||||
location: (location.x as f64, location.y as f64).into(),
|
||||
force,
|
||||
phase,
|
||||
}),
|
||||
});
|
||||
}
|
||||
app_state::handle_nonuser_events(touch_events);
|
||||
}
|
||||
}
|
||||
|
||||
let mut decl = ClassDecl::new(&format!("WinitUIView{}", ID), root_view_class)
|
||||
.expect("Failed to declare class `WinitUIView`");
|
||||
ID += 1;
|
||||
decl.add_method(
|
||||
sel!(drawRect:),
|
||||
draw_rect as extern "C" fn(&Object, Sel, CGRect),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(layoutSubviews),
|
||||
layout_subviews as extern "C" fn(&Object, Sel),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(setContentScaleFactor:),
|
||||
set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(touchesBegan:withEvent:),
|
||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(touchesMoved:withEvent:),
|
||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(touchesEnded:withEvent:),
|
||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(touchesCancelled:withEvent:),
|
||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
||||
);
|
||||
|
||||
decl.register()
|
||||
})
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn get_view_controller_class() -> &'static Class {
|
||||
static mut CLASS: Option<&'static Class> = None;
|
||||
if CLASS.is_none() {
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
|
||||
let uiviewcontroller_class = class!(UIViewController);
|
||||
|
||||
extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL {
|
||||
YES
|
||||
}
|
||||
|
||||
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
|
||||
.expect("Failed to declare class `WinitUIViewController`");
|
||||
decl.add_method(
|
||||
sel!(shouldAutorotate),
|
||||
should_autorotate as extern "C" fn(&Object, Sel) -> BOOL,
|
||||
);
|
||||
add_property! {
|
||||
decl,
|
||||
prefers_status_bar_hidden: BOOL,
|
||||
setPrefersStatusBarHidden: |object| {
|
||||
unsafe {
|
||||
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
|
||||
}
|
||||
},
|
||||
prefersStatusBarHidden,
|
||||
}
|
||||
add_property! {
|
||||
decl,
|
||||
prefers_home_indicator_auto_hidden: BOOL,
|
||||
setPrefersHomeIndicatorAutoHidden:
|
||||
os_capabilities.home_indicator_hidden,
|
||||
OSCapabilities::home_indicator_hidden_err_msg;
|
||||
|object| {
|
||||
unsafe {
|
||||
let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
|
||||
}
|
||||
},
|
||||
prefersHomeIndicatorAutoHidden,
|
||||
}
|
||||
add_property! {
|
||||
decl,
|
||||
supported_orientations: UIInterfaceOrientationMask,
|
||||
setSupportedInterfaceOrientations: |object| {
|
||||
unsafe {
|
||||
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
|
||||
}
|
||||
},
|
||||
supportedInterfaceOrientations,
|
||||
}
|
||||
add_property! {
|
||||
decl,
|
||||
preferred_screen_edges_deferring_system_gestures: UIRectEdge,
|
||||
setPreferredScreenEdgesDeferringSystemGestures:
|
||||
os_capabilities.defer_system_gestures,
|
||||
OSCapabilities::defer_system_gestures_err_msg;
|
||||
|object| {
|
||||
unsafe {
|
||||
let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
||||
}
|
||||
},
|
||||
preferredScreenEdgesDeferringSystemGestures,
|
||||
}
|
||||
CLASS = Some(decl.register());
|
||||
}
|
||||
CLASS.unwrap()
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn get_window_class() -> &'static Class {
|
||||
static mut CLASS: Option<&'static Class> = None;
|
||||
if CLASS.is_none() {
|
||||
let uiwindow_class = class!(UIWindow);
|
||||
|
||||
extern "C" fn become_key_window(object: &Object, _: Sel) {
|
||||
unsafe {
|
||||
app_state::handle_nonuser_event(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(Event::WindowEvent {
|
||||
window_id: RootWindowId(object.into()),
|
||||
event: WindowEvent::Focused(false),
|
||||
});
|
||||
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
|
||||
}
|
||||
}
|
||||
|
||||
let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class)
|
||||
.expect("Failed to declare class `WinitUIWindow`");
|
||||
decl.add_method(
|
||||
sel!(becomeKeyWindow),
|
||||
become_key_window as extern "C" fn(&Object, Sel),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(resignKeyWindow),
|
||||
resign_key_window as extern "C" fn(&Object, Sel),
|
||||
);
|
||||
|
||||
CLASS = Some(decl.register());
|
||||
}
|
||||
CLASS.unwrap()
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn create_view(
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
) -> id {
|
||||
let class = get_view_class(platform_attributes.root_view_class);
|
||||
|
||||
let view: id = msg_send![class, alloc];
|
||||
assert!(!view.is_null(), "Failed to create `UIView` instance");
|
||||
let view: id = msg_send![view, initWithFrame: frame];
|
||||
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
|
||||
let () = msg_send![view, setMultipleTouchEnabled: YES];
|
||||
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
|
||||
let () = msg_send![view, setContentScaleFactor: hidpi_factor as CGFloat];
|
||||
}
|
||||
|
||||
view
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn create_view_controller(
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
view: id,
|
||||
) -> id {
|
||||
let class = get_view_controller_class();
|
||||
|
||||
let view_controller: id = msg_send![class, alloc];
|
||||
assert!(
|
||||
!view_controller.is_null(),
|
||||
"Failed to create `UIViewController` instance"
|
||||
);
|
||||
let view_controller: id = msg_send![view_controller, init];
|
||||
assert!(
|
||||
!view_controller.is_null(),
|
||||
"Failed to initialize `UIViewController` instance"
|
||||
);
|
||||
let status_bar_hidden = if platform_attributes.prefers_status_bar_hidden {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
};
|
||||
let idiom = event_loop::get_idiom();
|
||||
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
|
||||
platform_attributes.valid_orientations,
|
||||
idiom,
|
||||
);
|
||||
let prefers_home_indicator_hidden = if platform_attributes.prefers_home_indicator_hidden {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
};
|
||||
let edges: UIRectEdge = platform_attributes
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.into();
|
||||
let () = msg_send![
|
||||
view_controller,
|
||||
setPrefersStatusBarHidden: status_bar_hidden
|
||||
];
|
||||
let () = msg_send![
|
||||
view_controller,
|
||||
setSupportedInterfaceOrientations: supported_orientations
|
||||
];
|
||||
let () = msg_send![
|
||||
view_controller,
|
||||
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
|
||||
];
|
||||
let () = msg_send![
|
||||
view_controller,
|
||||
setPreferredScreenEdgesDeferringSystemGestures: edges
|
||||
];
|
||||
let () = msg_send![view_controller, setView: view];
|
||||
view_controller
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn create_window(
|
||||
window_attributes: &WindowAttributes,
|
||||
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
view_controller: id,
|
||||
) -> id {
|
||||
let class = get_window_class();
|
||||
|
||||
let window: id = msg_send![class, alloc];
|
||||
assert!(!window.is_null(), "Failed to create `UIWindow` instance");
|
||||
let window: id = msg_send![window, initWithFrame: frame];
|
||||
assert!(
|
||||
!window.is_null(),
|
||||
"Failed to initialize `UIWindow` instance"
|
||||
);
|
||||
let () = msg_send![window, setRootViewController: view_controller];
|
||||
match window_attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
let uiscreen = video_mode.monitor().ui_screen() as id;
|
||||
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
|
||||
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
|
||||
}
|
||||
Some(Fullscreen::Borderless(ref monitor)) => {
|
||||
msg_send![window, setScreen:monitor.ui_screen()]
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
pub fn create_delegate_class() {
|
||||
extern "C" fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL {
|
||||
unsafe {
|
||||
app_state::did_finish_launching();
|
||||
}
|
||||
YES
|
||||
}
|
||||
|
||||
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
|
||||
unsafe { app_state::handle_nonuser_event(Event::Resumed) }
|
||||
}
|
||||
|
||||
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
|
||||
unsafe { app_state::handle_nonuser_event(Event::Suspended) }
|
||||
}
|
||||
|
||||
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
|
||||
extern "C" fn did_enter_background(_: &Object, _: Sel, _: id) {}
|
||||
|
||||
extern "C" fn will_terminate(_: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
||||
let windows: id = msg_send![app, windows];
|
||||
let windows_enum: id = msg_send![windows, objectEnumerator];
|
||||
let mut events = Vec::new();
|
||||
loop {
|
||||
let window: id = msg_send![windows_enum, nextObject];
|
||||
if window == nil {
|
||||
break;
|
||||
}
|
||||
let is_winit_window: BOOL = msg_send![window, isKindOfClass: class!(WinitUIWindow)];
|
||||
if is_winit_window == YES {
|
||||
events.push(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Destroyed,
|
||||
});
|
||||
}
|
||||
}
|
||||
app_state::handle_nonuser_events(events);
|
||||
app_state::terminated();
|
||||
}
|
||||
}
|
||||
|
||||
let ui_responder = class!(UIResponder);
|
||||
let mut decl =
|
||||
ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
|
||||
|
||||
unsafe {
|
||||
decl.add_method(
|
||||
sel!(application:didFinishLaunchingWithOptions:),
|
||||
did_finish_launching as extern "C" fn(&mut Object, Sel, id, id) -> BOOL,
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(applicationDidBecomeActive:),
|
||||
did_become_active as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationWillResignActive:),
|
||||
will_resign_active as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationWillEnterForeground:),
|
||||
will_enter_foreground as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationDidEnterBackground:),
|
||||
did_enter_background as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(applicationWillTerminate:),
|
||||
will_terminate as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.register();
|
||||
}
|
||||
}
|
||||
615
src/platform_impl/ios/window.rs
Normal file
615
src/platform_impl/ios/window.rs
Normal file
@@ -0,0 +1,615 @@
|
||||
use raw_window_handle::{ios::IOSHandle, RawWindowHandle};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use objc::runtime::{Class, Object, BOOL, NO, YES};
|
||||
|
||||
use crate::{
|
||||
dpi::{self, LogicalPosition, LogicalSize},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::{Event, WindowEvent},
|
||||
icon::Icon,
|
||||
monitor::MonitorHandle as RootMonitorHandle,
|
||||
platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
|
||||
platform_impl::platform::{
|
||||
app_state, event_loop,
|
||||
ffi::{
|
||||
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
|
||||
UIRectEdge, UIScreenOverscanCompensation,
|
||||
},
|
||||
monitor, view, EventLoopWindowTarget, MonitorHandle,
|
||||
},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
|
||||
};
|
||||
|
||||
pub struct Inner {
|
||||
pub window: id,
|
||||
pub view_controller: id,
|
||||
pub view: id,
|
||||
gl_or_metal_backed: bool,
|
||||
}
|
||||
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let () = msg_send![self.view, release];
|
||||
let () = msg_send![self.view_controller, release];
|
||||
let () = msg_send![self.window, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
pub fn set_title(&self, _title: &str) {
|
||||
debug!("`Window::set_title` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
match visible {
|
||||
true => unsafe {
|
||||
let () = msg_send![self.window, setHidden: NO];
|
||||
},
|
||||
false => unsafe {
|
||||
let () = msg_send![self.window, setHidden: YES];
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
unsafe {
|
||||
if self.gl_or_metal_backed {
|
||||
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
|
||||
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
|
||||
// raw or gl/metal for drawing this work is completely avoided.
|
||||
//
|
||||
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
|
||||
// testing.
|
||||
//
|
||||
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
|
||||
app_state::queue_gl_or_metal_redraw(self.window);
|
||||
} else {
|
||||
let () = msg_send![self.view, setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
||||
unsafe {
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
Ok(LogicalPosition {
|
||||
x: safe_area.origin.x as _,
|
||||
y: safe_area.origin.y as _,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
||||
unsafe {
|
||||
let screen_frame = self.screen_frame();
|
||||
Ok(LogicalPosition {
|
||||
x: screen_frame.origin.x as _,
|
||||
y: screen_frame.origin.y as _,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_outer_position(&self, position: LogicalPosition) {
|
||||
unsafe {
|
||||
let screen_frame = self.screen_frame();
|
||||
let new_screen_frame = CGRect {
|
||||
origin: CGPoint {
|
||||
x: position.x as _,
|
||||
y: position.y as _,
|
||||
},
|
||||
size: screen_frame.size,
|
||||
};
|
||||
let bounds = self.from_screen_space(new_screen_frame);
|
||||
let () = msg_send![self.window, setBounds: bounds];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> LogicalSize {
|
||||
unsafe {
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
LogicalSize {
|
||||
width: safe_area.size.width as _,
|
||||
height: safe_area.size.height as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn outer_size(&self) -> LogicalSize {
|
||||
unsafe {
|
||||
let screen_frame = self.screen_frame();
|
||||
LogicalSize {
|
||||
width: screen_frame.size.width as _,
|
||||
height: screen_frame.size.height as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_inner_size(&self, _size: LogicalSize) {
|
||||
unimplemented!("not clear what `Window::set_inner_size` means on iOS");
|
||||
}
|
||||
|
||||
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
|
||||
warn!("`Window::set_min_inner_size` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
|
||||
warn!("`Window::set_max_inner_size` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_resizable(&self, _resizable: bool) {
|
||||
warn!("`Window::set_resizable` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn hidpi_factor(&self) -> f64 {
|
||||
unsafe {
|
||||
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
|
||||
hidpi as _
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
|
||||
debug!("`Window::set_cursor_icon` ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&self, _visible: bool) {
|
||||
debug!("`Window::set_cursor_visible` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_maximized(&self, _maximized: bool) {
|
||||
warn!("`Window::set_maximized` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||
unsafe {
|
||||
let uiscreen = match monitor {
|
||||
Some(Fullscreen::Exclusive(video_mode)) => {
|
||||
let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
|
||||
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
|
||||
uiscreen
|
||||
}
|
||||
Some(Fullscreen::Borderless(monitor)) => monitor.ui_screen() as id,
|
||||
None => {
|
||||
warn!("`Window::set_fullscreen(None)` ignored on iOS");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// this is pretty slow on iOS, so avoid doing it if we can
|
||||
let current: id = msg_send![self.window, screen];
|
||||
if uiscreen != current {
|
||||
let () = msg_send![self.window, setScreen: uiscreen];
|
||||
}
|
||||
|
||||
let bounds: CGRect = msg_send![uiscreen, bounds];
|
||||
let () = msg_send![self.window, setFrame: bounds];
|
||||
|
||||
// For external displays, we must disable overscan compensation or
|
||||
// the displayed image will have giant black bars surrounding it on
|
||||
// each side
|
||||
let () = msg_send![
|
||||
uiscreen,
|
||||
setOverscanCompensation: UIScreenOverscanCompensation::None
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
unsafe {
|
||||
let monitor = self.current_monitor();
|
||||
let uiscreen = monitor.inner.ui_screen();
|
||||
let screen_space_bounds = self.screen_frame();
|
||||
let screen_bounds: CGRect = msg_send![uiscreen, bounds];
|
||||
|
||||
// TODO: track fullscreen instead of relying on brittle float comparisons
|
||||
if screen_space_bounds.origin.x == screen_bounds.origin.x
|
||||
&& screen_space_bounds.origin.y == screen_bounds.origin.y
|
||||
&& screen_space_bounds.size.width == screen_bounds.size.width
|
||||
&& screen_space_bounds.size.height == screen_bounds.size.height
|
||||
{
|
||||
Some(Fullscreen::Borderless(monitor))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_decorations(&self, _decorations: bool) {
|
||||
warn!("`Window::set_decorations` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||
warn!("`Window::set_always_on_top` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_window_icon(&self, _icon: Option<Icon>) {
|
||||
warn!("`Window::set_window_icon` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_ime_position(&self, _position: LogicalPosition) {
|
||||
warn!("`Window::set_ime_position` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn current_monitor(&self) -> RootMonitorHandle {
|
||||
unsafe {
|
||||
let uiscreen: id = msg_send![self.window, screen];
|
||||
RootMonitorHandle {
|
||||
inner: MonitorHandle::retained_new(uiscreen),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
unsafe { monitor::uiscreens() }
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
unsafe { monitor::main_uiscreen() }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WindowId {
|
||||
self.window.into()
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let handle = IOSHandle {
|
||||
ui_window: self.window as _,
|
||||
ui_view: self.view as _,
|
||||
ui_view_controller: self.view_controller as _,
|
||||
..IOSHandle::empty()
|
||||
};
|
||||
RawWindowHandle::IOS(handle)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
pub inner: Inner,
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Window {}
|
||||
unsafe impl Sync for Window {}
|
||||
|
||||
impl Deref for Window {
|
||||
type Target = Inner;
|
||||
|
||||
fn deref(&self) -> &Inner {
|
||||
unsafe {
|
||||
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
|
||||
}
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Window {
|
||||
fn deref_mut(&mut self) -> &mut Inner {
|
||||
unsafe {
|
||||
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
|
||||
}
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new<T>(
|
||||
_event_loop: &EventLoopWindowTarget<T>,
|
||||
window_attributes: WindowAttributes,
|
||||
platform_attributes: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, RootOsError> {
|
||||
if let Some(_) = window_attributes.min_inner_size {
|
||||
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
|
||||
}
|
||||
if let Some(_) = window_attributes.max_inner_size {
|
||||
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
|
||||
}
|
||||
if window_attributes.always_on_top {
|
||||
warn!("`WindowAttributes::always_on_top` is unsupported on iOS");
|
||||
}
|
||||
// TODO: transparency, visible
|
||||
|
||||
unsafe {
|
||||
let screen = match window_attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
video_mode.video_mode.monitor.ui_screen() as id
|
||||
}
|
||||
Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id,
|
||||
None => monitor::main_uiscreen().ui_screen(),
|
||||
};
|
||||
|
||||
let screen_bounds: CGRect = msg_send![screen, bounds];
|
||||
|
||||
let frame = match window_attributes.inner_size {
|
||||
Some(dim) => CGRect {
|
||||
origin: screen_bounds.origin,
|
||||
size: CGSize {
|
||||
width: dim.width as _,
|
||||
height: dim.height as _,
|
||||
},
|
||||
},
|
||||
None => screen_bounds,
|
||||
};
|
||||
|
||||
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
|
||||
|
||||
let gl_or_metal_backed = {
|
||||
let view_class: id = msg_send![view, class];
|
||||
let layer_class: id = msg_send![view_class, layerClass];
|
||||
let is_metal: BOOL =
|
||||
msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
|
||||
let is_gl: BOOL = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
|
||||
is_metal == YES || is_gl == YES
|
||||
};
|
||||
|
||||
let view_controller =
|
||||
view::create_view_controller(&window_attributes, &platform_attributes, view);
|
||||
let window = view::create_window(
|
||||
&window_attributes,
|
||||
&platform_attributes,
|
||||
frame,
|
||||
view_controller,
|
||||
);
|
||||
|
||||
let result = Window {
|
||||
inner: Inner {
|
||||
window,
|
||||
view_controller,
|
||||
view,
|
||||
gl_or_metal_backed,
|
||||
},
|
||||
};
|
||||
app_state::set_key_window(window);
|
||||
|
||||
// Like the Windows and macOS backends, we send a `HiDpiFactorChanged` and `Resized`
|
||||
// event on window creation if the DPI factor != 1.0
|
||||
let hidpi_factor: CGFloat = msg_send![view, contentScaleFactor];
|
||||
if hidpi_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(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
|
||||
})
|
||||
.chain(std::iter::once(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Resized(size),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WindowExtIOS
|
||||
impl Inner {
|
||||
pub fn ui_window(&self) -> id {
|
||||
self.window
|
||||
}
|
||||
pub fn ui_view_controller(&self) -> id {
|
||||
self.view_controller
|
||||
}
|
||||
pub fn ui_view(&self) -> id {
|
||||
self.view
|
||||
}
|
||||
|
||||
pub fn set_hidpi_factor(&self, hidpi_factor: f64) {
|
||||
unsafe {
|
||||
assert!(
|
||||
dpi::validate_hidpi_factor(hidpi_factor),
|
||||
"`WindowExtIOS::set_hidpi_factor` received an invalid hidpi factor"
|
||||
);
|
||||
let hidpi_factor = hidpi_factor as CGFloat;
|
||||
let () = msg_send![self.view, setContentScaleFactor: hidpi_factor];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
||||
unsafe {
|
||||
let idiom = event_loop::get_idiom();
|
||||
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
|
||||
valid_orientations,
|
||||
idiom,
|
||||
);
|
||||
msg_send![
|
||||
self.view_controller,
|
||||
setSupportedInterfaceOrientations: supported_orientations
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||
unsafe {
|
||||
let prefers_home_indicator_hidden = if hidden { YES } else { NO };
|
||||
let () = msg_send![
|
||||
self.view_controller,
|
||||
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
||||
let edges: UIRectEdge = edges.into();
|
||||
unsafe {
|
||||
let () = msg_send![
|
||||
self.view_controller,
|
||||
setPreferredScreenEdgesDeferringSystemGestures: edges
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||
unsafe {
|
||||
let status_bar_hidden = if hidden { YES } else { NO };
|
||||
let () = msg_send![
|
||||
self.view_controller,
|
||||
setPrefersStatusBarHidden: status_bar_hidden
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
// requires main thread
|
||||
unsafe fn screen_frame(&self) -> CGRect {
|
||||
self.to_screen_space(msg_send![self.window, bounds])
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
let screen: id = msg_send![self.window, screen];
|
||||
if !screen.is_null() {
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
msg_send![self.window, convertRect:rect toCoordinateSpace:screen_space]
|
||||
} else {
|
||||
rect
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
let screen: id = msg_send![self.window, screen];
|
||||
if !screen.is_null() {
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
msg_send![self.window, convertRect:rect fromCoordinateSpace:screen_space]
|
||||
} else {
|
||||
rect
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn safe_area_screen_space(&self) -> CGRect {
|
||||
let bounds: CGRect = msg_send![self.window, bounds];
|
||||
if app_state::os_capabilities().safe_area {
|
||||
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
|
||||
let safe_bounds = CGRect {
|
||||
origin: CGPoint {
|
||||
x: bounds.origin.x + safe_area.left,
|
||||
y: bounds.origin.y + safe_area.top,
|
||||
},
|
||||
size: CGSize {
|
||||
width: bounds.size.width - safe_area.left - safe_area.right,
|
||||
height: bounds.size.height - safe_area.top - safe_area.bottom,
|
||||
},
|
||||
};
|
||||
self.to_screen_space(safe_bounds)
|
||||
} else {
|
||||
let screen_frame = self.to_screen_space(bounds);
|
||||
let status_bar_frame: CGRect = {
|
||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
||||
assert!(
|
||||
!app.is_null(),
|
||||
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS"
|
||||
);
|
||||
msg_send![app, statusBarFrame]
|
||||
};
|
||||
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
|
||||
(screen_frame.origin.y, screen_frame.size.height)
|
||||
} else {
|
||||
let y = status_bar_frame.size.height;
|
||||
let height = screen_frame.size.height
|
||||
- (status_bar_frame.size.height - screen_frame.origin.y);
|
||||
(y, height)
|
||||
};
|
||||
CGRect {
|
||||
origin: CGPoint {
|
||||
x: screen_frame.origin.x,
|
||||
y,
|
||||
},
|
||||
size: CGSize {
|
||||
width: screen_frame.size.width,
|
||||
height,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId {
|
||||
window: id,
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId {
|
||||
window: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for WindowId {}
|
||||
unsafe impl Sync for WindowId {}
|
||||
|
||||
impl From<&Object> for WindowId {
|
||||
fn from(window: &Object) -> WindowId {
|
||||
WindowId {
|
||||
window: window as *const _ as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&mut Object> for WindowId {
|
||||
fn from(window: &mut Object) -> WindowId {
|
||||
WindowId {
|
||||
window: window as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<id> for WindowId {
|
||||
fn from(window: id) -> WindowId {
|
||||
WindowId { window }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub root_view_class: &'static Class,
|
||||
pub hidpi_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,
|
||||
valid_orientations: Default::default(),
|
||||
prefers_home_indicator_hidden: false,
|
||||
prefers_status_bar_hidden: false,
|
||||
preferred_screen_edges_deferring_system_gestures: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
726
src/platform_impl/linux/mod.rs
Normal file
726
src/platform_impl/linux/mod.rs
Normal file
@@ -0,0 +1,726 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
|
||||
use std::{collections::VecDeque, env, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, sync::Arc};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::RawWindowHandle;
|
||||
use smithay_client_toolkit::reexports::client::ConnectError;
|
||||
|
||||
pub use self::x11::XNotSupported;
|
||||
use self::x11::{
|
||||
ffi::XVisualInfo, get_xtarget, util::WindowType as XWindowType, XConnection, XError,
|
||||
};
|
||||
use crate::{
|
||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
icon::Icon,
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||
};
|
||||
|
||||
pub mod wayland;
|
||||
pub mod x11;
|
||||
|
||||
/// Environment variable specifying which backend should be used on unix platform.
|
||||
///
|
||||
/// Legal values are x11 and wayland. If this variable is set only the named backend
|
||||
/// will be tried by winit. If it is not set, winit will try to connect to a wayland connection,
|
||||
/// and if it fails will fallback on x11.
|
||||
///
|
||||
/// If this variable is set with any other value, winit will panic.
|
||||
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub visual_infos: Option<XVisualInfo>,
|
||||
pub screen_id: Option<i32>,
|
||||
pub resize_increments: Option<(u32, u32)>,
|
||||
pub base_size: Option<(u32, u32)>,
|
||||
pub class: Option<(String, String)>,
|
||||
pub override_redirect: bool,
|
||||
pub x11_window_types: Vec<XWindowType>,
|
||||
pub gtk_theme_variant: Option<String>,
|
||||
pub app_id: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
visual_infos: None,
|
||||
screen_id: None,
|
||||
resize_increments: None,
|
||||
base_size: None,
|
||||
class: None,
|
||||
override_redirect: false,
|
||||
x11_window_types: vec![XWindowType::Normal],
|
||||
gtk_theme_variant: None,
|
||||
app_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
|
||||
{ Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) };
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OsError {
|
||||
XError(XError),
|
||||
XMisc(&'static str),
|
||||
}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
OsError::XError(e) => f.pad(&e.description),
|
||||
OsError::XMisc(e) => f.pad(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Window {
|
||||
X(x11::Window),
|
||||
Wayland(wayland::Window),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum WindowId {
|
||||
X(x11::WindowId),
|
||||
Wayland(wayland::WindowId),
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId::Wayland(wayland::WindowId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DeviceId {
|
||||
X(x11::DeviceId),
|
||||
Wayland(wayland::DeviceId),
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId::Wayland(wayland::DeviceId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum MonitorHandle {
|
||||
X(x11::MonitorHandle),
|
||||
Wayland(wayland::MonitorHandle),
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.name(),
|
||||
&MonitorHandle::Wayland(ref m) => m.name(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_identifier(&self) -> u32 {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.native_identifier(),
|
||||
&MonitorHandle::Wayland(ref m) => m.native_identifier(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.size(),
|
||||
&MonitorHandle::Wayland(ref m) => m.size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn position(&self) -> PhysicalPosition {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.position(),
|
||||
&MonitorHandle::Wayland(ref m) => m.position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f64 {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.hidpi_factor(),
|
||||
&MonitorHandle::Wayland(ref m) => m.hidpi_factor() as f64,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
|
||||
match self {
|
||||
MonitorHandle::X(m) => Box::new(m.video_modes()),
|
||||
MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum VideoMode {
|
||||
X(x11::VideoMode),
|
||||
Wayland(wayland::VideoMode),
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize {
|
||||
match self {
|
||||
&VideoMode::X(ref m) => m.size(),
|
||||
&VideoMode::Wayland(ref m) => m.size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> u16 {
|
||||
match self {
|
||||
&VideoMode::X(ref m) => m.bit_depth(),
|
||||
&VideoMode::Wayland(ref m) => m.bit_depth(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
match self {
|
||||
&VideoMode::X(ref m) => m.refresh_rate(),
|
||||
&VideoMode::Wayland(ref m) => m.refresh_rate(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn monitor(&self) -> RootMonitorHandle {
|
||||
match self {
|
||||
&VideoMode::X(ref m) => m.monitor(),
|
||||
&VideoMode::Wayland(ref m) => m.monitor(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
#[inline]
|
||||
pub fn new<T>(
|
||||
window_target: &EventLoopWindowTarget<T>,
|
||||
attribs: WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Self, RootOsError> {
|
||||
match *window_target {
|
||||
EventLoopWindowTarget::Wayland(ref window_target) => {
|
||||
wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland)
|
||||
}
|
||||
EventLoopWindowTarget::X(ref window_target) => {
|
||||
x11::Window::new(window_target, attribs, pl_attribs).map(Window::X)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
match self {
|
||||
&Window::X(ref w) => WindowId::X(w.id()),
|
||||
&Window::Wayland(ref w) => WindowId::Wayland(w.id()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, title: &str) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_title(title),
|
||||
&Window::Wayland(ref w) => w.set_title(title),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_visible(visible),
|
||||
&Window::Wayland(ref w) => w.set_visible(visible),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.outer_position(),
|
||||
&Window::Wayland(ref w) => w.outer_position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
||||
match self {
|
||||
&Window::X(ref m) => m.inner_position(),
|
||||
&Window::Wayland(ref m) => m.inner_position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_outer_position(&self, position: LogicalPosition) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_outer_position(position),
|
||||
&Window::Wayland(ref w) => w.set_outer_position(position),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_size(&self) -> LogicalSize {
|
||||
match self {
|
||||
&Window::X(ref w) => w.inner_size(),
|
||||
&Window::Wayland(ref w) => w.inner_size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> LogicalSize {
|
||||
match self {
|
||||
&Window::X(ref w) => w.outer_size(),
|
||||
&Window::Wayland(ref w) => w.outer_size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: LogicalSize) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_inner_size(size),
|
||||
&Window::Wayland(ref w) => w.set_inner_size(size),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_inner_size(&self, dimensions: Option<LogicalSize>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_min_inner_size(dimensions),
|
||||
&Window::Wayland(ref w) => w.set_min_inner_size(dimensions),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_inner_size(&self, dimensions: Option<LogicalSize>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_max_inner_size(dimensions),
|
||||
&Window::Wayland(ref w) => w.set_max_inner_size(dimensions),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_resizable(resizable),
|
||||
&Window::Wayland(ref w) => w.set_resizable(resizable),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_cursor_icon(cursor),
|
||||
&Window::Wayland(ref w) => w.set_cursor_icon(cursor),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
||||
match self {
|
||||
&Window::X(ref window) => window.set_cursor_grab(grab),
|
||||
&Window::Wayland(ref window) => window.set_cursor_grab(grab),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
match self {
|
||||
&Window::X(ref window) => window.set_cursor_visible(visible),
|
||||
&Window::Wayland(ref window) => window.set_cursor_visible(visible),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f64 {
|
||||
match self {
|
||||
&Window::X(ref w) => w.hidpi_factor(),
|
||||
&Window::Wayland(ref w) => w.hidpi_factor() as f64,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_cursor_position(position),
|
||||
&Window::Wayland(ref w) => w.set_cursor_position(position),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_maximized(maximized),
|
||||
&Window::Wayland(ref w) => w.set_maximized(maximized),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.fullscreen(),
|
||||
&Window::Wayland(ref w) => w.fullscreen(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_fullscreen(monitor),
|
||||
&Window::Wayland(ref w) => w.set_fullscreen(monitor),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_decorations(decorations),
|
||||
&Window::Wayland(ref w) => w.set_decorations(decorations),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, always_on_top: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_always_on_top(always_on_top),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_window_icon(window_icon),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, position: LogicalPosition) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_ime_position(position),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_redraw(&self) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.request_redraw(),
|
||||
&Window::Wayland(ref w) => w.request_redraw(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> RootMonitorHandle {
|
||||
match self {
|
||||
&Window::X(ref window) => RootMonitorHandle {
|
||||
inner: MonitorHandle::X(window.current_monitor()),
|
||||
},
|
||||
&Window::Wayland(ref window) => RootMonitorHandle {
|
||||
inner: MonitorHandle::Wayland(window.current_monitor()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
match self {
|
||||
&Window::X(ref window) => window
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::X)
|
||||
.collect(),
|
||||
&Window::Wayland(ref window) => window
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::Wayland)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
match self {
|
||||
&Window::X(ref window) => MonitorHandle::X(window.primary_monitor()),
|
||||
&Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
match self {
|
||||
&Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()),
|
||||
&Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn x_error_callback(
|
||||
display: *mut x11::ffi::Display,
|
||||
event: *mut x11::ffi::XErrorEvent,
|
||||
) -> c_int {
|
||||
let xconn_lock = X11_BACKEND.lock();
|
||||
if let Ok(ref xconn) = *xconn_lock {
|
||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||
// which do not require initialization.
|
||||
let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
|
||||
(xconn.xlib.XGetErrorText)(
|
||||
display,
|
||||
(*event).error_code as c_int,
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
buf.len() as c_int,
|
||||
);
|
||||
let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy();
|
||||
|
||||
let error = XError {
|
||||
description: description.into_owned(),
|
||||
error_code: (*event).error_code,
|
||||
request_code: (*event).request_code,
|
||||
minor_code: (*event).minor_code,
|
||||
};
|
||||
|
||||
error!("X11 error: {:#?}", error);
|
||||
|
||||
*xconn.latest_error.lock() = Some(error);
|
||||
}
|
||||
// Fun fact: this return value is completely ignored.
|
||||
0
|
||||
}
|
||||
|
||||
pub enum EventLoop<T: 'static> {
|
||||
Wayland(wayland::EventLoop<T>),
|
||||
X(x11::EventLoop<T>),
|
||||
}
|
||||
|
||||
pub enum EventLoopProxy<T: 'static> {
|
||||
X(x11::EventLoopProxy<T>),
|
||||
Wayland(wayland::EventLoopProxy<T>),
|
||||
}
|
||||
|
||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
EventLoopProxy::X(proxy) => EventLoopProxy::X(proxy.clone()),
|
||||
EventLoopProxy::Wayland(proxy) => EventLoopProxy::Wayland(proxy.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> EventLoop<T> {
|
||||
assert_is_main_thread("new_any_thread");
|
||||
|
||||
EventLoop::new_any_thread()
|
||||
}
|
||||
|
||||
pub fn new_any_thread() -> EventLoop<T> {
|
||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
||||
match env_var.as_str() {
|
||||
"x11" => {
|
||||
// TODO: propagate
|
||||
return EventLoop::new_x11_any_thread()
|
||||
.expect("Failed to initialize X11 backend");
|
||||
}
|
||||
"wayland" => {
|
||||
return EventLoop::new_wayland_any_thread()
|
||||
.expect("Failed to initialize Wayland backend");
|
||||
}
|
||||
_ => panic!(
|
||||
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
|
||||
BACKEND_PREFERENCE_ENV_VAR,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
let wayland_err = match EventLoop::new_wayland_any_thread() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
let x11_err = match EventLoop::new_x11_any_thread() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
let err_string = format!(
|
||||
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
|
||||
wayland_err, x11_err,
|
||||
);
|
||||
panic!(err_string);
|
||||
}
|
||||
|
||||
pub fn new_wayland() -> Result<EventLoop<T>, ConnectError> {
|
||||
assert_is_main_thread("new_wayland_any_thread");
|
||||
|
||||
EventLoop::new_wayland_any_thread()
|
||||
}
|
||||
|
||||
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, ConnectError> {
|
||||
wayland::EventLoop::new().map(EventLoop::Wayland)
|
||||
}
|
||||
|
||||
pub fn new_x11() -> Result<EventLoop<T>, XNotSupported> {
|
||||
assert_is_main_thread("new_x11_any_thread");
|
||||
|
||||
EventLoop::new_x11_any_thread()
|
||||
}
|
||||
|
||||
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
|
||||
X11_BACKEND
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map(Arc::clone)
|
||||
.map(x11::EventLoop::new)
|
||||
.map(EventLoop::X)
|
||||
.map_err(|err| err.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evlp) => evlp
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::Wayland)
|
||||
.collect(),
|
||||
EventLoop::X(ref evlp) => get_xtarget(&evlp.target)
|
||||
.x_connection()
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::X)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()),
|
||||
EventLoop::X(ref evlp) => {
|
||||
MonitorHandle::X(get_xtarget(&evlp.target).x_connection().primary_monitor())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evlp) => EventLoopProxy::Wayland(evlp.create_proxy()),
|
||||
EventLoop::X(ref evlp) => EventLoopProxy::X(evlp.create_proxy()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, callback: F)
|
||||
where
|
||||
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
match *self {
|
||||
EventLoop::Wayland(ref mut evlp) => evlp.run_return(callback),
|
||||
EventLoop::X(ref mut evlp) => evlp.run_return(callback),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run<F>(self, callback: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
match self {
|
||||
EventLoop::Wayland(evlp) => evlp.run(callback),
|
||||
EventLoop::X(evlp) => evlp.run(callback),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evl) => evl.window_target(),
|
||||
EventLoop::X(ref evl) => evl.window_target(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopProxy<T> {
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
match *self {
|
||||
EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event),
|
||||
EventLoopProxy::X(ref proxy) => proxy.send_event(event),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum EventLoopWindowTarget<T> {
|
||||
Wayland(wayland::EventLoopWindowTarget<T>),
|
||||
X(x11::EventLoopWindowTarget<T>),
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
#[inline]
|
||||
pub fn is_wayland(&self) -> bool {
|
||||
match *self {
|
||||
EventLoopWindowTarget::Wayland(_) => true,
|
||||
EventLoopWindowTarget::X(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sticky_exit_callback<T, F>(
|
||||
evt: Event<T>,
|
||||
target: &RootELW<T>,
|
||||
control_flow: &mut ControlFlow,
|
||||
callback: &mut F,
|
||||
) where
|
||||
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
// make ControlFlow::Exit sticky by providing a dummy
|
||||
// control flow reference if it is already Exit.
|
||||
let mut dummy = ControlFlow::Exit;
|
||||
let cf = if *control_flow == ControlFlow::Exit {
|
||||
&mut dummy
|
||||
} else {
|
||||
control_flow
|
||||
};
|
||||
// user callback
|
||||
callback(evt, target, cf)
|
||||
}
|
||||
|
||||
fn assert_is_main_thread(suggested_method: &str) {
|
||||
if !is_main_thread() {
|
||||
panic!(
|
||||
"Initializing the event loop outside of the main thread is a significant \
|
||||
cross-platform compatibility hazard. If you really, absolutely need to create an \
|
||||
EventLoop on a different thread, please use the `EventLoopExtUnix::{}` function.",
|
||||
suggested_method
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn is_main_thread() -> bool {
|
||||
use libc::{c_long, getpid, syscall, SYS_gettid};
|
||||
|
||||
unsafe { syscall(SYS_gettid) == getpid() as c_long }
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
fn is_main_thread() -> bool {
|
||||
use libc::pthread_main_np;
|
||||
|
||||
unsafe { pthread_main_np() == 1 }
|
||||
}
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
fn is_main_thread() -> bool {
|
||||
use libc::_lwp_self;
|
||||
|
||||
unsafe { _lwp_self() == 1 }
|
||||
}
|
||||
1057
src/platform_impl/linux/wayland/event_loop.rs
Normal file
1057
src/platform_impl/linux/wayland/event_loop.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,22 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::{make_wid, DeviceId, EventsLoopProxy, EventsLoopSink};
|
||||
use sctk::keyboard::{
|
||||
self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind,
|
||||
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 sctk::reexports::client::protocol::wl_keyboard;
|
||||
use sctk::reexports::client::Proxy;
|
||||
use sctk::reexports::client::protocol::wl_seat;
|
||||
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
|
||||
|
||||
use {ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent,
|
||||
};
|
||||
|
||||
pub fn init_keyboard(
|
||||
seat: &Proxy<wl_seat::WlSeat>,
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
events_loop_proxy: EventsLoopProxy,
|
||||
seat: &wl_seat::WlSeat,
|
||||
sink: ::calloop::channel::Sender<crate::event::Event<()>>,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
) -> Proxy<wl_keyboard::WlKeyboard> {
|
||||
) -> wl_keyboard::WlKeyboard {
|
||||
// { variables to be captured by the closures
|
||||
let target = Arc::new(Mutex::new(None));
|
||||
let my_sink = sink.clone();
|
||||
@@ -27,88 +27,119 @@ pub fn init_keyboard(
|
||||
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
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send_event(WindowEvent::Focused(true), wid);
|
||||
*target.lock().unwrap() = Some(wid);
|
||||
}
|
||||
KbEvent::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send_event(WindowEvent::Focused(false), wid);
|
||||
*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,
|
||||
};
|
||||
let vkcode = key_to_vkey(rawkey, keysym);
|
||||
let mut guard = my_sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
},
|
||||
wid,
|
||||
);
|
||||
// send char event only on key press, not release
|
||||
if let ElementState::Released = state {
|
||||
return;
|
||||
}
|
||||
if let Some(txt) = utf8 {
|
||||
for chr in txt.chars() {
|
||||
guard.send_event(WindowEvent::ReceivedCharacter(chr), wid);
|
||||
move |evt: KbEvent<'_>, _| {
|
||||
match evt {
|
||||
KbEvent::Enter { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink
|
||||
.send(Event::WindowEvent {
|
||||
window_id: mk_root_wid(wid),
|
||||
event: WindowEvent::Focused(true),
|
||||
})
|
||||
.unwrap();
|
||||
*target.lock().unwrap() = Some(wid);
|
||||
}
|
||||
KbEvent::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink
|
||||
.send(Event::WindowEvent {
|
||||
window_id: mk_root_wid(wid),
|
||||
event: WindowEvent::Focused(false),
|
||||
})
|
||||
.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(Event::WindowEvent {
|
||||
window_id: mk_root_wid(wid),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: device_id(),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
})
|
||||
.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(Event::WindowEvent {
|
||||
window_id: mk_root_wid(wid),
|
||||
event: WindowEvent::ReceivedCharacter(chr),
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
|
||||
KbEvent::Modifiers { modifiers: event_modifiers } => {
|
||||
*modifiers_tracker.lock().unwrap() = event_modifiers.into()
|
||||
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
|
||||
KbEvent::Modifiers {
|
||||
modifiers: event_modifiers,
|
||||
} => {
|
||||
let modifiers = ModifiersState::from_wayland(event_modifiers);
|
||||
|
||||
*modifiers_tracker.lock().unwrap() = modifiers;
|
||||
|
||||
my_sink
|
||||
.send(Event::DeviceEvent {
|
||||
device_id: device_id(),
|
||||
event: DeviceEvent::ModifiersChanged { modifiers },
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
},
|
||||
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);
|
||||
let mut guard = repeat_sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: repeat_event.rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: my_modifiers.lock().unwrap().clone(),
|
||||
repeat_sink
|
||||
.send(Event::WindowEvent {
|
||||
window_id: mk_root_wid(wid),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: device_id(),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: repeat_event.rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: my_modifiers.lock().unwrap().clone(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
},
|
||||
wid,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
if let Some(txt) = repeat_event.utf8 {
|
||||
for chr in txt.chars() {
|
||||
guard.send_event(WindowEvent::ReceivedCharacter(chr), wid);
|
||||
repeat_sink
|
||||
.send(Event::WindowEvent {
|
||||
window_id: mk_root_wid(wid),
|
||||
event: WindowEvent::ReceivedCharacter(chr),
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
events_loop_proxy.wakeup().unwrap();
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -123,51 +154,64 @@ pub fn init_keyboard(
|
||||
// In this case, we don't have the keymap information (it is
|
||||
// supposed to be serialized by the compositor using libxkbcommon)
|
||||
|
||||
// { variables to be captured by the closure
|
||||
let mut target = None;
|
||||
let my_sink = sink;
|
||||
// }
|
||||
seat.get_keyboard(|keyboard| {
|
||||
keyboard.implement(move |evt, _| match evt {
|
||||
wl_keyboard::Event::Enter { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send_event(WindowEvent::Focused(true), wid);
|
||||
target = Some(wid);
|
||||
}
|
||||
wl_keyboard::Event::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send_event(WindowEvent::Focused(false), wid);
|
||||
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,
|
||||
};
|
||||
my_sink.lock().unwrap().send_event(
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: key,
|
||||
virtual_keycode: None,
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
},
|
||||
wid,
|
||||
);
|
||||
// { 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(Event::WindowEvent {
|
||||
window_id: mk_root_wid(wid),
|
||||
event: WindowEvent::Focused(true),
|
||||
})
|
||||
.unwrap();
|
||||
target = Some(wid);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}, ())
|
||||
}).unwrap()
|
||||
wl_keyboard::Event::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink
|
||||
.send(Event::WindowEvent {
|
||||
window_id: mk_root_wid(wid),
|
||||
event: WindowEvent::Focused(false),
|
||||
})
|
||||
.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(Event::WindowEvent {
|
||||
window_id: mk_root_wid(wid),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: device_id(),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: key,
|
||||
virtual_keycode: None,
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,7 +234,7 @@ fn key_to_vkey(rawkey: u32, keysym: u32) -> Option<VirtualKeyCode> {
|
||||
}
|
||||
|
||||
fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
use sctk::keyboard::keysyms;
|
||||
use smithay_client_toolkit::keyboard::keysyms;
|
||||
match keysym {
|
||||
// letters
|
||||
keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A),
|
||||
@@ -358,8 +402,8 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<keyboard::ModifiersState> for ModifiersState {
|
||||
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
|
||||
impl ModifiersState {
|
||||
pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState {
|
||||
ModifiersState {
|
||||
shift: mods.shift,
|
||||
ctrl: mods.ctrl,
|
||||
@@ -368,3 +412,11 @@ impl From<keyboard::ModifiersState> for ModifiersState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn device_id() -> crate::event::DeviceId {
|
||||
crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId))
|
||||
}
|
||||
|
||||
fn mk_root_wid(wid: crate::platform_impl::wayland::WindowId) -> crate::window::WindowId {
|
||||
crate::window::WindowId(crate::platform_impl::WindowId::Wayland(wid))
|
||||
}
|
||||
41
src/platform_impl/linux/wayland/mod.rs
Normal file
41
src/platform_impl/linux/wayland/mod.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
#![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, VideoMode,
|
||||
WindowEventsSink,
|
||||
},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::wl_surface;
|
||||
|
||||
mod event_loop;
|
||||
mod keyboard;
|
||||
mod pointer;
|
||||
mod touch;
|
||||
mod window;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub 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)
|
||||
}
|
||||
268
src/platform_impl/linux/wayland/pointer.rs
Normal file
268
src/platform_impl/linux/wayland/pointer.rs
Normal file
@@ -0,0 +1,268 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
WindowEvent,
|
||||
};
|
||||
|
||||
use super::{
|
||||
event_loop::{CursorManager, 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,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{
|
||||
zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::Lifetime,
|
||||
zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface;
|
||||
|
||||
pub fn implement_pointer<T: 'static>(
|
||||
seat: &wl_seat::WlSeat,
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
cursor_manager: Arc<Mutex<CursorManager>>,
|
||||
) -> 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();
|
||||
let mut cursor_manager = cursor_manager.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,
|
||||
);
|
||||
}
|
||||
|
||||
cursor_manager.reload_cursor_style();
|
||||
}
|
||||
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!(),
|
||||
}
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn implement_locked_pointer(
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
constraints: &ZwpPointerConstraintsV1,
|
||||
) -> Result<ZwpLockedPointerV1, ()> {
|
||||
constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent.to_raw(), |c| {
|
||||
c.implement_closure(|_, _| (), ())
|
||||
})
|
||||
}
|
||||
114
src/platform_impl/linux/wayland/touch.rs
Normal file
114
src/platform_impl/linux/wayland/touch.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
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(),
|
||||
force: None, // TODO
|
||||
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(),
|
||||
force: None, // TODO
|
||||
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(),
|
||||
force: None, // TODO
|
||||
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(),
|
||||
force: None, // TODO
|
||||
id: pt.id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
@@ -1,40 +1,61 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use raw_window_handle::unix::WaylandHandle;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
mem::replace,
|
||||
sync::{Arc, Mutex, Weak},
|
||||
};
|
||||
|
||||
use {CreationError, MouseCursor, WindowAttributes};
|
||||
use dpi::{LogicalPosition, LogicalSize};
|
||||
use platform::{MonitorId as PlatformMonitorId, PlatformSpecificWindowBuilderAttributes as PlAttributes};
|
||||
use window::MonitorId as RootMonitorId;
|
||||
use crate::{
|
||||
dpi::{LogicalPosition, LogicalSize},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
monitor::MonitorHandle as RootMonitorHandle,
|
||||
platform_impl::{
|
||||
platform::wayland::event_loop::{available_monitors, primary_monitor},
|
||||
MonitorHandle as PlatformMonitorHandle,
|
||||
PlatformSpecificWindowBuilderAttributes as PlAttributes,
|
||||
},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||
};
|
||||
|
||||
use sctk::surface::{get_dpi_factor, get_outputs};
|
||||
use sctk::window::{ConceptFrame, Event as WEvent, State as WState, Window as SWindow, Theme};
|
||||
use sctk::reexports::client::{Display, Proxy};
|
||||
use sctk::reexports::client::protocol::{wl_seat, wl_surface};
|
||||
use sctk::reexports::client::protocol::wl_surface::RequestsTrait as SurfaceRequests;
|
||||
use sctk::output::OutputMgr;
|
||||
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, EventsLoop, MonitorId, WindowId};
|
||||
use platform::platform::wayland::event_loop::{get_available_monitors, get_primary_monitor};
|
||||
use super::{event_loop::CursorManager, make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
|
||||
|
||||
pub struct Window {
|
||||
surface: Proxy<wl_surface::WlSurface>,
|
||||
surface: wl_surface::WlSurface,
|
||||
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
|
||||
cursor_manager: Arc<Mutex<CursorManager>>,
|
||||
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>>,
|
||||
cursor_grab_changed: Arc<Mutex<Option<bool>>>, // Update grab state
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(evlp: &EventsLoop, attributes: WindowAttributes, pl_attribs: PlAttributes) -> Result<Window, CreationError> {
|
||||
let (width, height) = attributes.dimensions.map(Into::into).unwrap_or((800, 600));
|
||||
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 cursor_manager = evlp.cursor_manager.clone();
|
||||
let surface = evlp.env.create_surface(move |dpi, surface| {
|
||||
window_store.lock().unwrap().dpi_change(&surface, dpi);
|
||||
surface.set_buffer_scale(dpi);
|
||||
@@ -52,9 +73,9 @@ impl Window {
|
||||
let is_fullscreen = states.contains(&WState::Fullscreen);
|
||||
|
||||
for window in &mut store.windows {
|
||||
if window.surface.equals(&my_surface) {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.newsize = new_size;
|
||||
window.need_refresh = true;
|
||||
*(window.need_refresh.lock().unwrap()) = true;
|
||||
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
@@ -64,7 +85,7 @@ impl Window {
|
||||
WEvent::Refresh => {
|
||||
let store = window_store.lock().unwrap();
|
||||
for window in &store.windows {
|
||||
if window.surface.equals(&my_surface) {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
@@ -73,33 +94,38 @@ impl Window {
|
||||
WEvent::Close => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
for window in &mut store.windows {
|
||||
if window.surface.equals(&my_surface) {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.closed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(app_id) = pl_attribs.app_id {
|
||||
frame.set_app_id(app_id);
|
||||
}
|
||||
|
||||
frame.set_title(attributes.title);
|
||||
|
||||
for &(_, ref seat) in evlp.seats.lock().unwrap().iter() {
|
||||
frame.new_seat(seat);
|
||||
}
|
||||
|
||||
// Check for fullscreen requirements
|
||||
if let Some(RootMonitorId {
|
||||
inner: PlatformMonitorId::Wayland(ref monitor_id),
|
||||
}) = attributes.fullscreen
|
||||
{
|
||||
frame.set_fullscreen(Some(&monitor_id.proxy));
|
||||
} else if attributes.maximized {
|
||||
frame.set_maximized();
|
||||
match attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
panic!("Wayland doesn't support exclusive fullscreen")
|
||||
}
|
||||
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||
})) => frame.set_fullscreen(Some(&monitor_id.proxy)),
|
||||
Some(Fullscreen::Borderless(_)) => unreachable!(),
|
||||
None => {
|
||||
if attributes.maximized {
|
||||
frame.set_maximized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frame.set_resizable(attributes.resizable);
|
||||
@@ -107,20 +133,26 @@ impl Window {
|
||||
// set decorations
|
||||
frame.set_decorate(attributes.decorations);
|
||||
|
||||
// set title
|
||||
frame.set_title(attributes.title);
|
||||
|
||||
// min-max dimensions
|
||||
frame.set_min_size(attributes.min_dimensions.map(Into::into));
|
||||
frame.set_max_size(attributes.max_dimensions.map(Into::into));
|
||||
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));
|
||||
let cursor_grab_changed = Arc::new(Mutex::new(None));
|
||||
|
||||
evlp.store.lock().unwrap().windows.push(InternalWindow {
|
||||
closed: false,
|
||||
newsize: None,
|
||||
size: size.clone(),
|
||||
need_refresh: need_refresh.clone(),
|
||||
fullscreen: fullscreen.clone(),
|
||||
need_refresh: false,
|
||||
cursor_grab_changed: cursor_grab_changed.clone(),
|
||||
need_frame_refresh: need_frame_refresh.clone(),
|
||||
surface: surface.clone(),
|
||||
kill_switch: kill_switch.clone(),
|
||||
@@ -132,13 +164,16 @@ impl Window {
|
||||
|
||||
Ok(Window {
|
||||
display: evlp.display.clone(),
|
||||
surface: surface,
|
||||
frame: frame,
|
||||
surface,
|
||||
frame,
|
||||
outputs: evlp.env.outputs.clone(),
|
||||
size: size,
|
||||
size,
|
||||
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
|
||||
need_frame_refresh: need_frame_refresh,
|
||||
fullscreen: fullscreen,
|
||||
need_frame_refresh,
|
||||
need_refresh,
|
||||
cursor_manager,
|
||||
fullscreen,
|
||||
cursor_grab_changed,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -151,42 +186,38 @@ impl Window {
|
||||
self.frame.lock().unwrap().set_title(title.into());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
pub fn set_visible(&self, _visible: bool) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
// TODO
|
||||
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
// Not possible with wayland
|
||||
None
|
||||
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
// Not possible with wayland
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, _pos: LogicalPosition) {
|
||||
pub fn set_outer_position(&self, _pos: LogicalPosition) {
|
||||
// Not possible with wayland
|
||||
}
|
||||
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
Some(self.size.lock().unwrap().clone().into())
|
||||
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 get_outer_size(&self) -> Option<LogicalSize> {
|
||||
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);
|
||||
Some((w, h).into())
|
||||
(w, h).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -198,13 +229,19 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
self.frame.lock().unwrap().set_min_size(dimensions.map(Into::into));
|
||||
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_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
self.frame.lock().unwrap().set_max_size(dimensions.map(Into::into));
|
||||
pub fn set_max_inner_size(&self, dimensions: Option<LogicalSize>) {
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_max_size(dimensions.map(Into::into));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -230,75 +267,91 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_fullscreen(&self) -> Option<MonitorId> {
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
if *(self.fullscreen.lock().unwrap()) {
|
||||
Some(self.get_current_monitor())
|
||||
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(self.current_monitor()),
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
||||
if let Some(RootMonitorId {
|
||||
inner: PlatformMonitorId::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_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
match fullscreen {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
panic!("Wayland doesn't support exclusive fullscreen")
|
||||
}
|
||||
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||
})) => {
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_fullscreen(Some(&monitor_id.proxy));
|
||||
}
|
||||
Some(Fullscreen::Borderless(_)) => unreachable!(),
|
||||
None => 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(&self, _cursor: MouseCursor) {
|
||||
// TODO
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
let mut cursor_manager = self.cursor_manager.lock().unwrap();
|
||||
cursor_manager.set_cursor_icon(cursor);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide_cursor(&self, _hide: bool) {
|
||||
// TODO: This isn't possible on Wayland yet
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let mut cursor_manager = self.cursor_manager.lock().unwrap();
|
||||
cursor_manager.set_cursor_visible(visible);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
|
||||
Err("Cursor grabbing is not yet possible on Wayland.".to_owned())
|
||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
||||
*self.cursor_grab_changed.lock().unwrap() = Some(grab);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), String> {
|
||||
Err("Setting the cursor position is not yet possible on Wayland.".to_owned())
|
||||
pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn get_display(&self) -> &Display {
|
||||
pub fn display(&self) -> &Display {
|
||||
&*self.display
|
||||
}
|
||||
|
||||
pub fn get_surface(&self) -> &Proxy<wl_surface::WlSurface> {
|
||||
pub fn surface(&self) -> &wl_surface::WlSurface {
|
||||
&self.surface
|
||||
}
|
||||
|
||||
pub fn get_current_monitor(&self) -> MonitorId {
|
||||
pub fn current_monitor(&self) -> MonitorHandle {
|
||||
let output = get_outputs(&self.surface).last().unwrap().clone();
|
||||
MonitorId {
|
||||
MonitorHandle {
|
||||
proxy: output,
|
||||
mgr: self.outputs.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
get_available_monitors(&self.outputs)
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
available_monitors(&self.outputs)
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
get_primary_monitor(&self.outputs)
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
primary_monitor(&self.outputs)
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> WaylandHandle {
|
||||
WaylandHandle {
|
||||
surface: self.surface().as_ref().c_ptr() as *mut _,
|
||||
display: self.display().as_ref().c_ptr() as *mut _,
|
||||
..WaylandHandle::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,12 +367,13 @@ impl Drop for Window {
|
||||
*/
|
||||
|
||||
struct InternalWindow {
|
||||
surface: Proxy<wl_surface::WlSurface>,
|
||||
surface: wl_surface::WlSurface,
|
||||
newsize: Option<(u32, u32)>,
|
||||
size: Arc<Mutex<(u32, u32)>>,
|
||||
need_refresh: Arc<Mutex<bool>>,
|
||||
fullscreen: Arc<Mutex<bool>>,
|
||||
need_refresh: bool,
|
||||
need_frame_refresh: Arc<Mutex<bool>>,
|
||||
cursor_grab_changed: Arc<Mutex<Option<bool>>>,
|
||||
closed: bool,
|
||||
kill_switch: Arc<Mutex<bool>>,
|
||||
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
|
||||
@@ -331,6 +385,19 @@ pub struct WindowStore {
|
||||
windows: Vec<InternalWindow>,
|
||||
}
|
||||
|
||||
pub struct WindowStoreForEach<'a> {
|
||||
pub newsize: Option<(u32, u32)>,
|
||||
pub size: &'a mut (u32, u32),
|
||||
pub new_dpi: Option<i32>,
|
||||
pub refresh: bool,
|
||||
pub frame_refresh: bool,
|
||||
pub closed: bool,
|
||||
pub grab_cursor: Option<bool>,
|
||||
pub surface: &'a wl_surface::WlSurface,
|
||||
pub wid: WindowId,
|
||||
pub frame: Option<&'a mut SWindow<ConceptFrame>>,
|
||||
}
|
||||
|
||||
impl WindowStore {
|
||||
pub fn new() -> WindowStore {
|
||||
WindowStore {
|
||||
@@ -338,9 +405,9 @@ impl WindowStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_wid(&self, surface: &Proxy<wl_surface::WlSurface>) -> Option<WindowId> {
|
||||
pub fn find_wid(&self, surface: &wl_surface::WlSurface) -> Option<WindowId> {
|
||||
for window in &self.windows {
|
||||
if surface.equals(&window.surface) {
|
||||
if surface.as_ref().equals(&window.surface.as_ref()) {
|
||||
return Some(make_wid(surface));
|
||||
}
|
||||
}
|
||||
@@ -362,7 +429,7 @@ impl WindowStore {
|
||||
pruned
|
||||
}
|
||||
|
||||
pub fn new_seat(&self, seat: &Proxy<wl_seat::WlSeat>) {
|
||||
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);
|
||||
@@ -370,9 +437,9 @@ impl WindowStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn dpi_change(&mut self, surface: &Proxy<wl_surface::WlSurface>, new: i32) {
|
||||
fn dpi_change(&mut self, surface: &wl_surface::WlSurface, new: i32) {
|
||||
for window in &mut self.windows {
|
||||
if surface.equals(&window.surface) {
|
||||
if surface.as_ref().equals(&window.surface.as_ref()) {
|
||||
window.new_dpi = Some(new);
|
||||
}
|
||||
}
|
||||
@@ -380,25 +447,26 @@ impl WindowStore {
|
||||
|
||||
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>>),
|
||||
F: FnMut(WindowStoreForEach<'_>),
|
||||
{
|
||||
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,
|
||||
window.need_refresh,
|
||||
::std::mem::replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
|
||||
window.closed,
|
||||
make_wid(&window.surface),
|
||||
opt_mutex_lock.as_mut().map(|m| &mut **m),
|
||||
);
|
||||
f(WindowStoreForEach {
|
||||
newsize: window.newsize.take(),
|
||||
size: &mut *(window.size.lock().unwrap()),
|
||||
new_dpi: window.new_dpi,
|
||||
refresh: replace(&mut *window.need_refresh.lock().unwrap(), false),
|
||||
frame_refresh: replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
|
||||
closed: window.closed,
|
||||
grab_cursor: window.cursor_grab_changed.lock().unwrap().take(),
|
||||
surface: &window.surface,
|
||||
wid: make_wid(&window.surface),
|
||||
frame: opt_mutex_lock.as_mut().map(|m| &mut **m),
|
||||
});
|
||||
if let Some(dpi) = window.new_dpi.take() {
|
||||
window.current_dpi = dpi;
|
||||
}
|
||||
window.need_refresh = false;
|
||||
// avoid re-spamming the event
|
||||
window.closed = false;
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::Utf8Error;
|
||||
use std::os::raw::*;
|
||||
use std::{
|
||||
io,
|
||||
os::raw::*,
|
||||
path::{Path, PathBuf},
|
||||
str::Utf8Error,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use percent_encoding::percent_decode;
|
||||
|
||||
@@ -127,13 +129,15 @@ impl Dnd {
|
||||
DndState::Accepted => (1, self.atoms.action_private as c_long),
|
||||
DndState::Rejected => (0, self.atoms.none as c_long),
|
||||
};
|
||||
self.xconn.send_client_msg(
|
||||
target_window,
|
||||
target_window,
|
||||
self.atoms.status,
|
||||
None,
|
||||
[this_window as c_long, accepted, 0, 0, action],
|
||||
).flush()
|
||||
self.xconn
|
||||
.send_client_msg(
|
||||
target_window,
|
||||
target_window,
|
||||
self.atoms.status,
|
||||
None,
|
||||
[this_window as c_long, accepted, 0, 0, action],
|
||||
)
|
||||
.flush()
|
||||
}
|
||||
|
||||
pub unsafe fn send_finished(
|
||||
@@ -146,24 +150,23 @@ impl Dnd {
|
||||
DndState::Accepted => (1, self.atoms.action_private as c_long),
|
||||
DndState::Rejected => (0, self.atoms.none as c_long),
|
||||
};
|
||||
self.xconn.send_client_msg(
|
||||
target_window,
|
||||
target_window,
|
||||
self.atoms.finished,
|
||||
None,
|
||||
[this_window as c_long, accepted, action, 0, 0],
|
||||
).flush()
|
||||
self.xconn
|
||||
.send_client_msg(
|
||||
target_window,
|
||||
target_window,
|
||||
self.atoms.finished,
|
||||
None,
|
||||
[this_window as c_long, accepted, action, 0, 0],
|
||||
)
|
||||
.flush()
|
||||
}
|
||||
|
||||
pub unsafe fn get_type_list(
|
||||
&self,
|
||||
source_window: c_ulong,
|
||||
) -> Result<Vec<ffi::Atom>, util::GetPropertyError> {
|
||||
self.xconn.get_property(
|
||||
source_window,
|
||||
self.atoms.type_list,
|
||||
ffi::XA_ATOM,
|
||||
)
|
||||
self.xconn
|
||||
.get_property(source_window, self.atoms.type_list, ffi::XA_ATOM)
|
||||
}
|
||||
|
||||
pub unsafe fn convert_selection(&self, window: c_ulong, time: c_ulong) {
|
||||
@@ -181,11 +184,8 @@ impl Dnd {
|
||||
&self,
|
||||
window: c_ulong,
|
||||
) -> Result<Vec<c_uchar>, util::GetPropertyError> {
|
||||
self.xconn.get_property(
|
||||
window,
|
||||
self.atoms.selection,
|
||||
self.atoms.uri_list,
|
||||
)
|
||||
self.xconn
|
||||
.get_property(window, self.atoms.selection, self.atoms.uri_list)
|
||||
}
|
||||
|
||||
pub fn parse_data(&self, data: &mut Vec<c_uchar>) -> Result<Vec<PathBuf>, DndDataParseError> {
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user