mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-27 07:03:15 -04:00
Compare commits
140 Commits
kchibisov/
...
dpi_v0.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
587ade844d | ||
|
|
6756549ac9 | ||
|
|
17666e3171 | ||
|
|
f6ca06cd58 | ||
|
|
e634cc609f | ||
|
|
aa83726c72 | ||
|
|
1800fa1670 | ||
|
|
a5e6d0aaaf | ||
|
|
4fe4ce3d77 | ||
|
|
078c4c0c4f | ||
|
|
c8579a1882 | ||
|
|
ab96fa8395 | ||
|
|
6461cfa9b1 | ||
|
|
6c214e71ae | ||
|
|
ecc884ac91 | ||
|
|
24e2c6914a | ||
|
|
ed4d70fdd4 | ||
|
|
07c25b9703 | ||
|
|
cdbdd974fb | ||
|
|
7e13248be3 | ||
|
|
b15a40cd14 | ||
|
|
8db4a9cc61 | ||
|
|
a4ab7dc64c | ||
|
|
afb731bb52 | ||
|
|
ef37b1d5dd | ||
|
|
2b4e8ef916 | ||
|
|
ae28eea406 | ||
|
|
a0464ae83b | ||
|
|
16d5f46db1 | ||
|
|
5cada36ae8 | ||
|
|
b3dcfa1275 | ||
|
|
f1c5afd84e | ||
|
|
be1baf164c | ||
|
|
39c0862198 | ||
|
|
aa8ebdc795 | ||
|
|
ea68916055 | ||
|
|
e26b831f23 | ||
|
|
8c3e69c08b | ||
|
|
46879429ed | ||
|
|
0c89ea7386 | ||
|
|
6c0e3c3b15 | ||
|
|
675582bd46 | ||
|
|
4d6fe7e35c | ||
|
|
f9912baf09 | ||
|
|
f290619dce | ||
|
|
23011c6b0a | ||
|
|
3a39a6ddb0 | ||
|
|
90cf9a3398 | ||
|
|
05d8fa0b91 | ||
|
|
d7d20507ed | ||
|
|
f6e66a71f8 | ||
|
|
c09160d1a8 | ||
|
|
2230e71093 | ||
|
|
5e6350d142 | ||
|
|
a6998af997 | ||
|
|
5c48ec7977 | ||
|
|
953d9b4268 | ||
|
|
f5dcd2aabe | ||
|
|
77f1c73f06 | ||
|
|
24c226416e | ||
|
|
69382fda9a | ||
|
|
ee245c569d | ||
|
|
5462f27dda | ||
|
|
927deb030f | ||
|
|
5ea81efc74 | ||
|
|
6896de5b73 | ||
|
|
d736763216 | ||
|
|
e316a89847 | ||
|
|
f0d8689039 | ||
|
|
4d5e68c6e2 | ||
|
|
5835c9102e | ||
|
|
e47081e385 | ||
|
|
171d53c042 | ||
|
|
35379f305a | ||
|
|
4d2a0dd2b3 | ||
|
|
4a8b659228 | ||
|
|
f314cd2b9a | ||
|
|
3657506f6e | ||
|
|
edca3ebc41 | ||
|
|
ca46e29203 | ||
|
|
132fbe14d5 | ||
|
|
164bf85b5b | ||
|
|
cfa8f027cc | ||
|
|
2e5db75101 | ||
|
|
19e5bee3d1 | ||
|
|
fc6cf89ac0 | ||
|
|
dbcdb6f1b4 | ||
|
|
d0c6c34eaa | ||
|
|
f2688d94ea | ||
|
|
f6b20852bf | ||
|
|
59b1eb5410 | ||
|
|
f781e13166 | ||
|
|
9f8ac8feb5 | ||
|
|
74958ecc6f | ||
|
|
3a60cbaba5 | ||
|
|
ae4c449670 | ||
|
|
b2896d7408 | ||
|
|
edfb4b03f4 | ||
|
|
c8c1eca3c7 | ||
|
|
d3207a8d76 | ||
|
|
9d347b72d9 | ||
|
|
13bb51c7e0 | ||
|
|
bb4aa22cf9 | ||
|
|
3e9b80d47a | ||
|
|
c913cdab0b | ||
|
|
a5f5ce6a3d | ||
|
|
fb6b1d487b | ||
|
|
7d77ccfad3 | ||
|
|
c23bed20b8 | ||
|
|
8fe2b62adf | ||
|
|
4e3165f3d8 | ||
|
|
da2268ae22 | ||
|
|
eccd9e415d | ||
|
|
32cd1ad9a7 | ||
|
|
6e1b9fa24d | ||
|
|
380eea0072 | ||
|
|
a18658284c | ||
|
|
8ddd10a7f9 | ||
|
|
4f1c5b6129 | ||
|
|
7e819bb2ce | ||
|
|
dfea49f488 | ||
|
|
b674d20edf | ||
|
|
8db3e0e043 | ||
|
|
d37c591378 | ||
|
|
9419e4e1a7 | ||
|
|
241b7a80bb | ||
|
|
e716adcc0a | ||
|
|
aee95114db | ||
|
|
8f4a8efa99 | ||
|
|
1e1f0fd7e9 | ||
|
|
6e008b39e9 | ||
|
|
6c4da19197 | ||
|
|
a61e7bb4f4 | ||
|
|
7fbc2118b6 | ||
|
|
a96491f302 | ||
|
|
d96fd02f33 | ||
|
|
92e9bfe0fc | ||
|
|
3392e9c1de | ||
|
|
038ef5c3ad | ||
|
|
70c54ee0ff |
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,4 +2,3 @@
|
||||
- [ ] Added an entry to the `changelog` module if knowledge of this change could be valuable to users
|
||||
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
|
||||
- [ ] Created or updated an example program if it would help users understand this functionality
|
||||
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented
|
||||
|
||||
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
@@ -68,20 +68,25 @@ jobs:
|
||||
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
|
||||
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
|
||||
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
|
||||
- { name: 'macOS x86_64', target: x86_64-apple-darwin, os: macos-latest, }
|
||||
- { name: 'macOS Aarch64', target: aarch64-apple-darwin, os: macos-latest, }
|
||||
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
|
||||
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
|
||||
- { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
||||
exclude:
|
||||
# Web on nightly needs extra arguments
|
||||
# Web on nightly needs extra arguments
|
||||
|
||||
- toolchain: nightly
|
||||
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest }
|
||||
platform: { name: 'Web' }
|
||||
# Rustup is broken.
|
||||
- toolchain: nightly
|
||||
platform: { name: 'Windows 32bit GNU' }
|
||||
# Android is tested on stable-3
|
||||
- toolchain: '1.73'
|
||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
platform: { name: 'Android' }
|
||||
# Redox OS doesn't follow MSRV
|
||||
- toolchain: '1.73'
|
||||
platform: { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest }
|
||||
platform: { name: 'Redox OS' }
|
||||
include:
|
||||
- toolchain: '1.73'
|
||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
@@ -180,6 +185,12 @@ jobs:
|
||||
contains(matrix.platform.name, 'Linux 64bit') &&
|
||||
matrix.toolchain != '1.73'
|
||||
run: cargo test -p dpi
|
||||
|
||||
- name: Check dpi crate (no_std)
|
||||
if: >
|
||||
contains(matrix.platform.name, 'Linux 64bit') &&
|
||||
matrix.toolchain != '1.73'
|
||||
run: cargo check -p dpi --no-default-features
|
||||
|
||||
- name: Build tests
|
||||
if: >
|
||||
@@ -243,10 +254,11 @@ jobs:
|
||||
- { name: 'Android', target: aarch64-linux-android }
|
||||
- { name: 'iOS', target: aarch64-apple-ios }
|
||||
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
|
||||
- { name: 'macOS', target: x86_64-apple-darwin }
|
||||
- { name: 'macOS', target: aarch64-apple-darwin }
|
||||
- { name: 'Redox OS', target: x86_64-unknown-redox }
|
||||
- { name: 'Web', target: wasm32-unknown-unknown }
|
||||
- { name: 'Windows', target: x86_64-pc-windows-gnu }
|
||||
- { name: 'Windows GNU', target: x86_64-pc-windows-gnu }
|
||||
- { name: 'Windows MSVC', target: x86_64-pc-windows-msvc }
|
||||
|
||||
steps:
|
||||
- uses: taiki-e/checkout-action@v1
|
||||
|
||||
@@ -19,6 +19,16 @@ All patches have to be sent on Github as [pull requests][prs]. To simplify your
|
||||
life during review it's recommended to check the "give contributors write access
|
||||
to the branch" checkbox.
|
||||
|
||||
We use unstable Rustfmt options across the project, so please run
|
||||
`cargo +nightly fmt` before submitting your work. If you are unable to do so,
|
||||
the maintainers can do it for you before merging, just state so in your pull
|
||||
request description. For details on how to use nightly, consult [the
|
||||
documentation][toolchains].
|
||||
|
||||
When editing markdown files (`.md`) they must be wrapped at 80 characters.
|
||||
|
||||
[toolchains]: https://rust-lang.github.io/rustup/concepts/toolchains.html
|
||||
|
||||
#### Handling review
|
||||
|
||||
During the review process certain events could require an action from your side,
|
||||
|
||||
114
Cargo.toml
114
Cargo.toml
@@ -21,11 +21,10 @@ name = "winit"
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.30.4"
|
||||
version = "0.30.10"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [
|
||||
"rwh_06",
|
||||
"serde",
|
||||
"mint",
|
||||
# Enabled to get docs to compile
|
||||
@@ -38,12 +37,13 @@ targets = [
|
||||
"i686-pc-windows-msvc",
|
||||
"x86_64-pc-windows-msvc",
|
||||
# macOS
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-apple-darwin",
|
||||
# Unix (X11 & Wayland)
|
||||
"i686-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
# iOS
|
||||
"x86_64-apple-ios",
|
||||
"aarch64-apple-ios",
|
||||
# Android
|
||||
"aarch64-linux-android",
|
||||
# Web
|
||||
@@ -54,9 +54,8 @@ targets = [
|
||||
[features]
|
||||
android-game-activity = ["android-activity/game-activity"]
|
||||
android-native-activity = ["android-activity/native-activity"]
|
||||
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
mint = ["dpi/mint"]
|
||||
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
|
||||
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde", "bitflags/serde"]
|
||||
wayland = [
|
||||
"wayland-client",
|
||||
@@ -79,10 +78,10 @@ cfg_aliases = "0.2.1"
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
cursor-icon = "1.1.0"
|
||||
dpi = { version = "0.1.1", path = "dpi" }
|
||||
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
|
||||
dpi = { version = "0.1.2", path = "dpi" }
|
||||
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] }
|
||||
serde = { workspace = true, optional = true }
|
||||
smol_str = "0.2.0"
|
||||
smol_str = "0.3"
|
||||
tracing = { version = "0.1.40", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -90,8 +89,8 @@ image = { version = "0.25.0", default-features = false, features = ["png"] }
|
||||
tracing = { version = "0.1.40", default-features = false, features = ["log"] }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
|
||||
softbuffer = { version = "0.4.0", default-features = false, features = [
|
||||
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
|
||||
softbuffer = { version = "0.4.6", default-features = false, features = [
|
||||
"x11",
|
||||
"x11-dlopen",
|
||||
"wayland",
|
||||
@@ -101,18 +100,19 @@ softbuffer = { version = "0.4.0", default-features = false, features = [
|
||||
# Android
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android-activity = "0.6.0"
|
||||
ndk = { version = "0.9.0", default-features = false }
|
||||
ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
|
||||
|
||||
# AppKit or UIKit
|
||||
[target.'cfg(target_vendor = "apple")'.dependencies]
|
||||
core-foundation = "0.9.3"
|
||||
objc2 = "0.5.2"
|
||||
block2 = "0.6.1"
|
||||
dispatch2 = { version = "0.3.0", default-features = false, features = ["std", "objc2"] }
|
||||
objc2 = "0.6.1"
|
||||
|
||||
# AppKit
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
block2 = "0.5.1"
|
||||
core-graphics = "0.23.1"
|
||||
objc2-app-kit = { version = "0.2.2", features = [
|
||||
objc2-app-kit = { version = "0.3.1", default-features = false, features = [
|
||||
"std",
|
||||
"objc2-core-foundation",
|
||||
"NSAppearance",
|
||||
"NSApplication",
|
||||
"NSBitmapImageRep",
|
||||
@@ -129,29 +129,61 @@ objc2-app-kit = { version = "0.2.2", features = [
|
||||
"NSMenu",
|
||||
"NSMenuItem",
|
||||
"NSOpenGLView",
|
||||
"NSPanel",
|
||||
"NSPasteboard",
|
||||
"NSResponder",
|
||||
"NSRunningApplication",
|
||||
"NSScreen",
|
||||
"NSTextInputClient",
|
||||
"NSTextInputContext",
|
||||
"NSToolbar",
|
||||
"NSView",
|
||||
"NSWindow",
|
||||
"NSWindowScripting",
|
||||
"NSWindowTabGroup",
|
||||
] }
|
||||
objc2-foundation = { version = "0.2.2", features = [
|
||||
objc2-core-foundation = { version = "0.3.1", default-features = false, features = [
|
||||
"std",
|
||||
"block2",
|
||||
"dispatch",
|
||||
"CFBase",
|
||||
"CFCGTypes",
|
||||
"CFData",
|
||||
"CFRunLoop",
|
||||
"CFString",
|
||||
"CFUUID",
|
||||
] }
|
||||
objc2-core-graphics = { version = "0.3.1", default-features = false, features = [
|
||||
"std",
|
||||
"libc",
|
||||
"CGDirectDisplay",
|
||||
"CGDisplayConfiguration",
|
||||
"CGDisplayFade",
|
||||
"CGError",
|
||||
"CGRemoteOperation",
|
||||
"CGWindowLevel",
|
||||
] }
|
||||
objc2-core-video = { version = "0.3.1", default-features = false, features = [
|
||||
"std",
|
||||
"objc2-core-graphics",
|
||||
"CVBase",
|
||||
"CVReturn",
|
||||
"CVDisplayLink",
|
||||
] }
|
||||
objc2-foundation = { version = "0.3.1", default-features = false, features = [
|
||||
"std",
|
||||
"block2",
|
||||
"objc2-core-foundation",
|
||||
"NSArray",
|
||||
"NSAttributedString",
|
||||
"NSData",
|
||||
"NSDictionary",
|
||||
"NSDistributedNotificationCenter",
|
||||
"NSEnumerator",
|
||||
"NSGeometry",
|
||||
"NSKeyValueObserving",
|
||||
"NSNotification",
|
||||
"NSObjCRuntime",
|
||||
"NSOperation",
|
||||
"NSPathUtilities",
|
||||
"NSProcessInfo",
|
||||
"NSRunLoop",
|
||||
@@ -162,23 +194,36 @@ objc2-foundation = { version = "0.2.2", features = [
|
||||
|
||||
# UIKit
|
||||
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
|
||||
objc2-foundation = { version = "0.2.2", features = [
|
||||
"dispatch",
|
||||
objc2-core-foundation = { version = "0.3.1", default-features = false, features = [
|
||||
"std",
|
||||
"CFCGTypes",
|
||||
"CFBase",
|
||||
"CFRunLoop",
|
||||
"CFString",
|
||||
] }
|
||||
objc2-foundation = { version = "0.3.1", default-features = false, features = [
|
||||
"std",
|
||||
"block2",
|
||||
"objc2-core-foundation",
|
||||
"NSArray",
|
||||
"NSEnumerator",
|
||||
"NSGeometry",
|
||||
"NSObjCRuntime",
|
||||
"NSOperation",
|
||||
"NSString",
|
||||
"NSProcessInfo",
|
||||
"NSThread",
|
||||
"NSSet",
|
||||
] }
|
||||
objc2-ui-kit = { version = "0.2.2", features = [
|
||||
objc2-ui-kit = { version = "0.3.1", default-features = false, features = [
|
||||
"std",
|
||||
"objc2-core-foundation",
|
||||
"UIApplication",
|
||||
"UIDevice",
|
||||
"UIEvent",
|
||||
"UIGeometry",
|
||||
"UIGestureRecognizer",
|
||||
"UITextInput",
|
||||
"UITextInputTraits",
|
||||
"UIOrientation",
|
||||
"UIPanGestureRecognizer",
|
||||
"UIPinchGestureRecognizer",
|
||||
@@ -197,7 +242,7 @@ objc2-ui-kit = { version = "0.2.2", features = [
|
||||
# Windows
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
unicode-segmentation = "1.7.1"
|
||||
windows-sys = { version = "0.52.0", features = [
|
||||
windows-sys = { version = "0.59.0", features = [
|
||||
"Win32_Devices_HumanInterfaceDevice",
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
@@ -208,6 +253,7 @@ windows-sys = { version = "0.52.0", features = [
|
||||
"Win32_System_Com",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Ole",
|
||||
"Win32_Security",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Threading",
|
||||
@@ -242,15 +288,16 @@ sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-feature
|
||||
"calloop",
|
||||
], optional = true }
|
||||
sctk-adwaita = { version = "0.10.1", default-features = false, optional = true }
|
||||
wayland-backend = { version = "0.3.5", default-features = false, features = [
|
||||
wayland-backend = { version = "0.3.10", default-features = false, features = [
|
||||
"client_system",
|
||||
], optional = true }
|
||||
wayland-client = { version = "0.31.4", optional = true }
|
||||
wayland-protocols = { version = "0.32.2", features = ["staging"], optional = true }
|
||||
wayland-protocols-plasma = { version = "0.3.2", features = ["client"], optional = true }
|
||||
wayland-client = { version = "0.31.10", optional = true }
|
||||
wayland-protocols = { version = "0.32.8", features = ["staging"], optional = true }
|
||||
wayland-protocols-plasma = { version = "0.3.8", features = ["client"], optional = true }
|
||||
x11-dl = { version = "2.19.1", optional = true }
|
||||
x11rb = { version = "0.13.0", default-features = false, features = [
|
||||
"allow-unsafe-code",
|
||||
"cursor",
|
||||
"dl-libxcb",
|
||||
"randr",
|
||||
"resource_manager",
|
||||
@@ -263,16 +310,16 @@ xkbcommon-dl = "0.4.2"
|
||||
# Orbital
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
orbclient = { version = "0.3.47", default-features = false }
|
||||
redox_syscall = "0.4.1"
|
||||
redox_syscall = "0.5.7"
|
||||
|
||||
# Web
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
js-sys = "0.3.64"
|
||||
js-sys = "0.3.70"
|
||||
pin-project = "1"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-futures = "0.4.43"
|
||||
web-time = "1"
|
||||
web_sys = { package = "web-sys", version = "0.3.64", features = [
|
||||
web_sys = { package = "web-sys", version = "0.3.70", features = [
|
||||
"AbortController",
|
||||
"AbortSignal",
|
||||
"Blob",
|
||||
@@ -289,6 +336,7 @@ web_sys = { package = "web-sys", version = "0.3.64", features = [
|
||||
"FocusEvent",
|
||||
"HtmlCanvasElement",
|
||||
"HtmlElement",
|
||||
"HtmlHtmlElement",
|
||||
"HtmlImageElement",
|
||||
"ImageBitmap",
|
||||
"ImageBitmapOptions",
|
||||
@@ -336,11 +384,9 @@ wasm-bindgen-test = "0.3"
|
||||
[[example]]
|
||||
doc-scrape-examples = true
|
||||
name = "window"
|
||||
required-features = ["rwh_06"]
|
||||
|
||||
[[example]]
|
||||
name = "child_window"
|
||||
required-features = ["rwh_06"]
|
||||
|
||||
[workspace]
|
||||
members = ["dpi"]
|
||||
|
||||
198
FEATURES.md
198
FEATURES.md
@@ -47,201 +47,3 @@ through the implementation work necessary to function on all platforms. When one
|
||||
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
|
||||
If that gets accepted, the platform-specific functions get deprecated and become permanently
|
||||
exposed through the core, cross-platform API.
|
||||
|
||||
# Features
|
||||
|
||||
## Extending this section
|
||||
|
||||
If your PR makes notable changes to Winit's features, please update this section as follows:
|
||||
|
||||
- If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core
|
||||
feature, add a row to the feature matrix and describe what platforms the feature has been implemented on.
|
||||
|
||||
- If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the
|
||||
API rework on all relevant platforms, please move it to the `Completed API Reworks` table.
|
||||
|
||||
- If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*,
|
||||
or mark it as *mostly completed* and link to an issue describing the problems with the implementation.
|
||||
|
||||
## Core
|
||||
|
||||
### Windowing
|
||||
- **Window initialization**: Winit allows the creation of a window
|
||||
- **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context
|
||||
- **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan
|
||||
- **Window decorations**: The windows created by winit are properly decorated, and the decorations can
|
||||
be deactivated
|
||||
- **Window decorations toggle**: Decorations can be turned on or off after window creation
|
||||
- **Window resizing**: The windows created by winit can be resized and generate the appropriate events
|
||||
when they are. The application can precisely control its window size if desired.
|
||||
- **Window resize increments**: When the window gets resized, the application can choose to snap the window's
|
||||
size to specific values.
|
||||
- **Window transparency**: Winit allows the creation of windows with a transparent background.
|
||||
- **Window maximization**: The windows created by winit can be maximized upon creation.
|
||||
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
|
||||
creation.
|
||||
- **Window minimization**: The windows created by winit can be minimized after creation.
|
||||
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
|
||||
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
|
||||
creation.
|
||||
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
|
||||
for fullscreen windows and, if applicable, captures the monitor for exclusive
|
||||
use by this application.
|
||||
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
|
||||
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
|
||||
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
|
||||
get drawn above their owner.
|
||||
|
||||
|
||||
### System Information
|
||||
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
|
||||
- **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth).
|
||||
|
||||
### Input Handling
|
||||
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
|
||||
- **Mouse set location**: Forcibly changing the location of the pointer.
|
||||
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
|
||||
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
|
||||
- **Cursor icon**: Changing the cursor icon or hiding the cursor.
|
||||
- **Cursor image**: Changing the cursor to your own image.
|
||||
- **Cursor hittest**: Handle or ignore mouse events for a window.
|
||||
- **Touch events**: Single-touch events.
|
||||
- **Touch pressure**: Touch events contain information about the amount of force being applied.
|
||||
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
|
||||
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
|
||||
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
|
||||
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
|
||||
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
|
||||
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
|
||||
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
|
||||
|
||||
## Platform
|
||||
### Windows
|
||||
* Setting the name of the internal window class
|
||||
* Setting the taskbar icon
|
||||
* Setting the parent window
|
||||
* Setting a menu bar
|
||||
* `WS_EX_NOREDIRECTIONBITMAP` support
|
||||
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
|
||||
* Changing a system-drawn backdrop
|
||||
* Setting the window border color
|
||||
* Setting the title bar background color
|
||||
* Setting the title color
|
||||
* Setting the corner rounding preference
|
||||
|
||||
### macOS
|
||||
* Window activation policy
|
||||
* Window movable by background
|
||||
* Transparent titlebar
|
||||
* Hidden titlebar
|
||||
* Hidden titlebar buttons
|
||||
* Full-size content view
|
||||
* Accepts first mouse
|
||||
* Set a preferred theme and get current theme.
|
||||
|
||||
### Unix
|
||||
* Window urgency
|
||||
* X11 Window Class
|
||||
* X11 Override Redirect Flag
|
||||
* GTK Theme Variant
|
||||
* Base window size
|
||||
* Setting the X11 parent window
|
||||
|
||||
### iOS
|
||||
* Get the `UIScreen` object pointer
|
||||
* Setting the `UIView` hidpi factor
|
||||
* Valid orientations
|
||||
* Home indicator visibility
|
||||
* Status bar visibility and style
|
||||
* Deferring system gestures
|
||||
* Getting the preferred video mode
|
||||
|
||||
### Web
|
||||
* Get if the systems preferred color scheme is "dark"
|
||||
|
||||
## Compatibility Matrix
|
||||
|
||||
Legend:
|
||||
|
||||
- ✔️: Works as intended
|
||||
- ▢: Mostly works, but some bugs are known
|
||||
- ❌: Missing feature or large bugs making it unusable
|
||||
- **N/A**: Not applicable for this platform
|
||||
- ❓: Unknown status
|
||||
|
||||
### Windowing
|
||||
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|
||||
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|
||||
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|
||||
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|
||||
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|
||||
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|
||||
|Window resize increments |✔️ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|
||||
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|
||||
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|
||||
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|
||||
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** |
|
||||
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |
|
||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
|
||||
|
||||
### System information
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|
||||
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|
||||
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|
||||
|
||||
### Input handling
|
||||
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|
||||
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|
||||
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
||||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|
||||
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|
||||
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|
||||
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|
||||
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|
||||
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|
||||
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |▢[#720] |**N/A**|**N/A**|❓ |**N/A** |
|
||||
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|
||||
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|
||||
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|
||||
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|
||||
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|
||||
|
||||
### Pending API Reworks
|
||||
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|
||||
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|
||||
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|
||||
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|
||||
|
||||
### Completed API Reworks
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||
|
||||
[#165]: https://github.com/rust-windowing/winit/issues/165
|
||||
[#219]: https://github.com/rust-windowing/winit/issues/219
|
||||
[#242]: https://github.com/rust-windowing/winit/issues/242
|
||||
[#306]: https://github.com/rust-windowing/winit/issues/306
|
||||
[#315]: https://github.com/rust-windowing/winit/issues/315
|
||||
[#319]: https://github.com/rust-windowing/winit/issues/319
|
||||
[#33]: https://github.com/rust-windowing/winit/issues/33
|
||||
[#459]: https://github.com/rust-windowing/winit/issues/459
|
||||
[#5]: https://github.com/rust-windowing/winit/issues/5
|
||||
[#63]: https://github.com/rust-windowing/winit/issues/63
|
||||
[#720]: https://github.com/rust-windowing/winit/issues/720
|
||||
[#721]: https://github.com/rust-windowing/winit/issues/721
|
||||
[#750]: https://github.com/rust-windowing/winit/issues/750
|
||||
[#753]: https://github.com/rust-windowing/winit/issues/753
|
||||
[#804]: https://github.com/rust-windowing/winit/issues/804
|
||||
|
||||
16
README.md
16
README.md
@@ -2,13 +2,13 @@
|
||||
|
||||
[](https://crates.io/crates/winit)
|
||||
[](https://docs.rs/winit)
|
||||
[](https://rust-windowing.github.io/winit/winit/index.html)
|
||||
[](https://github.com/rust-windowing/winit/actions)
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.30.4"
|
||||
winit = "0.30.10"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -33,6 +33,10 @@ Winit is designed to be a low-level brick in a hierarchy of libraries. Consequen
|
||||
show something on the window you need to use the platform-specific getters provided by winit, or
|
||||
another library.
|
||||
|
||||
## CONTRIBUING
|
||||
|
||||
For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
## MSRV Policy
|
||||
|
||||
This crate's Minimum Supported Rust Version (MSRV) is **1.73**. Changes to
|
||||
@@ -66,4 +70,10 @@ same MSRV policy.
|
||||
|
||||
### Platform-specific usage
|
||||
|
||||
Check out the [`winit::platform`](https://rust-windowing.github.io/winit/winit/platform/index.html) module for platform-specific usage.
|
||||
Check out the [`winit::platform`](https://docs.rs/winit/latest/winit/platform/index.html) module for platform-specific usage.
|
||||
|
||||
### Repository License
|
||||
|
||||
Note that the license in `LICENSE` doesn't apply in full to the DPI package [./dpi](./dpi).
|
||||
Full details can be found in that folder's README.
|
||||
<!-- This doesn't apply to users of the Winit crate, but this is also the repository level README -->
|
||||
|
||||
26
deny.toml
26
deny.toml
@@ -7,6 +7,7 @@
|
||||
all-features = true
|
||||
exclude-dev = true
|
||||
targets = [
|
||||
{ triple = "aarch64-apple-darwin" },
|
||||
{ triple = "aarch64-apple-ios" },
|
||||
{ triple = "aarch64-linux-android" },
|
||||
{ triple = "i686-pc-windows-gnu" },
|
||||
@@ -25,12 +26,12 @@ targets = [
|
||||
|
||||
[licenses]
|
||||
allow = [
|
||||
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
|
||||
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
|
||||
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
|
||||
"ISC", # https://tldrlegal.com/license/-isc-license
|
||||
"MIT", # https://tldrlegal.com/license/mit-license
|
||||
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
|
||||
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
|
||||
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
|
||||
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
|
||||
"ISC", # https://tldrlegal.com/license/isc-license
|
||||
"MIT", # https://tldrlegal.com/license/mit-license
|
||||
"Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
|
||||
]
|
||||
confidence-threshold = 1.0
|
||||
private = { ignore = true }
|
||||
@@ -54,19 +55,6 @@ crate = "android-activity"
|
||||
allow-globs = ["freetype2/*"]
|
||||
crate = "freetype-sys"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow = [
|
||||
{ path = "releases/friends.sh", checksum = "f896ccdcb8445d29ed6dd0d9a360f94d4f33af2f1cc9965e7bb38b156c45949d" },
|
||||
]
|
||||
crate = "wasm-bindgen"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow = [
|
||||
{ path = "ui-tests/update-all-references.sh", checksum = "8b8dbf31e7ada1314956db7a20ab14b13af3ae246a6295afdc7dc96af8ec3773" },
|
||||
{ path = "ui-tests/update-references.sh", checksum = "65375c25981646e08e8589449a06be4505b1a2c9e10d35f650be4b1b495dff22" },
|
||||
]
|
||||
crate = "wasm-bindgen-macro"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["lib/*.a"]
|
||||
crate = "windows_i686_gnu"
|
||||
|
||||
@@ -9,3 +9,10 @@ by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
|
||||
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
|
||||
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
|
||||
which have been released under the same license as a derivative work.
|
||||
|
||||
## `coordinate-systems*`
|
||||
|
||||
These files are created by [Mads Marquart](https://github.com/madsmtm) using
|
||||
[draw.io](https://draw.io/), and compressed using [svgomg.net](https://svgomg.net/).
|
||||
|
||||
They are licensed under the [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) license.
|
||||
130
docs/coordinate-systems.drawio
Normal file
130
docs/coordinate-systems.drawio
Normal file
@@ -0,0 +1,130 @@
|
||||
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0" version="24.8.6" pages="2">
|
||||
<diagram name="desktop" id="3DDum1nDijUk3y7wIDRm">
|
||||
<mxGraphModel dx="1080" dy="707" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1000" pageHeight="500" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-1" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#E8E8E8;fontColor=#333333;strokeColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="200" y="80" width="480" height="360" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-4" value="" style="rounded=1;whiteSpace=wrap;html=1;shadow=0;fillColor=#d5e8d4;strokeColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="260" y="340" width="360" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-2" value="" style="rounded=1;whiteSpace=wrap;html=1;shadow=0;fillColor=#dae8fc;strokeColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="260" y="140" width="360" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-3" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#DBDBDB;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
|
||||
<mxGeometry x="200" y="60" width="480" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-5" value="" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#d5e8d4;" parent="1" vertex="1">
|
||||
<mxGeometry x="260" y="180" width="360" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-6" value="" style="endArrow=none;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#666666;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-4" target="cRYnzpdCW-J0f_YpP3mc-2" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="10" y="310" as="sourcePoint" />
|
||||
<mxPoint x="60" y="260" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-7" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#666666;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-4" target="cRYnzpdCW-J0f_YpP3mc-2" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="770" y="570" as="sourcePoint" />
|
||||
<mxPoint x="770" y="210" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-8" value="" style="endArrow=none;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=0;strokeColor=#666666;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-2" target="cRYnzpdCW-J0f_YpP3mc-5" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="220.00000000000023" y="179.69" as="sourcePoint" />
|
||||
<mxPoint x="740.0000000000002" y="179.69" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-9" value="<font>outer_position</font>" style="endArrow=blockThin;html=1;strokeWidth=3;rounded=0;exitX=0;exitY=0;exitDx=0;exitDy=0;dashed=1;align=right;fontSize=20;fontFamily=monospace;fontColor=#6C8EBF;labelBackgroundColor=none;spacingLeft=0;spacingRight=15;spacing=0;fillColor=#dae8fc;strokeColor=#6C8EBF;endFill=1;startArrow=oval;startFill=1;endSize=6;targetPerimeterSpacing=0;entryX=0;entryY=0;entryDx=0;entryDy=0;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-3" edge="1" target="cRYnzpdCW-J0f_YpP3mc-2">
|
||||
<mxGeometry x="-0.36" y="-24" width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="80" y="160" as="sourcePoint" />
|
||||
<mxPoint x="240" y="160" as="targetPoint" />
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-10" value="<font>outer_size</font>" style="endArrow=none;html=1;strokeWidth=3;rounded=0;dashed=1;align=left;fontSize=20;fontFamily=monospace;fontColor=#6C8EBF;labelBackgroundColor=none;spacingLeft=15;spacingRight=0;spacing=0;exitX=1;exitY=0;exitDx=0;exitDy=0;fillColor=#dae8fc;strokeColor=#6c8ebf;entryX=1;entryY=1;entryDx=0;entryDy=0;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-2" edge="1" target="cRYnzpdCW-J0f_YpP3mc-4">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="850" y="170" as="sourcePoint" />
|
||||
<mxPoint x="760" y="420" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="860" y="140" />
|
||||
<mxPoint x="860" y="380" />
|
||||
</Array>
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-11" value="<font>surface_size</font>" style="endArrow=none;html=1;strokeWidth=3;rounded=0;dashed=1;align=left;fontSize=20;fontFamily=monospace;fontColor=#82B366;labelBackgroundColor=none;spacingLeft=15;spacingRight=0;spacing=0;entryX=1;entryY=1;entryDx=0;entryDy=0;fillColor=#d5e8d4;strokeColor=#82B366;" parent="1" target="cRYnzpdCW-J0f_YpP3mc-4" edge="1">
|
||||
<mxGeometry x="0.0526" width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="600" y="180" as="sourcePoint" />
|
||||
<mxPoint x="760" y="420" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="700" y="180" />
|
||||
<mxPoint x="700" y="380" />
|
||||
</Array>
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="cRYnzpdCW-J0f_YpP3mc-12" value="<font>surface_position</font>" style="endArrow=blockThin;html=1;strokeWidth=3;rounded=0;dashed=1;align=right;fontSize=20;fontFamily=monospace;fontColor=#82B366;labelBackgroundColor=none;spacingLeft=0;spacingRight=15;spacing=0;fillColor=#d5e8d4;strokeColor=#82b366;exitX=0;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0;entryDx=0;entryDy=0;curved=1;startArrow=oval;startFill=1;endFill=1;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-2" target="cRYnzpdCW-J0f_YpP3mc-5" edge="1">
|
||||
<mxGeometry y="-50" width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="140" y="140" as="sourcePoint" />
|
||||
<mxPoint x="160" y="200" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="250" y="160" />
|
||||
</Array>
|
||||
<mxPoint x="-5" y="-22" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram name="mobile" id="D5mAeJSS4Z33KEKjPCBt">
|
||||
<mxGraphModel dx="1710" dy="1120" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="720" pageHeight="720" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-1" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E8E8E8;fontColor=#333333;strokeColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="200" y="40" width="320" height="640" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-2" value="" style="rounded=1;whiteSpace=wrap;html=1;shadow=0;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="210" y="50" width="300" height="620" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-4" value="" style="rounded=0;whiteSpace=wrap;html=1;shadow=0;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
|
||||
<mxGeometry x="210" y="90" width="300" height="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-20" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DBDBDB;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
|
||||
<mxGeometry x="290" y="640" width="140" height="10" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-3" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DBDBDB;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
|
||||
<mxGeometry x="300" y="50" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-12" value="<font>surface_size</font>" style="endArrow=none;html=1;strokeWidth=3;rounded=0;dashed=1;align=left;fontSize=20;fontFamily=monospace;fontColor=#82B366;labelBackgroundColor=none;spacingLeft=15;spacingRight=15;spacing=0;fillColor=#d5e8d4;strokeColor=#82b366;exitX=1;exitY=0;exitDx=0;exitDy=0;" parent="1" source="RxwCrVmIsQwV7z5iJ9nY-2" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="210" y="50" as="sourcePoint" />
|
||||
<mxPoint x="510" y="670" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="560" y="50" />
|
||||
<mxPoint x="560" y="670" />
|
||||
</Array>
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NrHAzeOh65jb3hkBOxW9-1" value="<div>safe_area.top</div>" style="endArrow=blockThin;html=1;strokeWidth=3;rounded=0;align=right;fontSize=20;fontFamily=monospace;fontColor=#D79B00;labelBackgroundColor=none;spacingLeft=0;spacingRight=15;spacing=0;fillColor=#ffe6cc;strokeColor=#d79b00;startArrow=blockThin;startFill=1;endFill=1;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="180" y="50" as="sourcePoint" />
|
||||
<mxPoint x="180" y="90" as="targetPoint" />
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="NrHAzeOh65jb3hkBOxW9-5" value="<div>safe_area.bottom</div>" style="endArrow=blockThin;html=1;strokeWidth=3;rounded=0;align=right;fontSize=20;fontFamily=monospace;fontColor=#D79B00;labelBackgroundColor=none;spacingLeft=0;spacingRight=15;spacing=0;fillColor=#ffe6cc;strokeColor=#d79b00;startArrow=blockThin;startFill=1;endFill=1;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="180" y="670" as="sourcePoint" />
|
||||
<mxPoint x="180" y="630" as="targetPoint" />
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
1
docs/res/coordinate-systems-desktop.svg
Normal file
1
docs/res/coordinate-systems-desktop.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.9 KiB |
1
docs/res/coordinate-systems-mobile.svg
Normal file
1
docs/res/coordinate-systems-mobile.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="720" height="720" viewBox="-0.5 -0.5 720 720" class="ge-export-svg-auto"><defs><style><![CDATA[@media (prefers-color-scheme:dark){svg.ge-export-svg-auto svg:not(mjx-container>svg),svg.ge-export-svg-auto:not(mjx-container>svg){filter:invert(100%) hue-rotate(180deg)}}]]></style></defs><g data-cell-id="0"><g data-cell-id="1"><rect x="200" y="40" width="320" height="640" rx="48" ry="48" fill="#e8e8e8" stroke="#666" pointer-events="all" data-cell-id="RxwCrVmIsQwV7z5iJ9nY-1"/><rect x="210" y="50" width="300" height="620" rx="45" ry="45" fill="#d5e8d4" stroke="#82b366" pointer-events="all" data-cell-id="RxwCrVmIsQwV7z5iJ9nY-2"/><path fill="#ffe6cc" stroke="#d79b00" pointer-events="all" d="M210 90h300v540H210z" data-cell-id="RxwCrVmIsQwV7z5iJ9nY-4"/><rect x="290" y="640" width="140" height="10" rx="1.5" ry="1.5" fill="#dbdbdb" stroke="#666" pointer-events="all" data-cell-id="RxwCrVmIsQwV7z5iJ9nY-20"/><rect x="300" y="50" width="120" height="30" rx="4.5" ry="4.5" fill="#dbdbdb" stroke="#666" pointer-events="all" data-cell-id="RxwCrVmIsQwV7z5iJ9nY-3"/><g data-cell-id="RxwCrVmIsQwV7z5iJ9nY-12"><path d="M510 50h50v620h-50" fill="none" stroke="#82b366" stroke-width="3" stroke-miterlimit="10" stroke-dasharray="9 9" pointer-events="stroke"/><switch transform="translate(-.5 -.5)"><foreignObject style="overflow:visible;text-align:left" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display:flex;align-items:unsafe center;justify-content:unsafe flex-start;width:1px;height:1px;padding-top:360px;margin-left:575px"><div style="box-sizing:border-box;font-size:0;text-align:left" data-drawio-colors="color: #82B366;"><div style="display:inline-block;font-size:20px;font-family:"monospace";color:#82b366;line-height:1.2;pointer-events:all;white-space:nowrap"><font>surface_size</font></div></div></div></foreignObject><text x="575" y="366" fill="#82B366" font-family=""monospace"" font-size="20">surfa...</text></switch></g><g data-cell-id="NrHAzeOh65jb3hkBOxW9-1"><path d="M180 62.35v15.3" fill="none" stroke="#d79b00" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/><path d="m180 53.35 3 9h-6ZM180 86.65l-3-9h6Z" fill="#d79b00" stroke="#d79b00" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/><switch transform="translate(-.5 -.5)"><foreignObject style="overflow:visible;text-align:left" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display:flex;align-items:unsafe center;justify-content:unsafe flex-end;width:1px;height:1px;padding-top:70px;margin-left:165px"><div style="box-sizing:border-box;font-size:0;text-align:right" data-drawio-colors="color: #D79B00;"><div style="display:inline-block;font-size:20px;font-family:"monospace";color:#d79b00;line-height:1.2;pointer-events:all;white-space:nowrap"><div>safe_area.top</div></div></div></div></foreignObject><text x="165" y="76" fill="#D79B00" font-family=""monospace"" font-size="20" text-anchor="end">safe_...</text></switch></g><g data-cell-id="NrHAzeOh65jb3hkBOxW9-5"><path d="M180 657.65v-15.3" fill="none" stroke="#d79b00" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/><path d="m180 666.65-3-9h6ZM180 633.35l3 9h-6Z" fill="#d79b00" stroke="#d79b00" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/><switch transform="translate(-.5 -.5)"><foreignObject style="overflow:visible;text-align:left" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display:flex;align-items:unsafe center;justify-content:unsafe flex-end;width:1px;height:1px;padding-top:650px;margin-left:165px"><div style="box-sizing:border-box;font-size:0;text-align:right" data-drawio-colors="color: #D79B00;"><div style="display:inline-block;font-size:20px;font-family:"monospace";color:#d79b00;line-height:1.2;pointer-events:all;white-space:nowrap"><div>safe_area.bottom</div></div></div></div></foreignObject><text x="165" y="656" fill="#D79B00" font-family=""monospace"" font-size="20" text-anchor="end">safe_...</text></switch></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -11,6 +11,11 @@ Unreleased` header.
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.1.2
|
||||
|
||||
- Added `Insets`, `LogicalInsets` and `PhysicalInsets` types.
|
||||
- Make `no_std` compatible. If you use this functionality, DPI's license has changed.
|
||||
|
||||
## 0.1.1
|
||||
|
||||
- Derive `Debug`, `Copy`, `Clone`, `PartialEq`, `Serialize`, `Deserialize` traits for `PixelUnit`.
|
||||
|
||||
@@ -3,16 +3,22 @@ categories = ["gui"]
|
||||
description = "Types for handling UI scaling"
|
||||
edition.workspace = true
|
||||
keywords = ["DPI", "HiDPI", "scale-factor"]
|
||||
license.workspace = true
|
||||
# N.B. This is "AND", because of the imported libm code.
|
||||
license = "Apache-2.0 AND MIT"
|
||||
name = "dpi"
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
mint = ["dep:mint"]
|
||||
serde = ["dep:serde"]
|
||||
|
||||
# Access mathematical functions using the standard library implementations
|
||||
std = []
|
||||
|
||||
[dependencies]
|
||||
mint = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
@@ -26,12 +32,13 @@ targets = [
|
||||
"i686-pc-windows-msvc",
|
||||
"x86_64-pc-windows-msvc",
|
||||
# macOS
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-apple-darwin",
|
||||
# Unix (X11 & Wayland)
|
||||
"i686-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
# iOS
|
||||
"x86_64-apple-ios",
|
||||
"aarch64-apple-ios",
|
||||
# Android
|
||||
"aarch64-linux-android",
|
||||
# Web
|
||||
|
||||
51
dpi/LICENSE-LIBM-MIT
Normal file
51
dpi/LICENSE-LIBM-MIT
Normal file
@@ -0,0 +1,51 @@
|
||||
rust-lang/libm as a whole is available for use under the MIT license:
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
This Rust library contains the following copyrights:
|
||||
|
||||
Copyright (c) 2018 Jorge Aparicio
|
||||
|
||||
Portions of this software are derived from third-party works licensed under
|
||||
terms compatible with the above MIT license:
|
||||
|
||||
* musl libc https://www.musl-libc.org/. This library contains the following
|
||||
copyright:
|
||||
|
||||
Copyright © 2005-2020 Rich Felker, et al.
|
||||
|
||||
* The CORE-MATH project https://core-math.gitlabpages.inria.fr/. CORE-MATH
|
||||
routines are available under the MIT license on a per-file basis.
|
||||
|
||||
The musl libc COPYRIGHT file also includes the following notice relevant to
|
||||
math portions of the library:
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Much of the math library code (src/math/* and src/complex/*) is
|
||||
Copyright © 1993,2004 Sun Microsystems or
|
||||
Copyright © 2003-2011 David Schultz or
|
||||
Copyright © 2003-2009 Steven G. Kargl or
|
||||
Copyright © 2003-2009 Bruce D. Evans or
|
||||
Copyright © 2008 Stephen L. Moshier or
|
||||
Copyright © 2017-2018 Arm Limited
|
||||
and labelled as such in comments in the individual source files. All
|
||||
have been licensed under extremely permissive terms.
|
||||
------------------------------------------------------------------------------
|
||||
18
dpi/README.md
Normal file
18
dpi/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# DPI
|
||||
|
||||
Full docs can be found on docs.rs.
|
||||
|
||||
## License
|
||||
|
||||
Most of DPI is licensed under the Apache License, Version 2.0 ([LICENSE](LICENSE)).
|
||||
All files except for `src/libm.rs` (and `LICENSE-LIBM-MIT`) are available solely under that license.
|
||||
|
||||
For its `no_std` support, DPI uses code from the [libm](https://crates.io/crates/libm) crate.
|
||||
This is in the `libm.rs` file, and is licensed solely under the MIT Licence ([LICENSE-LIBM-MIT](LICENSE-LIBM-MIT)).
|
||||
That file contains details of all potentially applicable copyright notices.
|
||||
This is feature gated to only be included if you disable the `std` feature, otherwise it will not be compiled into your final binary
|
||||
(and so these license terms will not apply).
|
||||
|
||||
Overall, this means that the license for this crate depends on what features you have enabled.
|
||||
If you enable the `std` feature, then DPI uses only code available under the Apache-2.0 license, and so can be used under the terms of that license.
|
||||
However, if you disable the `std` feature, then both these licenses must be followed to use the crate as a whole.
|
||||
191
dpi/src/lib.rs
191
dpi/src/lib.rs
@@ -54,13 +54,25 @@
|
||||
//!
|
||||
//! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
|
||||
//! * `mint`: Enables mint (math interoperability standard types) conversions.
|
||||
//! * `std` (enabled by default): Uses the standard library mathematical functions (normally through
|
||||
//! your target platform's libm). This feature also changes the library's license from `Apache-2.0
|
||||
//! AND MIT` to `APACHE-2.0` (only). For full details, see the package README.
|
||||
//!
|
||||
//! To use this library on a target without the standard library available, you should disable
|
||||
//! default features (thus disabling the `std` feature, with the license consequences thereof).
|
||||
//!
|
||||
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
||||
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
||||
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![cfg_attr(feature = "std", forbid(unsafe_code))]
|
||||
#![no_std]
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod libm;
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
extern crate std;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -74,32 +86,32 @@ pub trait Pixel: Copy + Into<f64> {
|
||||
|
||||
impl Pixel for u8 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as u8
|
||||
round(f) as u8
|
||||
}
|
||||
}
|
||||
impl Pixel for u16 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as u16
|
||||
round(f) as u16
|
||||
}
|
||||
}
|
||||
impl Pixel for u32 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as u32
|
||||
round(f) as u32
|
||||
}
|
||||
}
|
||||
impl Pixel for i8 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as i8
|
||||
round(f) as i8
|
||||
}
|
||||
}
|
||||
impl Pixel for i16 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as i16
|
||||
round(f) as i16
|
||||
}
|
||||
}
|
||||
impl Pixel for i32 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as i32
|
||||
round(f) as i32
|
||||
}
|
||||
}
|
||||
impl Pixel for f32 {
|
||||
@@ -113,6 +125,15 @@ impl Pixel for f64 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Round f to the closest integer, rounding away from `0.0`
|
||||
#[inline]
|
||||
fn round(f: f64) -> f64 {
|
||||
#[cfg(feature = "std")]
|
||||
return f.round();
|
||||
#[cfg(not(feature = "std"))]
|
||||
return libm::round(f);
|
||||
}
|
||||
|
||||
/// Checks that the scale factor is a normal positive `f64`.
|
||||
///
|
||||
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing
|
||||
@@ -759,6 +780,150 @@ impl<P: Pixel> From<LogicalPosition<P>> for Position {
|
||||
}
|
||||
}
|
||||
|
||||
/// The logical distance between the edges of two rectangles.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LogicalInsets<P> {
|
||||
/// The distance to the top edge.
|
||||
pub top: P,
|
||||
/// The distance to the left edge.
|
||||
pub left: P,
|
||||
/// The distance to the bottom edge.
|
||||
pub bottom: P,
|
||||
/// The distance to the right edge.
|
||||
pub right: P,
|
||||
}
|
||||
|
||||
impl<P> LogicalInsets<P> {
|
||||
#[inline]
|
||||
pub const fn new(top: P, left: P, bottom: P, right: P) -> Self {
|
||||
Self { top, left, bottom, right }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> LogicalInsets<P> {
|
||||
#[inline]
|
||||
pub fn from_physical<T: Into<PhysicalInsets<X>>, X: Pixel>(
|
||||
physical: T,
|
||||
scale_factor: f64,
|
||||
) -> Self {
|
||||
physical.into().to_logical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalInsets<X> {
|
||||
assert!(validate_scale_factor(scale_factor));
|
||||
let top = self.top.into() * scale_factor;
|
||||
let left = self.left.into() * scale_factor;
|
||||
let bottom = self.bottom.into() * scale_factor;
|
||||
let right = self.right.into() * scale_factor;
|
||||
PhysicalInsets::new(top, left, bottom, right).cast()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cast<X: Pixel>(&self) -> LogicalInsets<X> {
|
||||
LogicalInsets {
|
||||
top: self.top.cast(),
|
||||
left: self.left.cast(),
|
||||
bottom: self.bottom.cast(),
|
||||
right: self.right.cast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The physical distance between the edges of two rectangles.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PhysicalInsets<P> {
|
||||
/// The distance to the top edge.
|
||||
pub top: P,
|
||||
/// The distance to the left edge.
|
||||
pub left: P,
|
||||
/// The distance to the bottom edge.
|
||||
pub bottom: P,
|
||||
/// The distance to the right edge.
|
||||
pub right: P,
|
||||
}
|
||||
|
||||
impl<P> PhysicalInsets<P> {
|
||||
#[inline]
|
||||
pub const fn new(top: P, left: P, bottom: P, right: P) -> Self {
|
||||
Self { top, left, bottom, right }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> PhysicalInsets<P> {
|
||||
#[inline]
|
||||
pub fn from_logical<T: Into<LogicalInsets<X>>, X: Pixel>(
|
||||
logical: T,
|
||||
scale_factor: f64,
|
||||
) -> Self {
|
||||
logical.into().to_physical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalInsets<X> {
|
||||
assert!(validate_scale_factor(scale_factor));
|
||||
let top = self.top.into() / scale_factor;
|
||||
let left = self.left.into() / scale_factor;
|
||||
let bottom = self.bottom.into() / scale_factor;
|
||||
let right = self.right.into() / scale_factor;
|
||||
LogicalInsets::new(top, left, bottom, right).cast()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cast<X: Pixel>(&self) -> PhysicalInsets<X> {
|
||||
PhysicalInsets {
|
||||
top: self.top.cast(),
|
||||
left: self.left.cast(),
|
||||
bottom: self.bottom.cast(),
|
||||
right: self.right.cast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Insets that are either physical or logical.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Insets {
|
||||
Physical(PhysicalInsets<u32>),
|
||||
Logical(LogicalInsets<f64>),
|
||||
}
|
||||
|
||||
impl Insets {
|
||||
pub fn new<S: Into<Self>>(insets: S) -> Self {
|
||||
insets.into()
|
||||
}
|
||||
|
||||
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalInsets<P> {
|
||||
match *self {
|
||||
Self::Physical(insets) => insets.to_logical(scale_factor),
|
||||
Self::Logical(insets) => insets.cast(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalInsets<P> {
|
||||
match *self {
|
||||
Self::Physical(insets) => insets.cast(),
|
||||
Self::Logical(insets) => insets.to_physical(scale_factor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> From<PhysicalInsets<P>> for Insets {
|
||||
#[inline]
|
||||
fn from(insets: PhysicalInsets<P>) -> Self {
|
||||
Self::Physical(insets.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> From<LogicalInsets<P>> for Insets {
|
||||
#[inline]
|
||||
fn from(insets: LogicalInsets<P>) -> Self {
|
||||
Self::Logical(insets.cast())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
@@ -1126,20 +1291,20 @@ mod tests {
|
||||
// Eat coverage for the Debug impls et al
|
||||
#[test]
|
||||
fn ensure_attrs_do_not_panic() {
|
||||
let _ = format!("{:?}", LogicalPosition::<u32>::default().clone());
|
||||
let _ = std::format!("{:?}", LogicalPosition::<u32>::default().clone());
|
||||
HashSet::new().insert(LogicalPosition::<u32>::default());
|
||||
|
||||
let _ = format!("{:?}", PhysicalPosition::<u32>::default().clone());
|
||||
let _ = std::format!("{:?}", PhysicalPosition::<u32>::default().clone());
|
||||
HashSet::new().insert(PhysicalPosition::<u32>::default());
|
||||
|
||||
let _ = format!("{:?}", LogicalSize::<u32>::default().clone());
|
||||
let _ = std::format!("{:?}", LogicalSize::<u32>::default().clone());
|
||||
HashSet::new().insert(LogicalSize::<u32>::default());
|
||||
|
||||
let _ = format!("{:?}", PhysicalSize::<u32>::default().clone());
|
||||
let _ = std::format!("{:?}", PhysicalSize::<u32>::default().clone());
|
||||
HashSet::new().insert(PhysicalSize::<u32>::default());
|
||||
|
||||
let _ = format!("{:?}", Size::Physical((1, 2).into()).clone());
|
||||
let _ = format!("{:?}", Position::Physical((1, 2).into()).clone());
|
||||
let _ = std::format!("{:?}", Size::Physical((1, 2).into()).clone());
|
||||
let _ = std::format!("{:?}", Position::Physical((1, 2).into()).clone());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
56
dpi/src/libm.rs
Normal file
56
dpi/src/libm.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2018 Jorge Aparicio
|
||||
// Copyright © 2005-2020 Rich Felker, et al.
|
||||
// Copyright © 1993,2004 Sun Microsystems or
|
||||
// Copyright © 2003-2011 David Schultz or
|
||||
// Copyright © 2003-2009 Steven G. Kargl or
|
||||
// Copyright © 2003-2009 Bruce D. Evans or
|
||||
// Copyright © 2008 Stephen L. Moshier or
|
||||
// Copyright © 2017-2018 Arm Limited
|
||||
// SPDX-License-Identifier: MIT
|
||||
// This file is licensed solely under the terms discussed in LICENSE-LIBM-MIT at the crate root.
|
||||
// See the package-level README for full details.
|
||||
|
||||
// Taken from https://github.com/rust-lang/libm/blob/master/src/math/mod.rs#L1
|
||||
macro_rules! force_eval {
|
||||
($e:expr) => {
|
||||
unsafe { ::core::ptr::read_volatile(&$e) }
|
||||
};
|
||||
}
|
||||
|
||||
// Taken from https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/round.rs
|
||||
pub(crate) fn round(x: f64) -> f64 {
|
||||
trunc(x + copysign(0.5 - 0.25 * f64::EPSILON, x))
|
||||
}
|
||||
|
||||
// Adapted from: https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/trunc.rs#L8-L12
|
||||
#[allow(clippy::needless_late_init /*, reason = "The original libm code uses this style" */)]
|
||||
fn trunc(x: f64) -> f64 {
|
||||
let x1p120 = f64::from_bits(0x4770000000000000); // 0x1p120f === 2 ^ 120
|
||||
|
||||
let mut i: u64 = x.to_bits();
|
||||
let mut e: i64 = ((i >> 52) & 0x7ff) as i64 - 0x3ff + 12;
|
||||
let m: u64;
|
||||
|
||||
if e >= 52 + 12 {
|
||||
return x;
|
||||
}
|
||||
if e < 12 {
|
||||
e = 1;
|
||||
}
|
||||
m = -1i64 as u64 >> e;
|
||||
if (i & m) == 0 {
|
||||
return x;
|
||||
}
|
||||
force_eval!(x + x1p120);
|
||||
i &= !m;
|
||||
f64::from_bits(i)
|
||||
}
|
||||
|
||||
// Taken from https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/copysign.rs
|
||||
fn copysign(x: f64, y: f64) -> f64 {
|
||||
let mut ux = x.to_bits();
|
||||
let uy = y.to_bits();
|
||||
ux &= (!0) >> 1;
|
||||
ux |= uy & (1 << 63);
|
||||
f64::from_bits(ux)
|
||||
}
|
||||
1290
examples/application.rs
Normal file
1290
examples/application.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,29 +8,40 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
use winit::event::{ElementState, KeyEvent, WindowEvent};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::raw_window_handle::HasRawWindowHandle;
|
||||
use winit::window::{Window, WindowId};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Debug)]
|
||||
struct WindowData {
|
||||
window: Box<dyn Window>,
|
||||
color: u32,
|
||||
}
|
||||
|
||||
impl WindowData {
|
||||
fn new(window: Box<dyn Window>, color: u32) -> Self {
|
||||
Self { window, color }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct Application {
|
||||
parent_window_id: Option<WindowId>,
|
||||
windows: HashMap<WindowId, Window>,
|
||||
windows: HashMap<WindowId, WindowData>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for Application {
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let attributes = Window::default_attributes()
|
||||
let attributes = WindowAttributes::default()
|
||||
.with_title("parent window")
|
||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
||||
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
|
||||
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
|
||||
let window = event_loop.create_window(attributes).unwrap();
|
||||
|
||||
println!("Parent window id: {:?})", window.id());
|
||||
self.parent_window_id = Some(window.id());
|
||||
|
||||
self.windows.insert(window.id(), window);
|
||||
self.windows.insert(window.id(), WindowData::new(window, 0xffbbbbbb));
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
@@ -44,7 +55,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
self.windows.clear();
|
||||
event_loop.exit();
|
||||
},
|
||||
WindowEvent::CursorEntered { device_id: _ } => {
|
||||
WindowEvent::PointerEntered { device_id: _, .. } => {
|
||||
// On x11, println when the cursor entered in a window even if the child window
|
||||
// is created by some key inputs.
|
||||
// the child windows are always placed at (0, 0) with size (200, 200) in the
|
||||
@@ -56,15 +67,24 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
event: KeyEvent { state: ElementState::Pressed, .. },
|
||||
..
|
||||
} => {
|
||||
let child_index = self.windows.len() - 1;
|
||||
let child_color =
|
||||
0xff000000 + 3_u32.pow((child_index + 2).rem_euclid(16) as u32);
|
||||
|
||||
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
|
||||
let child_window = spawn_child_window(parent_window, event_loop);
|
||||
let child_window =
|
||||
spawn_child_window(parent_window.window.as_ref(), event_loop, child_index);
|
||||
let child_id = child_window.id();
|
||||
println!("Child window created with id: {child_id:?}");
|
||||
self.windows.insert(child_id, child_window);
|
||||
self.windows.insert(child_id, WindowData::new(child_window, child_color));
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let Some(window) = self.windows.get(&window_id) {
|
||||
fill::fill_window(window);
|
||||
if window_id == self.parent_window_id.unwrap() {
|
||||
fill::fill_window(window.window.as_ref());
|
||||
} else {
|
||||
fill::fill_window_with_color(window.window.as_ref(), window.color);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
@@ -72,12 +92,23 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_child_window(parent: &Window, event_loop: &dyn ActiveEventLoop) -> Window {
|
||||
fn spawn_child_window(
|
||||
parent: &dyn Window,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
child_count: usize,
|
||||
) -> Box<dyn Window> {
|
||||
let parent = parent.raw_window_handle().unwrap();
|
||||
let mut window_attributes = Window::default_attributes()
|
||||
|
||||
// As child count increases, x goes from 0*128 to 5*128 and then repeats
|
||||
let x: f64 = child_count.rem_euclid(5) as f64 * 128.0;
|
||||
|
||||
// After 5 windows have been put side by side horizontally, a new row starts
|
||||
let y: f64 = (child_count / 5) as f64 * 96.0;
|
||||
|
||||
let mut window_attributes = WindowAttributes::default()
|
||||
.with_title("child window")
|
||||
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
|
||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
||||
.with_surface_size(LogicalSize::new(128.0f32, 96.0))
|
||||
.with_position(Position::Logical(LogicalPosition::new(x, y)))
|
||||
.with_visible(true);
|
||||
// `with_parent_window` is unsafe. Parent window must be a valid window.
|
||||
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };
|
||||
|
||||
@@ -11,7 +11,7 @@ use winit::application::ApplicationHandler;
|
||||
use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent};
|
||||
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
|
||||
use winit::keyboard::{Key, NamedKey};
|
||||
use winit::window::{Window, WindowId};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
@@ -46,13 +46,13 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
event_loop.run_app(ControlFlowDemo::default())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
struct ControlFlowDemo {
|
||||
mode: Mode,
|
||||
request_redraw: bool,
|
||||
wait_cancelled: bool,
|
||||
close_requested: bool,
|
||||
window: Option<Window>,
|
||||
window: Option<Box<dyn Window>>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for ControlFlowDemo {
|
||||
@@ -66,7 +66,7 @@ impl ApplicationHandler for ControlFlowDemo {
|
||||
}
|
||||
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let window_attributes = Window::default_attributes().with_title(
|
||||
let window_attributes = WindowAttributes::default().with_title(
|
||||
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
|
||||
);
|
||||
self.window = Some(event_loop.create_window(window_attributes).unwrap());
|
||||
@@ -114,7 +114,7 @@ impl ApplicationHandler for ControlFlowDemo {
|
||||
WindowEvent::RedrawRequested => {
|
||||
let window = self.window.as_ref().unwrap();
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(window);
|
||||
fill::fill_window(window.as_ref());
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
65
examples/dnd.rs
Normal file
65
examples/dnd.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use std::error::Error;
|
||||
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
#[path = "util/tracing.rs"]
|
||||
mod tracing;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
tracing::init();
|
||||
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
let app = Application::new();
|
||||
Ok(event_loop.run_app(app)?)
|
||||
}
|
||||
|
||||
/// Application state and event handling.
|
||||
#[derive(Debug)]
|
||||
struct Application {
|
||||
window: Option<Box<dyn Window>>,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
fn new() -> Self {
|
||||
Self { window: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler for Application {
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let window_attributes =
|
||||
WindowAttributes::default().with_title("Drag and drop files on me!");
|
||||
self.window = Some(event_loop.create_window(window_attributes).unwrap());
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
_window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
match event {
|
||||
WindowEvent::DragLeft { .. }
|
||||
| WindowEvent::DragEntered { .. }
|
||||
| WindowEvent::DragMoved { .. }
|
||||
| WindowEvent::DragDropped { .. } => {
|
||||
println!("{:?}", event);
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
let window = self.window.as_ref().unwrap();
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(window.as_ref());
|
||||
},
|
||||
WindowEvent::CloseRequested => {
|
||||
event_loop.exit();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,19 +11,19 @@ fn main() -> std::process::ExitCode {
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
|
||||
use winit::window::{Window, WindowId};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
struct PumpDemo {
|
||||
window: Option<Window>,
|
||||
window: Option<Box<dyn Window>>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for PumpDemo {
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let window_attributes = Window::default_attributes().with_title("A fantastic window!");
|
||||
let window_attributes = WindowAttributes::default().with_title("A fantastic window!");
|
||||
self.window = Some(event_loop.create_window(window_attributes).unwrap());
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ fn main() -> std::process::ExitCode {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(window);
|
||||
fill::fill_window(window.as_ref());
|
||||
window.request_redraw();
|
||||
},
|
||||
_ => (),
|
||||
|
||||
@@ -9,16 +9,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
|
||||
use winit::window::{Window, WindowId};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
struct App {
|
||||
idx: usize,
|
||||
window_id: Option<WindowId>,
|
||||
window: Option<Window>,
|
||||
window: Option<Box<dyn Window>>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for App {
|
||||
@@ -29,9 +29,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let window_attributes = Window::default_attributes()
|
||||
let window_attributes = WindowAttributes::default()
|
||||
.with_title("Fantastic window number one!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0));
|
||||
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0));
|
||||
let window = event_loop.create_window(window_attributes).unwrap();
|
||||
self.window_id = Some(window.id());
|
||||
self.window = Some(window);
|
||||
@@ -65,11 +65,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
CloseRequested",
|
||||
self.idx
|
||||
);
|
||||
fill::cleanup_window(window);
|
||||
fill::cleanup_window(window.as_ref());
|
||||
self.window = None;
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill_window(window);
|
||||
fill::fill_window(window.as_ref());
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -9,9 +9,14 @@
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::cleanup_window;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::fill_window;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::fill_window_with_animated_color;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::fill_window_with_color;
|
||||
|
||||
#[cfg(all(feature = "rwh_06", not(any(target_os = "android", target_os = "ios"))))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod platform {
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
@@ -34,18 +39,20 @@ mod platform {
|
||||
/// The graphics context used to draw to a window.
|
||||
struct GraphicsContext {
|
||||
/// The global softbuffer context.
|
||||
context: RefCell<Context<&'static Window>>,
|
||||
context: RefCell<Context<&'static dyn Window>>,
|
||||
|
||||
/// The hash map of window IDs to surfaces.
|
||||
surfaces: HashMap<WindowId, Surface<&'static Window, &'static Window>>,
|
||||
surfaces: HashMap<WindowId, Surface<&'static dyn Window, &'static dyn Window>>,
|
||||
}
|
||||
|
||||
impl GraphicsContext {
|
||||
fn new(w: &Window) -> Self {
|
||||
fn new(w: &dyn Window) -> Self {
|
||||
Self {
|
||||
context: RefCell::new(
|
||||
Context::new(unsafe { mem::transmute::<&'_ Window, &'static Window>(w) })
|
||||
.expect("Failed to create a softbuffer context"),
|
||||
Context::new(unsafe {
|
||||
mem::transmute::<&'_ dyn Window, &'static dyn Window>(w)
|
||||
})
|
||||
.expect("Failed to create a softbuffer context"),
|
||||
),
|
||||
surfaces: HashMap::new(),
|
||||
}
|
||||
@@ -53,24 +60,24 @@ mod platform {
|
||||
|
||||
fn create_surface(
|
||||
&mut self,
|
||||
window: &Window,
|
||||
) -> &mut Surface<&'static Window, &'static Window> {
|
||||
window: &dyn Window,
|
||||
) -> &mut Surface<&'static dyn Window, &'static dyn Window> {
|
||||
self.surfaces.entry(window.id()).or_insert_with(|| {
|
||||
Surface::new(&self.context.borrow(), unsafe {
|
||||
mem::transmute::<&'_ Window, &'static Window>(window)
|
||||
mem::transmute::<&'_ dyn Window, &'static dyn Window>(window)
|
||||
})
|
||||
.expect("Failed to create a softbuffer surface")
|
||||
})
|
||||
}
|
||||
|
||||
fn destroy_surface(&mut self, window: &Window) {
|
||||
fn destroy_surface(&mut self, window: &dyn Window) {
|
||||
self.surfaces.remove(&window.id());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_window(window: &Window) {
|
||||
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
|
||||
GC.with(|gc| {
|
||||
let size = window.inner_size();
|
||||
let size = window.surface_size();
|
||||
let (Some(width), Some(height)) =
|
||||
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
||||
else {
|
||||
@@ -82,19 +89,33 @@ mod platform {
|
||||
let surface =
|
||||
gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xff181818;
|
||||
// Fill a buffer with a solid color
|
||||
|
||||
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(DARK_GRAY);
|
||||
buffer.fill(color);
|
||||
buffer.present().expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(window: &Window) {
|
||||
pub fn fill_window(window: &dyn Window) {
|
||||
fill_window_with_color(window, 0xff181818);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window_with_animated_color(window: &dyn Window, start: std::time::Instant) {
|
||||
let time = start.elapsed().as_secs_f32() * 1.5;
|
||||
let blue = (time.sin() * 255.0) as u32;
|
||||
let green = ((time.cos() * 255.0) as u32) << 8;
|
||||
let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
|
||||
let color = red | green | blue;
|
||||
fill_window_with_color(window, color);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(window: &dyn Window) {
|
||||
GC.with(|gc| {
|
||||
let mut gc = gc.borrow_mut();
|
||||
if let Some(context) = gc.as_mut() {
|
||||
@@ -104,14 +125,28 @@ mod platform {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "rwh_06", not(any(target_os = "android", target_os = "ios")))))]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
mod platform {
|
||||
pub fn fill_window(_window: &winit::window::Window) {
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window(_window: &dyn winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(_window: &winit::window::Window) {
|
||||
pub fn fill_window_with_color(_window: &dyn winit::window::Window, _color: u32) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window_with_animated_color(
|
||||
_window: &dyn winit::window::Window,
|
||||
_start: std::time::Instant,
|
||||
) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(_window: &dyn winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
}
|
||||
|
||||
1221
examples/window.rs
1221
examples/window.rs
File diff suppressed because it is too large
Load Diff
@@ -7,21 +7,22 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::platform::x11::WindowAttributesExtX11;
|
||||
use winit::window::{Window, WindowId};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XEmbedDemo {
|
||||
parent_window_id: u32,
|
||||
window: Option<Window>,
|
||||
window: Option<Box<dyn Window>>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for XEmbedDemo {
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let window_attributes = Window::default_attributes()
|
||||
let window_attributes = WindowAttributes::default()
|
||||
.with_title("An embedded window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.with_embed_parent_window(self.parent_window_id);
|
||||
|
||||
self.window = Some(event_loop.create_window(window_attributes).unwrap());
|
||||
@@ -38,7 +39,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(window);
|
||||
fill::fill_window(window.as_ref());
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
//! End user application handling.
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
|
||||
use crate::event_loop::ActiveEventLoop;
|
||||
#[cfg(macos_platform)]
|
||||
use crate::platform::macos::ApplicationHandlerExtMacOS;
|
||||
use crate::window::WindowId;
|
||||
|
||||
/// The handler of the application events.
|
||||
/// The handler of application-level events.
|
||||
///
|
||||
/// See [the top-level docs] for example usage, and [`EventLoop::run_app`] for an overview of when
|
||||
/// events are delivered.
|
||||
///
|
||||
/// This is [dropped] when the event loop is shut down. Note that this only works if you're passing
|
||||
/// the entire state to [`EventLoop::run_app`] (passing `&mut app` won't work).
|
||||
///
|
||||
/// [the top-level docs]: crate
|
||||
/// [`EventLoop::run_app`]: crate::event_loop::EventLoop::run_app
|
||||
/// [dropped]: std::ops::Drop
|
||||
pub trait ApplicationHandler {
|
||||
/// Emitted when new events arrive from the OS to be processed.
|
||||
///
|
||||
@@ -43,11 +53,20 @@ pub trait ApplicationHandler {
|
||||
/// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event
|
||||
/// [`bfcache`]: https://web.dev/bfcache/
|
||||
///
|
||||
/// ### Android
|
||||
///
|
||||
/// On Android, the [`resumed()`] method is called when the `Activity` is (again, if after a
|
||||
/// prior [`suspended()`]) being displayed to the user. This is a good place to begin drawing
|
||||
/// visual elements, running animations, etc. It is driven by Android's [`onStart()`] method.
|
||||
///
|
||||
/// [`onStart()`]: https://developer.android.com/reference/android/app/Activity#onStart()
|
||||
///
|
||||
/// ### Others
|
||||
///
|
||||
/// **Android / macOS / Orbital / Wayland / Windows / X11:** Unsupported.
|
||||
/// **macOS / Orbital / Wayland / Windows / X11:** Unsupported.
|
||||
///
|
||||
/// [`resumed()`]: Self::resumed
|
||||
/// [`resumed()`]: Self::resumed()
|
||||
/// [`suspended()`]: Self::suspended()
|
||||
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let _ = event_loop;
|
||||
}
|
||||
@@ -73,26 +92,24 @@ pub trait ApplicationHandler {
|
||||
///
|
||||
/// ### Android
|
||||
///
|
||||
/// On Android, the [`can_create_surfaces()`] method is called when a new [`SurfaceView`] has
|
||||
/// been created. This is expected to closely correlate with the [`onResume`] lifecycle
|
||||
/// event but there may technically be a discrepancy.
|
||||
/// On Android, the [`can_create_surfaces()`] method is called when a new [`NativeWindow`]
|
||||
/// (native [`Surface`]) is created which backs the application window. This is expected to
|
||||
/// closely correlate with the [`onStart`] lifecycle event which typically results in a surface
|
||||
/// to be created after the app becomes visible.
|
||||
///
|
||||
/// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume()
|
||||
///
|
||||
/// Applications that need to run on Android must wait until they have been "resumed" before
|
||||
/// Applications that need to run on Android must wait until they have received a surface before
|
||||
/// they will be able to create a render surface (such as an `EGLSurface`, [`VkSurfaceKHR`]
|
||||
/// or [`wgpu::Surface`]) which depend on having a [`SurfaceView`]. Applications must also
|
||||
/// assume that if they are [suspended], then their render surfaces are invalid and should
|
||||
/// be dropped.
|
||||
/// or [`wgpu::Surface`]) which depend on having a [`NativeWindow`]. Applications must handle
|
||||
/// [`destroy_surfaces()`], where their render surfaces are invalid and should be dropped.
|
||||
///
|
||||
/// [suspended]: Self::destroy_surfaces
|
||||
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
|
||||
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
|
||||
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
|
||||
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface
|
||||
/// [`onStart`]: https://developer.android.com/reference/android/app/Activity#onStart()
|
||||
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
|
||||
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
|
||||
///
|
||||
/// [`can_create_surfaces()`]: Self::can_create_surfaces
|
||||
/// [`destroy_surfaces()`]: Self::destroy_surfaces
|
||||
/// [`can_create_surfaces()`]: Self::can_create_surfaces()
|
||||
/// [`destroy_surfaces()`]: Self::destroy_surfaces()
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop);
|
||||
|
||||
/// Called after a wake up is requested using [`EventLoopProxy::wake_up()`].
|
||||
@@ -154,8 +171,6 @@ pub trait ApplicationHandler {
|
||||
///
|
||||
/// let (sender, receiver) = mpsc::channel();
|
||||
///
|
||||
/// let mut app = MyApp { receiver };
|
||||
///
|
||||
/// // Send an event in a loop
|
||||
/// let proxy = event_loop.create_proxy();
|
||||
/// let background_thread = thread::spawn(move || {
|
||||
@@ -163,7 +178,7 @@ pub trait ApplicationHandler {
|
||||
/// loop {
|
||||
/// println!("sending: {i}");
|
||||
/// if sender.send(i).is_err() {
|
||||
/// // Stop sending once `MyApp` is dropped
|
||||
/// // Stop sending once the receiver is dropped
|
||||
/// break;
|
||||
/// }
|
||||
/// // Trigger the wake-up _after_ we placed the event in the channel.
|
||||
@@ -174,9 +189,8 @@ pub trait ApplicationHandler {
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// event_loop.run_app(&mut app)?;
|
||||
/// event_loop.run_app(MyApp { receiver })?;
|
||||
///
|
||||
/// drop(app);
|
||||
/// background_thread.join().unwrap();
|
||||
///
|
||||
/// Ok(())
|
||||
@@ -195,10 +209,14 @@ pub trait ApplicationHandler {
|
||||
);
|
||||
|
||||
/// Emitted when the OS sends an event to a device.
|
||||
///
|
||||
/// For this to be called, it must be enabled with [`EventLoop::listen_device_events`].
|
||||
///
|
||||
/// [`EventLoop::listen_device_events`]: crate::event_loop::EventLoop::listen_device_events
|
||||
fn device_event(
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
device_id: DeviceId,
|
||||
device_id: Option<DeviceId>,
|
||||
event: DeviceEvent,
|
||||
) {
|
||||
let _ = (event_loop, device_id, event);
|
||||
@@ -237,18 +255,29 @@ pub trait ApplicationHandler {
|
||||
/// ### Web
|
||||
///
|
||||
/// On Web, the [`suspended()`] method is called in response to a [`pagehide`] event if the
|
||||
/// page is being restored from the [`bfcache`] (back/forward cache) - an in-memory cache that
|
||||
/// page is being stored in the [`bfcache`] (back/forward cache) - an in-memory cache that
|
||||
/// stores a complete snapshot of a page (including the JavaScript heap) as the user is
|
||||
/// navigating away.
|
||||
///
|
||||
/// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
|
||||
/// [`bfcache`]: https://web.dev/bfcache/
|
||||
///
|
||||
/// ### Android
|
||||
///
|
||||
/// On Android, the [`suspended()`] method is called when the `Activity` is no longer visible
|
||||
/// to the user. This is a good place to stop refreshing UI, running animations and other visual
|
||||
/// things. It is driven by Android's [`onStop()`] method.
|
||||
///
|
||||
/// After this event the application either receives [`resumed()`] again, or will exit.
|
||||
///
|
||||
/// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop()
|
||||
///
|
||||
/// ### Others
|
||||
///
|
||||
/// **Android / macOS / Orbital / Wayland / Windows / X11:** Unsupported.
|
||||
/// **macOS / Orbital / Wayland / Windows / X11:** Unsupported.
|
||||
///
|
||||
/// [`suspended()`]: Self::suspended
|
||||
/// [`resumed()`]: Self::resumed()
|
||||
/// [`suspended()`]: Self::suspended()
|
||||
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let _ = event_loop;
|
||||
}
|
||||
@@ -261,24 +290,22 @@ pub trait ApplicationHandler {
|
||||
///
|
||||
/// ### Android
|
||||
///
|
||||
/// On Android, the [`destroy_surfaces()`] method is called when the application's associated
|
||||
/// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`]
|
||||
/// lifecycle event but there may technically be a discrepancy.
|
||||
/// On Android, the [`destroy_surfaces()`] method is called when the application's
|
||||
/// [`NativeWindow`] (native [`Surface`]) is destroyed. This is expected to closely correlate
|
||||
/// with the [`onStop`] lifecycle event which typically results in the surface to be destroyed
|
||||
/// after the app becomes invisible.
|
||||
///
|
||||
/// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause()
|
||||
///
|
||||
/// Applications that need to run on Android should assume their [`SurfaceView`] has been
|
||||
/// Applications that need to run on Android should assume their [`NativeWindow`] has been
|
||||
/// destroyed, which indirectly invalidates any existing render surfaces that may have been
|
||||
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
|
||||
///
|
||||
/// After being [suspended] on Android applications must drop all render surfaces before
|
||||
/// the event callback completes, which may be re-created when the application is next
|
||||
/// [resumed].
|
||||
/// When receiving [`destroy_surfaces()`] Android applications should drop all render surfaces
|
||||
/// before the event callback completes, which may be re-created when the application next
|
||||
/// receives [`can_create_surfaces()`].
|
||||
///
|
||||
/// [suspended]: Self::destroy_surfaces
|
||||
/// [resumed]: Self::can_create_surfaces
|
||||
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
|
||||
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
|
||||
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
|
||||
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface
|
||||
/// [`onStop`]: https://developer.android.com/reference/android/app/Activity#onStop()
|
||||
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
|
||||
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
|
||||
///
|
||||
@@ -286,20 +313,12 @@ pub trait ApplicationHandler {
|
||||
///
|
||||
/// - **iOS / macOS / Orbital / Wayland / Web / Windows / X11:** Unsupported.
|
||||
///
|
||||
/// [`can_create_surfaces()`]: Self::can_create_surfaces
|
||||
/// [`destroy_surfaces()`]: Self::destroy_surfaces
|
||||
/// [`can_create_surfaces()`]: Self::can_create_surfaces()
|
||||
/// [`destroy_surfaces()`]: Self::destroy_surfaces()
|
||||
fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let _ = event_loop;
|
||||
}
|
||||
|
||||
/// Emitted when the event loop is being shut down.
|
||||
///
|
||||
/// This is irreversible - if this method is called, it is guaranteed that the event loop
|
||||
/// will exit right after.
|
||||
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let _ = event_loop;
|
||||
}
|
||||
|
||||
/// Emitted when the application has received a memory warning.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
@@ -328,22 +347,18 @@ pub trait ApplicationHandler {
|
||||
let _ = event_loop;
|
||||
}
|
||||
|
||||
/// Get the [`ApplicationHandler`] as [`Any`].
|
||||
/// The macOS-specific handler.
|
||||
///
|
||||
/// This is useful for downcasting to a concrete application type.
|
||||
/// The return value from this should not change at runtime.
|
||||
#[cfg(macos_platform)]
|
||||
#[inline(always)]
|
||||
fn as_any(&mut self) -> Option<&mut dyn Any> {
|
||||
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[deny(clippy::missing_trait_methods)]
|
||||
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
|
||||
#[inline(always)]
|
||||
fn as_any(&mut self) -> Option<&mut dyn Any> {
|
||||
(**self).as_any()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) {
|
||||
(**self).new_events(event_loop, cause);
|
||||
@@ -378,7 +393,7 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
|
||||
fn device_event(
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
device_id: DeviceId,
|
||||
device_id: Option<DeviceId>,
|
||||
event: DeviceEvent,
|
||||
) {
|
||||
(**self).device_event(event_loop, device_id, event);
|
||||
@@ -399,24 +414,20 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
|
||||
(**self).destroy_surfaces(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
(**self).exiting(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
(**self).memory_warning(event_loop);
|
||||
}
|
||||
|
||||
#[cfg(macos_platform)]
|
||||
#[inline]
|
||||
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
|
||||
(**self).macos_handler()
|
||||
}
|
||||
}
|
||||
|
||||
#[deny(clippy::missing_trait_methods)]
|
||||
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
|
||||
#[inline(always)]
|
||||
fn as_any(&mut self) -> Option<&mut dyn Any> {
|
||||
(**self).as_any()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) {
|
||||
(**self).new_events(event_loop, cause);
|
||||
@@ -451,7 +462,7 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
|
||||
fn device_event(
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
device_id: DeviceId,
|
||||
device_id: Option<DeviceId>,
|
||||
event: DeviceEvent,
|
||||
) {
|
||||
(**self).device_event(event_loop, device_id, event);
|
||||
@@ -472,13 +483,14 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
|
||||
(**self).destroy_surfaces(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
(**self).exiting(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
(**self).memory_warning(event_loop);
|
||||
}
|
||||
|
||||
#[cfg(macos_platform)]
|
||||
#[inline]
|
||||
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
|
||||
(**self).macos_handler()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,29 +43,46 @@ changelog entry.
|
||||
### Added
|
||||
|
||||
- Add `ActiveEventLoop::create_proxy()`.
|
||||
- On Web, implement `Error` for `platform::web::CustomCursorError`.
|
||||
- On Web, add `ActiveEventLoopExtWeb::is_cursor_lock_raw()` to determine if
|
||||
`DeviceEvent::MouseMotion` is returning raw data, not OS accelerated, when using
|
||||
`CursorGrabMode::Locked`.
|
||||
- On Web, implement `MonitorHandle` and `VideoModeHandle`.
|
||||
|
||||
|
||||
Without prompting the user for permission, only the current monitor is returned. But when
|
||||
prompting and being granted permission through
|
||||
`ActiveEventLoop::request_detailed_monitor_permission()`, access to all monitors and their
|
||||
information is available. This "detailed monitors" can be used in `Window::set_fullscreen()` as
|
||||
well.
|
||||
- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop.
|
||||
- Add `ActiveEventLoop::system_theme()`, returning the current system theme.
|
||||
- Add `Touch::finger_id` with a new type `FingerId`.
|
||||
- On Web and Windows, add `FingerIdExt*::is_primary()`, exposing a way to determine
|
||||
the primary finger in a multi-touch interaction.
|
||||
details is available. Handles created with "detailed monitor permissions" can be used in
|
||||
`Window::set_fullscreen()` as well.
|
||||
|
||||
Keep in mind that handles do not auto-upgrade after permissions are granted and have to be
|
||||
re-created to make full use of this feature.
|
||||
- Implement `Clone`, `Copy`, `Debug`, `Deserialize`, `Eq`, `Hash`, `Ord`, `PartialEq`, `PartialOrd`
|
||||
and `Serialize` on many types.
|
||||
- Add `MonitorHandle::current_video_mode()`.
|
||||
- Add `ApplicationHandlerExtMacOS` trait, and a `macos_handler` method to `ApplicationHandler` which returns a `dyn ApplicationHandlerExtMacOS` which allows for macOS specific extensions to winit.
|
||||
- Add a `standard_key_binding` method to the `ApplicationHandlerExtMacOS` trait. This allows handling of standard keybindings such as "go to end of line" on macOS.
|
||||
- On macOS, add `WindowExtMacOS::set_unified_titlebar` and `WindowAttributesExtMacOS::with_unified_titlebar`
|
||||
to use a larger style of titlebar.
|
||||
- Add `WindowId::into_raw()` and `from_raw()`.
|
||||
- Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId`, `primary` and `position` to all
|
||||
pointer events as part of the pointer event overhaul.
|
||||
- Add `DeviceId::into_raw()` and `from_raw()`.
|
||||
- Added `Window::surface_position`, which is the position of the surface inside the window.
|
||||
- Added `Window::safe_area`, which describes the area of the surface that is unobstructed.
|
||||
- On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes.
|
||||
- Add ability to make non-activating window on macOS using `NSPanel` with `NSWindowStyleMask::NonactivatingPanel`.
|
||||
- Implement `MonitorHandleProvider` for `MonitorHandle` to access common monitor API.
|
||||
- On X11, set an "area" attribute on XIM input connection to convey the cursor area.
|
||||
- Implement `CustomCursorProvider` for `CustomCursor` to access cursor API.
|
||||
- Add `CustomCursorSource::Url`, `CustomCursorSource::from_animation`.
|
||||
- Implement `CustomIconProvider` for `RgbaIcon`.
|
||||
- Add `icon` module that exposes winit's icon API.
|
||||
|
||||
### Changed
|
||||
|
||||
- Change `ActiveEventLoop` to be a trait.
|
||||
- Change `ActiveEventLoop` and `Window` to be traits, and added `cast_ref`/`cast_mut`/`cast`
|
||||
methods to extract the backend type from those.
|
||||
- `ActiveEventLoop::create_window` now returns `Box<dyn Window>`.
|
||||
- `ApplicationHandler` now uses `dyn ActiveEventLoop`.
|
||||
- On Web, let events wake up event loop immediately when using `ControlFlow::Poll`.
|
||||
- Bump MSRV from `1.70` to `1.73`.
|
||||
@@ -78,14 +95,14 @@ changelog entry.
|
||||
- Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now
|
||||
only wakes up the loop.
|
||||
- On X11, implement smooth resizing through the sync extension API.
|
||||
- `ApplicationHandler::create|destroy_surfaces()` was split off from
|
||||
- `ApplicationHandler::can_create|destroy_surfaces()` was split off from
|
||||
`ApplicationHandler::resumed/suspended()`.
|
||||
|
||||
`ApplicationHandler::can_create_surfaces()` should, for portability reasons
|
||||
to Android, be the only place to create render surfaces.
|
||||
|
||||
`ApplicationHandler::resumed/suspended()` are now only emitted by iOS and Web
|
||||
and now signify actually resuming/suspending the application.
|
||||
`ApplicationHandler::resumed/suspended()` are now only emitted by iOS, Web
|
||||
and Android, and now signify actually resuming/suspending the application.
|
||||
- Rename `platform::web::*ExtWebSys` to `*ExtWeb`.
|
||||
- Change signature of `EventLoop::run_app`, `EventLoopExtPumpEvents::pump_app_events` and
|
||||
`EventLoopExtRunOnDemand::run_app_on_demand` to accept a `impl ApplicationHandler` directly,
|
||||
@@ -97,6 +114,87 @@ changelog entry.
|
||||
- Changed how `ModifiersState` is serialized by Serde.
|
||||
- `VideoModeHandle::refresh_rate_millihertz()` and `bit_depth()` now return a `Option<NonZero*>`.
|
||||
- `MonitorHandle::position()` now returns an `Option`.
|
||||
- On macOS, remove custom application delegates. You are now allowed to override the
|
||||
application delegate yourself.
|
||||
- On X11, remove our dependency on libXcursor. (#3749)
|
||||
- Renamed the following APIs to make it clearer that the sizes apply to the underlying surface:
|
||||
- `WindowEvent::Resized` to `SurfaceResized`.
|
||||
- `InnerSizeWriter` to `SurfaceSizeWriter`.
|
||||
- `WindowAttributes.inner_size` to `surface_size`.
|
||||
- `WindowAttributes.min_inner_size` to `min_surface_size`.
|
||||
- `WindowAttributes.max_inner_size` to `max_surface_size`.
|
||||
- `WindowAttributes.resize_increments` to `surface_resize_increments`.
|
||||
- `WindowAttributes::with_inner_size` to `with_surface_size`.
|
||||
- `WindowAttributes::with_min_inner_size` to `with_min_surface_size`.
|
||||
- `WindowAttributes::with_max_inner_size` to `with_max_surface_size`.
|
||||
- `WindowAttributes::with_resize_increments` to `with_surface_resize_increments`.
|
||||
- `Window::inner_size` to `surface_size`.
|
||||
- `Window::request_inner_size` to `request_surface_size`.
|
||||
- `Window::set_min_inner_size` to `set_min_surface_size`.
|
||||
- `Window::set_max_inner_size` to `set_max_surface_size`.
|
||||
|
||||
To migrate, you can probably just replace all instances of `inner_size` with `surface_size` in your codebase.
|
||||
- Every event carrying a `DeviceId` now uses `Option<DeviceId>` instead. A `None` value signifies that the
|
||||
device can't be uniquely identified.
|
||||
- Pointer `WindowEvent`s were overhauled. The new events can handle any type of pointer, serving as
|
||||
a single pointer input source. Now your application can handle any pointer type without having to
|
||||
explicitly handle e.g. `Touch`:
|
||||
- Rename `CursorMoved` to `PointerMoved`.
|
||||
- Rename `CursorEntered` to `PointerEntered`.
|
||||
- Rename `CursorLeft` to `PointerLeft`.
|
||||
- Rename `MouseInput` to `PointerButton`.
|
||||
- Add `primary` to every `PointerEvent` as a way to identify discard non-primary pointers in a
|
||||
multi-touch interaction.
|
||||
- Add `position` to every `PointerEvent`.
|
||||
- `PointerMoved` is **not sent** after `PointerEntered` anymore.
|
||||
- Remove `Touch`, which is folded into the `Pointer*` events.
|
||||
- New `PointerKind` added to `PointerEntered` and `PointerLeft`, signifying which pointer type is
|
||||
the source of this event.
|
||||
- New `PointerSource` added to `PointerMoved`, similar to `PointerKind` but holding additional
|
||||
data.
|
||||
- New `ButtonSource` added to `PointerButton`, similar to `PointerKind` but holding pointer type
|
||||
specific buttons. Use `ButtonSource::mouse_button()` to easily normalize any pointer button
|
||||
type to a generic mouse button.
|
||||
- New `FingerId` added to `PointerKind::Touch` and `PointerSource::Touch` able to uniquely
|
||||
identify a finger in a multi-touch interaction. Replaces the old `Touch::id`.
|
||||
- In the same spirit rename `DeviceEvent::MouseMotion` to `PointerMotion`.
|
||||
- Remove `Force::Calibrated::altitude_angle`.
|
||||
- On X11, use bottom-right corner for IME hotspot in `Window::set_ime_cursor_area`.
|
||||
- On macOS and iOS, no longer emit `ScaleFactorChanged` upon window creation.
|
||||
- On macOS, no longer emit `Focused` upon window creation.
|
||||
- On iOS, emit more events immediately, instead of queuing them.
|
||||
- Update `smol_str` to version `0.3`
|
||||
- Rename `VideoModeHandle` to `VideoMode`, now it only stores plain data.
|
||||
- Make `Fullscreen::Exclusive` contain `(MonitorHandle, VideoMode)`.
|
||||
- Reworked the file drag-and-drop API.
|
||||
|
||||
The `WindowEvent::DroppedFile`, `WindowEvent::HoveredFile` and `WindowEvent::HoveredFileCancelled`
|
||||
events have been removed, and replaced with `WindowEvent::DragEntered`, `WindowEvent::DragMoved`,
|
||||
`WindowEvent::DragDropped` and `WindowEvent::DragLeft`.
|
||||
|
||||
The old drag-and-drop events were emitted once per file. This occurred when files were *first*
|
||||
hovered over the window, dropped, or left the window. The new drag-and-drop events are emitted
|
||||
once per set of files dragged, and include a list of all dragged files. They also include the
|
||||
pointer position.
|
||||
|
||||
The rough correspondence is:
|
||||
- `WindowEvent::HoveredFile` -> `WindowEvent::DragEntered`
|
||||
- `WindowEvent::DroppedFile` -> `WindowEvent::DragDropped`
|
||||
- `WindowEvent::HoveredFileCancelled` -> `WindowEvent::DragLeft`
|
||||
|
||||
The `WindowEvent::DragMoved` event is entirely new, and is emitted whenever the pointer moves
|
||||
whilst files are being dragged over the window. It doesn't contain any file paths, just the
|
||||
pointer position.
|
||||
- Updated `objc2` to `v0.6`.
|
||||
- Updated `windows-sys` to `v0.59`.
|
||||
- To match the corresponding changes in `windows-sys`, the `HWND`, `HMONITOR`, and `HMENU` types
|
||||
now alias to `*mut c_void` instead of `isize`.
|
||||
- Removed `KeyEventExtModifierSupplement`, and made the fields `text_with_all_modifiers` and
|
||||
`key_without_modifiers` public on `KeyEvent` instead.
|
||||
- Move `window::Fullscreen` to `monitor::Fullscreen`.
|
||||
- Renamed "super" key to "meta", to match the naming in the W3C specification.
|
||||
`NamedKey::Super` still exists, but it's non-functional and deprecated, `NamedKey::Meta` should be used instead.
|
||||
- Move `IconExtWindows` into `WinIcon`.
|
||||
|
||||
### Removed
|
||||
|
||||
@@ -119,14 +217,27 @@ changelog entry.
|
||||
v0.5 support. v0.6 remains in place and is enabled by default.
|
||||
- Remove `DeviceEvent::Added` and `DeviceEvent::Removed`.
|
||||
- Remove `DeviceEvent::Motion` and `WindowEvent::AxisMotion`.
|
||||
- Remove `Touch::id` in favor of `Touch::finger_id`.
|
||||
- Remove `MonitorHandle::size()` and `refresh_rate_millihertz()` in favor of
|
||||
`MonitorHandle::current_video_mode()`.
|
||||
- On Android, remove all `MonitorHandle` support instead of emitting false data.
|
||||
- Remove `impl From<u64> for WindowId` and `impl From<WindowId> for u64`. Replaced with
|
||||
`WindowId::into_raw()` and `from_raw()`.
|
||||
- Remove `dummy()` from `WindowId` and `DeviceId`.
|
||||
- Remove `WindowEvent::Touch` and `Touch` in favor of the new `PointerKind`, `PointerSource` and
|
||||
`ButtonSource` as part of the new pointer event overhaul.
|
||||
- Remove `Force::altitude_angle`.
|
||||
- Remove `Window::inner_position`, use the new `Window::surface_position` instead.
|
||||
- Remove `CustomCursorExtWeb`, use the `CustomCursorSource`.
|
||||
- Remove `CustomCursor::from_rgba`, use `CustomCursorSource` instead.
|
||||
- Remove `ApplicationHandler::exited`, the event loop being shut down can now be listened to in
|
||||
the `Drop` impl on the application handler.
|
||||
- Remove `NamedKey::Space`, match on `Key::Character(" ")` instead.
|
||||
- Remove `PartialEq` impl for `WindowAttributes`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On Web, pen events are now routed through to `WindowEvent::Cursor*`.
|
||||
- On macOS, fix panic when releasing not available monitor.
|
||||
- On MacOS, return the system theme in `Window::theme()` if no theme override is set.
|
||||
- On Orbital, `MonitorHandle::name()` now returns `None` instead of a dummy name.
|
||||
- On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface.
|
||||
- On macOS, fixed the scancode conversion for audio volume keys.
|
||||
- On macOS, fixed the scancode conversion for `IntlBackslash`.
|
||||
- On macOS, fixed redundant `SurfaceResized` event at window creation.
|
||||
|
||||
@@ -1,3 +1,104 @@
|
||||
## 0.30.10
|
||||
|
||||
### Added
|
||||
|
||||
- On Windows, add `IconExtWindows::from_resource_name`.
|
||||
- On Windows, add `CursorGrabMode::Locked`.
|
||||
- On Wayland, add `WindowExtWayland::xdg_toplevel`.
|
||||
|
||||
### Changed
|
||||
|
||||
- On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself).
|
||||
- On iOS, remove custom application delegates. You are now allowed to override the
|
||||
application delegate yourself.
|
||||
- On iOS, no longer act as-if the application successfully open all URLs. Override
|
||||
`application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw.
|
||||
- On macOS, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game`
|
||||
- On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode
|
||||
- On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus.
|
||||
- On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`.
|
||||
- On Wayland, apply fractional scaling to custom cursors.
|
||||
- On macOS, fixed `run_app_on_demand` returning without closing open windows.
|
||||
- On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates.
|
||||
- On macOS, store monitor handle to avoid panics after going in/out of sleep.
|
||||
- On macOS, allow certain invalid monitor handles and return `None` instead of panicking.
|
||||
- On Windows, fixed `Ime::Preedit` cursor offset calculation.
|
||||
|
||||
## 0.30.9
|
||||
|
||||
### Changed
|
||||
|
||||
- On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On X11, fix crash with uim.
|
||||
- On X11, fix modifiers for keys that were sent by the same X11 request.
|
||||
- On iOS, fix high CPU usage even when using `ControlFlow::Wait`.
|
||||
|
||||
## 0.30.8
|
||||
|
||||
### Added
|
||||
|
||||
- `ActivationToken::from_raw` and `ActivationToken::into_raw`.
|
||||
- On X11, add a workaround for disabling IME on GNOME.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On Windows, fixed the event loop not waking on accessibility requests.
|
||||
- On X11, fixed cursor grab mode state tracking on error.
|
||||
|
||||
## 0.30.7
|
||||
|
||||
### Fixed
|
||||
|
||||
- On X11, fixed KeyboardInput delivered twice when IME enabled.
|
||||
|
||||
## 0.30.6
|
||||
|
||||
### Added
|
||||
|
||||
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
|
||||
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
|
||||
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
|
||||
variables to test the respective modifiers of window creation.
|
||||
- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`.
|
||||
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize.
|
||||
- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the
|
||||
default activation policy, unless explicitly provided during initialization.
|
||||
- On macOS, fix crash when calling `drag_window()` without a left click present.
|
||||
- On X11, key events forward to IME anyway, even when it's disabled.
|
||||
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
|
||||
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
|
||||
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
|
||||
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
|
||||
- On macOS, fix crash when pressing Caps Lock in certain configurations.
|
||||
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.
|
||||
- On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor.
|
||||
|
||||
## 0.30.5
|
||||
|
||||
### Added
|
||||
|
||||
- Add `ActiveEventLoop::system_theme()`, returning the current system theme.
|
||||
- On Web, implement `Error` for `platform::web::CustomCursorError`.
|
||||
- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On MacOS, fix building with `feature = "rwh_04"`.
|
||||
- On Web, pen events are now routed through to `WindowEvent::Cursor*`.
|
||||
- On macOS, fix panic when releasing not available monitor.
|
||||
- On MacOS, return the system theme in `Window::theme()` if no theme override is set.
|
||||
|
||||
## 0.30.4
|
||||
|
||||
### Changed
|
||||
|
||||
216
src/cursor.rs
216
src/cursor.rs
@@ -1,13 +1,15 @@
|
||||
use core::fmt;
|
||||
use std::error::Error;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use cursor_icon::CursorIcon;
|
||||
|
||||
use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource};
|
||||
use crate::utils::{impl_dyn_casting, AsAny};
|
||||
|
||||
/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
|
||||
/// The maximum width and height for a cursor when using [`CustomCursorSource::from_rgba`].
|
||||
pub const MAX_CURSOR_SIZE: u16 = 2048;
|
||||
|
||||
const PIXEL_SIZE: usize = 4;
|
||||
@@ -50,71 +52,118 @@ impl From<CustomCursor> for Cursor {
|
||||
/// ```no_run
|
||||
/// # use winit::event_loop::ActiveEventLoop;
|
||||
/// # use winit::window::Window;
|
||||
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &Window) {
|
||||
/// use winit::window::CustomCursor;
|
||||
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) {
|
||||
/// use winit::window::CustomCursorSource;
|
||||
///
|
||||
/// let w = 10;
|
||||
/// let h = 10;
|
||||
/// let rgba = vec![255; (w * h * 4) as usize];
|
||||
///
|
||||
/// #[cfg(not(target_family = "wasm"))]
|
||||
/// let source = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
|
||||
/// let source = CustomCursorSource::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
|
||||
///
|
||||
/// #[cfg(target_family = "wasm")]
|
||||
/// let source = {
|
||||
/// use winit::platform::web::CustomCursorExtWeb;
|
||||
/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
|
||||
/// let source = CustomCursorSource::Url {
|
||||
/// url: String::from("http://localhost:3000/cursor.png"),
|
||||
/// hotspot_x: 0,
|
||||
/// hotspot_y: 0,
|
||||
/// };
|
||||
///
|
||||
/// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) {
|
||||
/// window.set_cursor(custom_cursor.clone());
|
||||
/// window.set_cursor(custom_cursor.clone().into());
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct CustomCursor {
|
||||
/// Platforms should make sure this is cheap to clone.
|
||||
pub(crate) inner: PlatformCustomCursor,
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CustomCursor(pub(crate) Arc<dyn CustomCursorProvider>);
|
||||
|
||||
pub trait CustomCursorProvider: AsAny + fmt::Debug + Send + Sync {
|
||||
/// Whether a cursor was backed by animation.
|
||||
fn is_animated(&self) -> bool;
|
||||
}
|
||||
|
||||
impl CustomCursor {
|
||||
/// Creates a new cursor from an rgba buffer.
|
||||
///
|
||||
/// The alpha channel is assumed to be **not** premultiplied.
|
||||
pub fn from_rgba(
|
||||
rgba: impl Into<Vec<u8>>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<CustomCursorSource, BadImage> {
|
||||
let _span =
|
||||
tracing::debug_span!("winit::Cursor::from_rgba", width, height, hotspot_x, hotspot_y)
|
||||
.entered();
|
||||
|
||||
Ok(CustomCursorSource {
|
||||
inner: PlatformCustomCursorSource::from_rgba(
|
||||
rgba.into(),
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
)?,
|
||||
})
|
||||
impl PartialEq for CustomCursor {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for CustomCursor {}
|
||||
|
||||
impl Hash for CustomCursor {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
Arc::as_ptr(&self.0).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CustomCursor {
|
||||
type Target = dyn CustomCursorProvider;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl_dyn_casting!(CustomCursorProvider);
|
||||
|
||||
/// Source for [`CustomCursor`].
|
||||
///
|
||||
/// See [`CustomCursor`] for more details.
|
||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
||||
pub struct CustomCursorSource {
|
||||
// Some platforms don't support custom cursors.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) inner: PlatformCustomCursorSource,
|
||||
pub enum CustomCursorSource {
|
||||
/// Cursor that is backed by RGBA image.
|
||||
///
|
||||
/// See [CustomCursorSource::from_rgba] for more.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Orbital:** Unsupported
|
||||
Image(CursorImage),
|
||||
/// Animated cursor.
|
||||
///
|
||||
/// See [CustomCursorSource::from_animation] for more.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported
|
||||
Animation(CursorAnimation),
|
||||
/// Creates a new cursor from a URL pointing to an image.
|
||||
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
|
||||
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
|
||||
///
|
||||
/// [PNG]: https://en.wikipedia.org/wiki/PNG
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported
|
||||
Url { hotspot_x: u16, hotspot_y: u16, url: String },
|
||||
}
|
||||
|
||||
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
|
||||
impl CustomCursorSource {
|
||||
/// Creates a new cursor from an rgba buffer.
|
||||
///
|
||||
/// The alpha channel is assumed to be **not** premultiplied.
|
||||
pub fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self::Image)
|
||||
}
|
||||
|
||||
/// Crates a new animated cursor from multiple [`CustomCursor`]s
|
||||
/// Supplied `cursors` can't be empty or other animations.
|
||||
pub fn from_animation(
|
||||
duration: Duration,
|
||||
cursors: Vec<CustomCursor>,
|
||||
) -> Result<Self, BadAnimation> {
|
||||
CursorAnimation::new(duration, cursors).map(Self::Animation)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error produced when using [`CustomCursorSource::from_rgba`] with invalid arguments.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum BadImage {
|
||||
@@ -164,47 +213,30 @@ impl fmt::Display for BadImage {
|
||||
|
||||
impl Error for BadImage {}
|
||||
|
||||
/// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with
|
||||
/// images.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
||||
pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
|
||||
/// An error produced when using [`CustomCursorSource::from_animation`] with invalid arguments.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum BadAnimation {
|
||||
/// Produced when no cursors were supplied.
|
||||
Empty,
|
||||
/// Produced when a supplied cursor is an animation.
|
||||
Animation,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl OnlyCursorImageSource {
|
||||
pub(crate) fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
|
||||
impl fmt::Display for BadAnimation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Empty => write!(f, "No cursors supplied"),
|
||||
Self::Animation => write!(f, "A supplied cursor is an animation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
|
||||
|
||||
impl Hash for OnlyCursorImage {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
Arc::as_ptr(&self.0).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for OnlyCursorImage {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for OnlyCursorImage {}
|
||||
impl Error for BadAnimation {}
|
||||
|
||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct CursorImage {
|
||||
pub struct CursorImage {
|
||||
pub(crate) rgba: Vec<u8>,
|
||||
pub(crate) width: u16,
|
||||
pub(crate) height: u16,
|
||||
@@ -247,20 +279,22 @@ impl CursorImage {
|
||||
}
|
||||
}
|
||||
|
||||
// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct NoCustomCursor;
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct CursorAnimation {
|
||||
pub(crate) duration: Duration,
|
||||
pub(crate) cursors: Vec<CustomCursor>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl NoCustomCursor {
|
||||
pub(crate) fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
|
||||
Ok(Self)
|
||||
impl CursorAnimation {
|
||||
pub fn new(duration: Duration, cursors: Vec<CustomCursor>) -> Result<Self, BadAnimation> {
|
||||
if cursors.is_empty() {
|
||||
return Err(BadAnimation::Empty);
|
||||
}
|
||||
|
||||
if cursors.iter().any(|cursor| cursor.is_animated()) {
|
||||
return Err(BadAnimation::Animation);
|
||||
}
|
||||
|
||||
Ok(Self { duration, cursors })
|
||||
}
|
||||
}
|
||||
|
||||
224
src/error.rs
224
src/error.rs
@@ -1,45 +1,40 @@
|
||||
use std::{error, fmt};
|
||||
use std::error::Error;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use crate::platform_impl;
|
||||
|
||||
// TODO: Rename
|
||||
/// An error that may be generated when requesting Winit state
|
||||
#[derive(Debug)]
|
||||
pub enum ExternalError {
|
||||
/// The operation is not supported by the backend.
|
||||
NotSupported(NotSupportedError),
|
||||
/// The operation was ignored.
|
||||
Ignored,
|
||||
/// The OS cannot perform the operation.
|
||||
Os(OsError),
|
||||
}
|
||||
|
||||
/// The error type for when the requested operation is not supported by the backend.
|
||||
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct NotSupportedError {
|
||||
_marker: (),
|
||||
}
|
||||
|
||||
/// The error type for when the OS cannot perform the requested operation.
|
||||
#[derive(Debug)]
|
||||
pub struct OsError {
|
||||
line: u32,
|
||||
file: &'static str,
|
||||
error: platform_impl::OsError,
|
||||
}
|
||||
|
||||
/// A general error that may occur while running the Winit event loop
|
||||
/// A general error that may occur while running or creating
|
||||
/// the event loop.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum EventLoopError {
|
||||
/// The operation is not supported by the backend.
|
||||
NotSupported(NotSupportedError),
|
||||
/// The OS cannot perform the operation.
|
||||
Os(OsError),
|
||||
/// The event loop can't be re-created.
|
||||
RecreationAttempt,
|
||||
/// Application has exit with an error status.
|
||||
ExitFailure(i32),
|
||||
/// Got unspecified OS-specific error during the request.
|
||||
Os(OsError),
|
||||
/// Creating the event loop with the requested configuration is not supported.
|
||||
NotSupported(NotSupportedError),
|
||||
}
|
||||
|
||||
impl fmt::Display for EventLoopError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
|
||||
Self::Os(err) => err.fmt(f),
|
||||
Self::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
|
||||
Self::NotSupported(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for EventLoopError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
if let Self::Os(err) = self {
|
||||
err.source()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OsError> for EventLoopError {
|
||||
@@ -48,18 +43,102 @@ impl From<OsError> for EventLoopError {
|
||||
}
|
||||
}
|
||||
|
||||
impl NotSupportedError {
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn new() -> NotSupportedError {
|
||||
NotSupportedError { _marker: () }
|
||||
impl From<NotSupportedError> for EventLoopError {
|
||||
fn from(value: NotSupportedError) -> Self {
|
||||
Self::NotSupported(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A general error that may occur during a request to the windowing system.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum RequestError {
|
||||
/// The request is not supported.
|
||||
NotSupported(NotSupportedError),
|
||||
/// The request was ignored by the operating system.
|
||||
Ignored,
|
||||
/// Got unspecified OS specific error during the request.
|
||||
Os(OsError),
|
||||
}
|
||||
|
||||
impl Display for RequestError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::NotSupported(err) => err.fmt(f),
|
||||
Self::Ignored => write!(f, "The request was ignored"),
|
||||
Self::Os(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Error for RequestError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
if let Self::Os(err) = self {
|
||||
err.source()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NotSupportedError> for RequestError {
|
||||
fn from(value: NotSupportedError) -> Self {
|
||||
Self::NotSupported(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OsError> for RequestError {
|
||||
fn from(value: OsError) -> Self {
|
||||
Self::Os(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// The requested operation is not supported.
|
||||
#[derive(Debug)]
|
||||
pub struct NotSupportedError {
|
||||
/// The reason why a certain operation is not supported.
|
||||
reason: &'static str,
|
||||
}
|
||||
|
||||
impl NotSupportedError {
|
||||
pub(crate) fn new(reason: &'static str) -> Self {
|
||||
Self { reason }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NotSupportedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Operation is not supported: {}", self.reason)
|
||||
}
|
||||
}
|
||||
impl Error for NotSupportedError {}
|
||||
|
||||
/// Unclassified error from the OS.
|
||||
#[derive(Debug)]
|
||||
pub struct OsError {
|
||||
line: u32,
|
||||
file: &'static str,
|
||||
error: Box<dyn Error + Send + Sync + 'static>,
|
||||
}
|
||||
|
||||
impl OsError {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError {
|
||||
OsError { line, file, error }
|
||||
pub(crate) fn new(
|
||||
line: u32,
|
||||
file: &'static str,
|
||||
error: impl Into<Box<dyn Error + Send + Sync + 'static>>,
|
||||
) -> Self {
|
||||
Self { line, file, error: error.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for OsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad(&format!("os error at {}:{}: {}", self.file, self.line, self.error))
|
||||
}
|
||||
}
|
||||
impl Error for OsError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(self.error.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,64 +148,3 @@ macro_rules! os_error {
|
||||
crate::error::OsError::new(line!(), file!(), $error)
|
||||
}};
|
||||
}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.pad(&format!("os error at {}:{}: {}", self.file, self.line, self.error))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ExternalError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
ExternalError::NotSupported(e) => e.fmt(f),
|
||||
ExternalError::Ignored => write!(f, "Operation was ignored"),
|
||||
ExternalError::Os(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NotSupportedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("NotSupportedError").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NotSupportedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.pad("the requested operation is not supported by Winit")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EventLoopError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
EventLoopError::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
|
||||
EventLoopError::NotSupported(e) => e.fmt(f),
|
||||
EventLoopError::Os(e) => e.fmt(f),
|
||||
EventLoopError::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for OsError {}
|
||||
impl error::Error for ExternalError {}
|
||||
impl error::Error for NotSupportedError {}
|
||||
impl error::Error for EventLoopError {}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::redundant_clone)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Eat attributes for testing
|
||||
#[test]
|
||||
fn ensure_fmt_does_not_panic() {
|
||||
let _ = format!("{:?}, {}", NotSupportedError::new(), NotSupportedError::new().clone());
|
||||
let _ = format!(
|
||||
"{:?}, {}",
|
||||
ExternalError::NotSupported(NotSupportedError::new()),
|
||||
ExternalError::NotSupported(NotSupportedError::new())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
889
src/event.rs
889
src/event.rs
File diff suppressed because it is too large
Load Diff
@@ -8,22 +8,24 @@
|
||||
//!
|
||||
//! See the root-level documentation for information on how to create and use an event loop to
|
||||
//! handle events.
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
#[cfg(not(web_platform))]
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
|
||||
#[cfg(web_platform)]
|
||||
use web_time::{Duration, Instant};
|
||||
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::error::{EventLoopError, ExternalError, OsError};
|
||||
use crate::error::{EventLoopError, RequestError};
|
||||
use crate::monitor::MonitorHandle;
|
||||
use crate::platform_impl;
|
||||
use crate::utils::{impl_dyn_casting, AsAny};
|
||||
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
|
||||
|
||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
||||
@@ -41,6 +43,7 @@ use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttri
|
||||
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoop {
|
||||
pub(crate) event_loop: platform_impl::EventLoop,
|
||||
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
|
||||
@@ -52,7 +55,7 @@ pub struct EventLoop {
|
||||
/// easier. But note that constructing multiple event loops is not supported.
|
||||
///
|
||||
/// This can be created using [`EventLoop::builder`].
|
||||
#[derive(Default, PartialEq, Eq, Hash)]
|
||||
#[derive(Default, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct EventLoopBuilder {
|
||||
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
|
||||
}
|
||||
@@ -72,7 +75,7 @@ impl EventLoopBuilder {
|
||||
/// Attempting to create the event loop off the main thread will panic. This
|
||||
/// restriction isn't strictly necessary on all platforms, but is imposed to
|
||||
/// eliminate any nasty surprises when porting to platforms that require it.
|
||||
/// `EventLoopBuilderExt::any_thread` functions are exposed in the relevant
|
||||
/// `EventLoopBuilderExt::with_any_thread` functions are exposed in the relevant
|
||||
/// [`platform`] module if the target platform supports creating an event
|
||||
/// loop on any thread.
|
||||
///
|
||||
@@ -115,18 +118,6 @@ impl EventLoopBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for EventLoopBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("EventLoopBuilder").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for EventLoop {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("EventLoop").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// Set through [`ActiveEventLoop::set_control_flow()`].
|
||||
///
|
||||
/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called.
|
||||
@@ -195,7 +186,56 @@ impl EventLoop {
|
||||
impl EventLoop {
|
||||
/// Run the application with the event loop on the calling thread.
|
||||
///
|
||||
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
|
||||
/// ## Event loop flow
|
||||
///
|
||||
/// This function internally handles the different parts of a traditional event-handling loop.
|
||||
/// You can imagine this method as being implemented like this:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let mut start_cause = StartCause::Init;
|
||||
///
|
||||
/// // Run the event loop.
|
||||
/// while !event_loop.exiting() {
|
||||
/// // Wake up.
|
||||
/// app.new_events(event_loop, start_cause);
|
||||
///
|
||||
/// // Indicate that surfaces can now safely be created.
|
||||
/// if start_cause == StartCause::Init {
|
||||
/// app.can_create_surfaces(event_loop);
|
||||
/// }
|
||||
///
|
||||
/// // Handle proxy wake-up event.
|
||||
/// if event_loop.proxy_wake_up_set() {
|
||||
/// event_loop.proxy_wake_up_clear();
|
||||
/// app.proxy_wake_up(event_loop);
|
||||
/// }
|
||||
///
|
||||
/// // Handle actions done by the user / system such as moving the cursor, resizing the
|
||||
/// // window, changing the window theme, etc.
|
||||
/// for event in event_loop.events() {
|
||||
/// match event {
|
||||
/// window event => app.window_event(event_loop, window_id, event),
|
||||
/// device event => app.device_event(event_loop, device_id, event),
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Handle redraws.
|
||||
/// for window_id in event_loop.pending_redraws() {
|
||||
/// app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
|
||||
/// }
|
||||
///
|
||||
/// // Done handling events, wait until we're woken up again.
|
||||
/// app.about_to_wait(event_loop);
|
||||
/// start_cause = event_loop.wait_if_necessary();
|
||||
/// }
|
||||
///
|
||||
/// // Finished running, drop application state.
|
||||
/// drop(app);
|
||||
/// ```
|
||||
///
|
||||
/// This is of course a very coarse-grained overview, and leaves out timing details like
|
||||
/// [`ControlFlow::WaitUntil`] and life-cycle methods like [`ApplicationHandler::resumed`], but
|
||||
/// it should give you an idea of how things fit together.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
@@ -207,10 +247,10 @@ impl EventLoop {
|
||||
///
|
||||
/// Web applications are recommended to use
|
||||
#[cfg_attr(
|
||||
any(web_platform, docsrs),
|
||||
web_platform,
|
||||
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
|
||||
)]
|
||||
#[cfg_attr(not(any(web_platform, docsrs)), doc = " `EventLoopExtWeb::spawn_app()`")]
|
||||
#[cfg_attr(not(web_platform), doc = " `EventLoopExtWeb::spawn_app()`")]
|
||||
/// [^1] instead of [`run_app()`] to avoid the need for the Javascript exception trick, and to
|
||||
/// make it clearer that the event loop runs asynchronously (via the browser's own,
|
||||
/// internal, event loop) and doesn't block the current thread of execution like it does
|
||||
@@ -268,15 +308,14 @@ impl EventLoop {
|
||||
pub fn create_custom_cursor(
|
||||
&self,
|
||||
custom_cursor: CustomCursorSource,
|
||||
) -> Result<CustomCursor, ExternalError> {
|
||||
) -> Result<CustomCursor, RequestError> {
|
||||
self.event_loop.window_target().create_custom_cursor(custom_cursor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl rwh_06::HasDisplayHandle for EventLoop {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
rwh_06::HasDisplayHandle::display_handle(self.event_loop.window_target().rwh_06_handle())
|
||||
impl HasDisplayHandle for EventLoop {
|
||||
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
|
||||
HasDisplayHandle::display_handle(self.event_loop.window_target().rwh_06_handle())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +347,7 @@ impl AsRawFd for EventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ActiveEventLoop {
|
||||
pub trait ActiveEventLoop: AsAny + fmt::Debug {
|
||||
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
|
||||
/// to the main event loop, possibly from another thread.
|
||||
fn create_proxy(&self) -> EventLoopProxy;
|
||||
@@ -321,7 +360,10 @@ pub trait ActiveEventLoop {
|
||||
///
|
||||
/// - **Web:** The window is created but not inserted into the Web page automatically. Please
|
||||
/// see the Web platform module for more information.
|
||||
fn create_window(&self, window_attributes: WindowAttributes) -> Result<Window, OsError>;
|
||||
fn create_window(
|
||||
&self,
|
||||
window_attributes: WindowAttributes,
|
||||
) -> Result<Box<dyn Window>, RequestError>;
|
||||
|
||||
/// Create custom cursor.
|
||||
///
|
||||
@@ -331,7 +373,7 @@ pub trait ActiveEventLoop {
|
||||
fn create_custom_cursor(
|
||||
&self,
|
||||
custom_cursor: CustomCursorSource,
|
||||
) -> Result<CustomCursor, ExternalError>;
|
||||
) -> Result<CustomCursor, RequestError>;
|
||||
|
||||
/// Returns the list of all the monitors available on the system.
|
||||
///
|
||||
@@ -339,10 +381,10 @@ pub trait ActiveEventLoop {
|
||||
///
|
||||
/// **Web:** Only returns the current monitor without
|
||||
#[cfg_attr(
|
||||
any(web_platform, docsrs),
|
||||
web_platform,
|
||||
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
|
||||
)]
|
||||
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
|
||||
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = MonitorHandle>>;
|
||||
|
||||
/// Returns the primary monitor of the system.
|
||||
@@ -354,10 +396,10 @@ pub trait ActiveEventLoop {
|
||||
/// - **Wayland:** Always returns `None`.
|
||||
/// - **Web:** Always returns `None` without
|
||||
#[cfg_attr(
|
||||
any(web_platform, docsrs),
|
||||
web_platform,
|
||||
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
|
||||
)]
|
||||
#[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")]
|
||||
#[cfg_attr(not(web_platform), doc = " detailed monitor permissions.")]
|
||||
fn primary_monitor(&self) -> Option<MonitorHandle>;
|
||||
|
||||
/// Change if or when [`DeviceEvent`]s are captured.
|
||||
@@ -388,14 +430,21 @@ pub trait ActiveEventLoop {
|
||||
/// Gets the current [`ControlFlow`].
|
||||
fn control_flow(&self) -> ControlFlow;
|
||||
|
||||
/// This exits the event loop.
|
||||
/// Stop the event loop.
|
||||
///
|
||||
/// See [`exiting`][crate::application::ApplicationHandler::exiting].
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// ### iOS
|
||||
///
|
||||
/// It is not possible to programmatically exit/quit an application on iOS, so this function is
|
||||
/// a no-op there. See also [this technical Q&A][qa1561].
|
||||
///
|
||||
/// [qa1561]: https://developer.apple.com/library/archive/qa/qa1561/_index.html
|
||||
fn exit(&self);
|
||||
|
||||
/// Returns if the [`EventLoop`] is about to stop.
|
||||
/// Returns whether the [`EventLoop`] is about to stop.
|
||||
///
|
||||
/// See [`exit()`][Self::exit].
|
||||
/// Set by [`exit()`][Self::exit].
|
||||
fn exiting(&self) -> bool;
|
||||
|
||||
/// Gets a persistent reference to the underlying platform display.
|
||||
@@ -403,19 +452,21 @@ pub trait ActiveEventLoop {
|
||||
/// See the [`OwnedDisplayHandle`] type for more information.
|
||||
fn owned_display_handle(&self) -> OwnedDisplayHandle;
|
||||
|
||||
/// Get the [`ActiveEventLoop`] as [`Any`].
|
||||
///
|
||||
/// This is useful for downcasting to a concrete event loop type.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Get the raw-window-handle handle.
|
||||
#[cfg(feature = "rwh_06")]
|
||||
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle;
|
||||
fn rwh_06_handle(&self) -> &dyn HasDisplayHandle;
|
||||
}
|
||||
|
||||
impl HasDisplayHandle for dyn ActiveEventLoop + '_ {
|
||||
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
|
||||
self.rwh_06_handle().display_handle()
|
||||
}
|
||||
}
|
||||
|
||||
impl_dyn_casting!(ActiveEventLoop);
|
||||
|
||||
/// A proxy for the underlying display handle.
|
||||
///
|
||||
/// The purpose of this type is to provide a cheaply clonable handle to the underlying
|
||||
/// The purpose of this type is to provide a cheaply cloneable handle to the underlying
|
||||
/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
|
||||
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`]
|
||||
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as
|
||||
@@ -425,36 +476,49 @@ pub trait ActiveEventLoop {
|
||||
///
|
||||
/// - A zero-sized type that is likely optimized out.
|
||||
/// - A reference-counted pointer to the underlying type.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Clone)]
|
||||
pub struct OwnedDisplayHandle {
|
||||
#[cfg_attr(not(feature = "rwh_06"), allow(dead_code))]
|
||||
pub(crate) platform: platform_impl::OwnedDisplayHandle,
|
||||
pub(crate) handle: Arc<dyn HasDisplayHandle>,
|
||||
}
|
||||
|
||||
impl OwnedDisplayHandle {
|
||||
pub(crate) fn new(handle: Arc<dyn HasDisplayHandle>) -> Self {
|
||||
Self { handle }
|
||||
}
|
||||
}
|
||||
|
||||
impl HasDisplayHandle for OwnedDisplayHandle {
|
||||
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
|
||||
self.handle.display_handle()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for OwnedDisplayHandle {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
|
||||
#[inline]
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = self.platform.raw_display_handle_rwh_06()?;
|
||||
|
||||
// SAFETY: The underlying display handle should be safe.
|
||||
let handle = unsafe { rwh_06::DisplayHandle::borrow_raw(raw) };
|
||||
|
||||
Ok(handle)
|
||||
impl PartialEq for OwnedDisplayHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self.display_handle(), other.display_handle()) {
|
||||
(Ok(lhs), Ok(rhs)) => lhs == rhs,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for OwnedDisplayHandle {}
|
||||
|
||||
pub(crate) trait EventLoopProxyProvider: Send + Sync + fmt::Debug {
|
||||
/// See [`EventLoopProxy::wake_up`] for details.
|
||||
fn wake_up(&self);
|
||||
}
|
||||
|
||||
/// Control the [`EventLoop`], possibly from a different thread, without referencing it directly.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EventLoopProxy {
|
||||
pub(crate) event_loop_proxy: platform_impl::EventLoopProxy,
|
||||
pub(crate) proxy: Arc<dyn EventLoopProxyProvider>,
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
@@ -474,13 +538,11 @@ impl EventLoopProxy {
|
||||
///
|
||||
/// [#3687]: https://github.com/rust-windowing/winit/pull/3687
|
||||
pub fn wake_up(&self) {
|
||||
self.event_loop_proxy.wake_up();
|
||||
self.proxy.wake_up();
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for EventLoopProxy {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ActiveEventLoop").finish_non_exhaustive()
|
||||
pub(crate) fn new(proxy: Arc<dyn EventLoopProxyProvider>) -> Self {
|
||||
Self { proxy }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
104
src/icon.rs
104
src/icon.rs
@@ -1,7 +1,20 @@
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, io, mem};
|
||||
|
||||
use crate::platform_impl::PlatformIcon;
|
||||
use crate::utils::{impl_dyn_casting, AsAny};
|
||||
|
||||
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
||||
|
||||
/// An icon used for the window titlebar, taskbar, etc.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Icon(pub(crate) Arc<dyn IconProvider>);
|
||||
|
||||
// TODO remove that once split.
|
||||
pub trait IconProvider: AsAny + fmt::Debug + Send + Sync {}
|
||||
|
||||
impl_dyn_casting!(IconProvider);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
@@ -12,10 +25,8 @@ pub(crate) struct Pixel {
|
||||
pub(crate) a: u8,
|
||||
}
|
||||
|
||||
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
||||
|
||||
#[derive(Debug)]
|
||||
/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
|
||||
/// An error produced when using [`RgbaIcon::new`] with invalid arguments.
|
||||
pub enum BadIcon {
|
||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
||||
/// safely interpreted as 32bpp RGBA pixels.
|
||||
@@ -50,69 +61,48 @@ impl fmt::Display for BadIcon {
|
||||
|
||||
impl Error for BadIcon {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct RgbaIcon {
|
||||
pub(crate) rgba: Vec<u8>,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RgbaIcon {
|
||||
pub(crate) width: u32,
|
||||
pub(crate) height: u32,
|
||||
pub(crate) rgba: Vec<u8>,
|
||||
}
|
||||
|
||||
/// For platforms which don't have window icons (e.g. Web)
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NoIcon;
|
||||
|
||||
#[allow(dead_code)] // These are not used on every platform
|
||||
mod constructors {
|
||||
use super::*;
|
||||
|
||||
impl RgbaIcon {
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
if rgba.len() % PIXEL_SIZE != 0 {
|
||||
return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
|
||||
}
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
if pixel_count != (width * height) as usize {
|
||||
Err(BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height: (width * height) as usize,
|
||||
pixel_count,
|
||||
})
|
||||
} else {
|
||||
Ok(RgbaIcon { rgba, width, height })
|
||||
}
|
||||
impl RgbaIcon {
|
||||
pub fn new(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
if rgba.len() % PIXEL_SIZE != 0 {
|
||||
return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
|
||||
}
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
if pixel_count != (width * height) as usize {
|
||||
Err(BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height: (width * height) as usize,
|
||||
pixel_count,
|
||||
})
|
||||
} else {
|
||||
Ok(RgbaIcon { rgba, width, height })
|
||||
}
|
||||
}
|
||||
|
||||
impl NoIcon {
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
// Create the rgba icon anyway to validate the input
|
||||
let _ = RgbaIcon::from_rgba(rgba, width, height)?;
|
||||
Ok(NoIcon)
|
||||
}
|
||||
pub fn width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn buffer(&self) -> &[u8] {
|
||||
self.rgba.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
/// An icon used for the window titlebar, taskbar, etc.
|
||||
#[derive(Clone, Eq, Hash, PartialEq)]
|
||||
pub struct Icon {
|
||||
pub(crate) inner: PlatformIcon,
|
||||
}
|
||||
impl IconProvider for RgbaIcon {}
|
||||
|
||||
impl fmt::Debug for Icon {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
fmt::Debug::fmt(&self.inner, formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
/// Creates an icon from 32bpp RGBA data.
|
||||
///
|
||||
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
|
||||
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
let _span = tracing::debug_span!("winit::Icon::from_rgba", width, height).entered();
|
||||
|
||||
Ok(Icon { inner: PlatformIcon::from_rgba(rgba, width, height)? })
|
||||
impl From<RgbaIcon> for Icon {
|
||||
fn from(value: RgbaIcon) -> Self {
|
||||
Self(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Types related to the keyboard.
|
||||
#![cfg_attr(feature = "serde", allow(deprecated))] // https://github.com/serde-rs/serde/issues/2195
|
||||
|
||||
// This file contains a substantial portion of the UI Events Specification by the W3C. In
|
||||
// particular, the variant names within `Key` and `KeyCode` and their documentation are modified
|
||||
@@ -283,11 +284,7 @@ impl PartialEq<PhysicalKey> for NativeKeyCode {
|
||||
|
||||
/// Code representing the location of a physical key
|
||||
///
|
||||
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few
|
||||
/// exceptions:
|
||||
/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and
|
||||
/// "SuperRight" here.
|
||||
/// - The key that the specification calls "Super" is reported as `Unidentified` here.
|
||||
/// This conforms to the UI Events Specification's [`KeyboardEvent.code`].
|
||||
///
|
||||
/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
|
||||
#[non_exhaustive]
|
||||
@@ -420,7 +417,7 @@ pub enum KeyCode {
|
||||
/// <kbd>CapsLock</kbd> or <kbd>⇪</kbd>
|
||||
CapsLock,
|
||||
/// The application context menu key, which is typically found between the right
|
||||
/// <kbd>Super</kbd> key and the right <kbd>Control</kbd> key.
|
||||
/// <kbd>Meta</kbd> key and the right <kbd>Control</kbd> key.
|
||||
ContextMenu,
|
||||
/// <kbd>Control</kbd> or <kbd>⌃</kbd>
|
||||
ControlLeft,
|
||||
@@ -429,9 +426,9 @@ pub enum KeyCode {
|
||||
/// <kbd>Enter</kbd> or <kbd>↵</kbd>. Labeled <kbd>Return</kbd> on Apple keyboards.
|
||||
Enter,
|
||||
/// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key.
|
||||
SuperLeft,
|
||||
MetaLeft,
|
||||
/// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key.
|
||||
SuperRight,
|
||||
MetaRight,
|
||||
/// <kbd>Shift</kbd> or <kbd>⇧</kbd>
|
||||
ShiftLeft,
|
||||
/// <kbd>Shift</kbd> or <kbd>⇧</kbd>
|
||||
@@ -613,9 +610,11 @@ pub enum KeyCode {
|
||||
AudioVolumeMute,
|
||||
AudioVolumeUp,
|
||||
WakeUp,
|
||||
// Legacy modifier key. Also called "Super" in certain places.
|
||||
Meta,
|
||||
// Legacy modifier key.
|
||||
#[deprecated = "marked as legacy in the spec, use Meta instead"]
|
||||
Super,
|
||||
// Legacy modifier key.
|
||||
#[deprecated = "marked as legacy in the spec, use Meta instead"]
|
||||
Hyper,
|
||||
Turbo,
|
||||
Abort,
|
||||
@@ -741,12 +740,7 @@ pub enum KeyCode {
|
||||
|
||||
/// A [`Key::Named`] value
|
||||
///
|
||||
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few
|
||||
/// exceptions:
|
||||
/// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's
|
||||
/// another key which the specification calls `Super`. That does not exist here.)
|
||||
/// - The `Space` variant here, can be identified by the character it generates in the
|
||||
/// specification.
|
||||
/// This conforms to the UI Events Specification's [`KeyboardEvent.key`].
|
||||
///
|
||||
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
|
||||
#[non_exhaustive]
|
||||
@@ -791,24 +785,22 @@ pub enum NamedKey {
|
||||
/// The Symbol modifier key (used on some virtual keyboards).
|
||||
Symbol,
|
||||
SymbolLock,
|
||||
// Legacy modifier key. Also called "Super" in certain places.
|
||||
Meta,
|
||||
// Legacy modifier key.
|
||||
#[deprecated = "marked as legacy in the spec, use Meta instead"]
|
||||
Super,
|
||||
// Legacy modifier key.
|
||||
#[deprecated = "marked as legacy in the spec, use Meta instead"]
|
||||
Hyper,
|
||||
/// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard
|
||||
/// Used to enable "meta" modifier function for interpreting concurrent or subsequent keyboard
|
||||
/// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘`
|
||||
/// key.
|
||||
///
|
||||
/// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key.
|
||||
Super,
|
||||
Meta,
|
||||
/// The `Enter` or `↵` key. Used to activate current selection or accept current input. This
|
||||
/// key value is also used for the `Return` (Macintosh numpad) key. This key value is also
|
||||
/// used for the Android `KEYCODE_DPAD_CENTER`.
|
||||
Enter,
|
||||
/// The Horizontal Tabulation `Tab` key.
|
||||
Tab,
|
||||
/// Used in text to insert a space between words. Usually located below the character keys.
|
||||
Space,
|
||||
/// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`)
|
||||
ArrowDown,
|
||||
/// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`)
|
||||
@@ -864,7 +856,7 @@ pub enum NamedKey {
|
||||
Attn,
|
||||
Cancel,
|
||||
/// Show the application’s context menu.
|
||||
/// This key is commonly found between the right `Super` key and the right `Control` key.
|
||||
/// This key is commonly found between the right `Meta` key and the right `Control` key.
|
||||
ContextMenu,
|
||||
/// The `Esc` key. This key was originally used to initiate an escape sequence, but is
|
||||
/// now more generally used to exit or "escape" the current context, such as closing a dialog
|
||||
@@ -1583,7 +1575,6 @@ impl NamedKey {
|
||||
NamedKey::Enter => Some("\r"),
|
||||
NamedKey::Backspace => Some("\x08"),
|
||||
NamedKey::Tab => Some("\t"),
|
||||
NamedKey::Space => Some(" "),
|
||||
NamedKey::Escape => Some("\x1b"),
|
||||
_ => None,
|
||||
}
|
||||
@@ -1709,7 +1700,9 @@ bitflags! {
|
||||
/// The "alt" key.
|
||||
const ALT = 0b100 << 6;
|
||||
/// This is the "windows" key on PC and "command" key on Mac.
|
||||
const SUPER = 0b100 << 9;
|
||||
const META = 0b100 << 9;
|
||||
#[deprecated = "use META instead"]
|
||||
const SUPER = Self::META.bits();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1729,9 +1722,9 @@ impl ModifiersState {
|
||||
self.intersects(Self::ALT)
|
||||
}
|
||||
|
||||
/// Returns `true` if the super key is pressed.
|
||||
pub fn super_key(&self) -> bool {
|
||||
self.intersects(Self::SUPER)
|
||||
/// Returns `true` if the meta key is pressed.
|
||||
pub fn meta_key(&self) -> bool {
|
||||
self.intersects(Self::META)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1764,7 +1757,11 @@ bitflags! {
|
||||
const RCONTROL = 0b0000_1000;
|
||||
const LALT = 0b0001_0000;
|
||||
const RALT = 0b0010_0000;
|
||||
const LSUPER = 0b0100_0000;
|
||||
const RSUPER = 0b1000_0000;
|
||||
const LMETA = 0b0100_0000;
|
||||
const RMETA = 0b1000_0000;
|
||||
#[deprecated = "use LMETA instead"]
|
||||
const LSUPER = Self::LMETA.bits();
|
||||
#[deprecated = "use RMETA instead"]
|
||||
const RSUPER = Self::RMETA.bits();
|
||||
}
|
||||
}
|
||||
|
||||
163
src/lib.rs
163
src/lib.rs
@@ -7,7 +7,12 @@
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use winit::event_loop::EventLoop;
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//!
|
||||
//! # // Intentionally use `fn main` for clarity
|
||||
//! fn main() {
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! // ...
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Then you create a [`Window`] with [`create_window`].
|
||||
@@ -21,9 +26,8 @@
|
||||
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
|
||||
//! [`DeviceEvent`].
|
||||
//!
|
||||
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
|
||||
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
|
||||
//! will run until [`exit()`] is used, at which point [`exiting()`] is called.
|
||||
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will dispatch events
|
||||
//! for every [`Window`] that was created with that particular [`EventLoop`].
|
||||
//!
|
||||
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
||||
//! model, since that can't be implemented properly on some platforms (e.g Web, iOS) and works
|
||||
@@ -44,19 +48,28 @@
|
||||
//! use winit::application::ApplicationHandler;
|
||||
//! use winit::event::WindowEvent;
|
||||
//! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
|
||||
//! use winit::window::{Window, WindowId};
|
||||
//! use winit::window::{Window, WindowId, WindowAttributes};
|
||||
//!
|
||||
//! #[derive(Default)]
|
||||
//! struct App {
|
||||
//! window: Option<Window>,
|
||||
//! window: Option<Box<dyn Window>>,
|
||||
//! }
|
||||
//!
|
||||
//! impl ApplicationHandler for App {
|
||||
//! fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
//! self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
|
||||
//! // The event loop has launched, and we can initialize our UI state.
|
||||
//!
|
||||
//! // Create a simple window with default attributes.
|
||||
//! self.window = Some(event_loop.create_window(WindowAttributes::default()).unwrap());
|
||||
//! }
|
||||
//!
|
||||
//! fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, id: WindowId, event: WindowEvent) {
|
||||
//! fn window_event(
|
||||
//! &mut self,
|
||||
//! event_loop: &dyn ActiveEventLoop,
|
||||
//! id: WindowId,
|
||||
//! event: WindowEvent,
|
||||
//! ) {
|
||||
//! // Called by `EventLoop::run_app` when a new event happens on the window.
|
||||
//! match event {
|
||||
//! WindowEvent::CloseRequested => {
|
||||
//! println!("The close button was pressed; stopping");
|
||||
@@ -77,25 +90,33 @@
|
||||
//! // applications which do not always need to. Applications that redraw continuously
|
||||
//! // can render here instead.
|
||||
//! self.window.as_ref().unwrap().request_redraw();
|
||||
//! }
|
||||
//! },
|
||||
//! _ => (),
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! # // Intentionally use `fn main` for clarity
|
||||
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! // Create a new event loop.
|
||||
//! let event_loop = EventLoop::new()?;
|
||||
//!
|
||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
||||
//! // dispatched any events. This is ideal for games and similar applications.
|
||||
//! event_loop.set_control_flow(ControlFlow::Poll);
|
||||
//! // Configure settings before launching.
|
||||
//!
|
||||
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
|
||||
//! // This is ideal for non-game applications that only update in response to user
|
||||
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
||||
//! event_loop.set_control_flow(ControlFlow::Wait);
|
||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
||||
//! // dispatched any events. This is ideal for games and similar applications.
|
||||
//! event_loop.set_control_flow(ControlFlow::Poll);
|
||||
//!
|
||||
//! let mut app = App::default();
|
||||
//! event_loop.run_app(&mut app);
|
||||
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
|
||||
//! // This is ideal for non-game applications that only update in response to user
|
||||
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
||||
//! event_loop.set_control_flow(ControlFlow::Wait);
|
||||
//!
|
||||
//! // Launch and begin running the event loop.
|
||||
//! event_loop.run_app(App::default())?;
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
|
||||
@@ -115,6 +136,45 @@
|
||||
//! [`visible` set to `false`][crate::window::WindowAttributes::with_visible] and explicitly make
|
||||
//! the window visible only once you're ready to render into it.
|
||||
//!
|
||||
//! There is another important concept you need to know about when drawing: the "safe area". This
|
||||
//! can be accessed with [`Window::safe_area`], and describes a rectangle in the surface that is not
|
||||
//! obscured by notches, the status bar, and so on. You should be drawing your background and
|
||||
//! non-important content on the entire surface, but restrict important content (such as
|
||||
//! interactable UIs, text, etc.) to only being drawn inside the safe area.
|
||||
//!
|
||||
//! [`Window::safe_area`]: crate::window::Window::safe_area
|
||||
//!
|
||||
//! # Coordinate systems
|
||||
//!
|
||||
//! Windowing systems use many different coordinate systems, and this is reflected in Winit as well;
|
||||
//! there are "desktop coordinates", which is the coordinates of a window or monitor relative to the
|
||||
//! desktop at large, "window coordinates" which is the coordinates of the surface, relative to the
|
||||
//! window, and finally "surface coordinates", which is the coordinates relative to the drawn
|
||||
//! surface. All of these coordinates are relative to the top-left corner of their respective
|
||||
//! origin.
|
||||
//!
|
||||
//! Most of the functionality in Winit works with surface coordinates, so usually you only need to
|
||||
//! concern yourself with those. In case you need to convert to some other coordinate system, Winit
|
||||
//! provides [`Window::surface_position`] and [`Window::surface_size`] to describe the surface's
|
||||
//! location in window coordinates, and Winit provides [`Window::outer_position`] and
|
||||
//! [`Window::outer_size`] to describe the window's location in desktop coordinates. Using these
|
||||
//! methods, you should be able to convert a position in one coordinate system to another.
|
||||
//!
|
||||
//! An overview of how these four methods fit together can be seen in the image below:
|
||||
#![doc = concat!("\n\n", include_str!("../docs/res/coordinate-systems-desktop.svg"), "\n\n")] // Rustfmt removes \n, adding them like this works around that.
|
||||
//! On mobile, the situation is usually a bit different; because of the smaller screen space,
|
||||
//! windows usually fill the whole screen at a time, and as such there is _rarely_ a difference
|
||||
//! between these three coordinate systems, although you should still strive to handle this, as
|
||||
//! they're still relevant in more niche area such as Mac Catalyst, or multi-tasking on tablets.
|
||||
//!
|
||||
//! This is illustrated in the image below, along with the safe area since it's often relevant on
|
||||
//! mobile.
|
||||
#![doc = concat!("\n\n", include_str!("../docs/res/coordinate-systems-mobile.svg"), "\n\n")] // Rustfmt removes \n, adding them like this works around that.
|
||||
//! [`Window::surface_position`]: crate::window::Window::surface_position
|
||||
//! [`Window::surface_size`]: crate::window::Window::surface_size
|
||||
//! [`Window::outer_position`]: crate::window::Window::outer_position
|
||||
//! [`Window::outer_size`]: crate::window::Window::outer_size
|
||||
//!
|
||||
//! # UI scaling
|
||||
//!
|
||||
//! UI scaling is important, go read the docs for the [`dpi`] crate for an
|
||||
@@ -147,6 +207,62 @@
|
||||
//! See the [`platform`] module for documentation on platform-specific cargo
|
||||
//! features.
|
||||
//!
|
||||
//! # Platform/Architecture Support
|
||||
//!
|
||||
//! Platform support on `winit` has two tiers: Tier 1 and Tier 2.
|
||||
//!
|
||||
//! - Tier 1 is **guaranteed to work**. Targets in this tier are actively tested both in CI and by
|
||||
//! maintainers.
|
||||
//! - Tier 2 is **guaranteed to build**. Code compilation is tested in CI, but deeper testing is not
|
||||
//! done.
|
||||
//!
|
||||
//! Please open an issue if you would like to add a Tier 2 target, or if you would
|
||||
//! like a Tier 2 target moved to Tier 1.
|
||||
//!
|
||||
//! ## Tier 1 Targets
|
||||
//!
|
||||
//! |Target Name |Target Triple |APIs |
|
||||
//! |-------------------------------|------------------------------------|---------------|
|
||||
//! |32-Bit x86 Windows with MSVC |`i686-pc-windows-msvc` |Win32 |
|
||||
//! |64-Bit x86 Windows with MSVC |`x86_64-pc-windows-msvc` |Win32 |
|
||||
//! |32-Bit x86 Windows with glibc |`i686-pc-windows-gnu` |Win32 |
|
||||
//! |64-Bit x86 Windows with glibc |`x86_64-pc-windows-gnu` |Win32 |
|
||||
//! |32-Bit x86 Linux with glibc |`i686-unknown-linux-gnu` |X11, Wayland |
|
||||
//! |64-Bit x86 Linux with glibc |`x86_64-unknown-linux-gnu` |X11, Wayland |
|
||||
//! |64-Bit ARM Android |`aarch64-linux-android` |Android |
|
||||
//! |64-Bit x86 Redox OS |`x86_64-unknown-redox` |Orbital |
|
||||
//! |32-Bit x86 Redox OS |`i686-unknown-redox` |Orbital |
|
||||
//! |64-Bit ARM Redox OS |`aarch64-unknown-redox` |Orbital |
|
||||
//! |64-bit x64 macOS |`x86_64-apple-darwin` |AppKit |
|
||||
//! |64-bit ARM macOS |`aarch64-apple-darwin` |AppKit |
|
||||
//! |32-bit Wasm Web browser |`wasm32-unknown-unknown` |`wasm-bindgen` |
|
||||
//!
|
||||
//! ## Tier 2 Targets
|
||||
//!
|
||||
//! |Target Name |Target Triple |APIs |
|
||||
//! |------------------------------------|------------------------------------|---------------|
|
||||
//! |64-Bit ARM Windows with MSVC |`aarch64-pc-windows-msvc` |Win32 |
|
||||
//! |32-Bit x86 Windows 7 with MSVC |`i686-win7-windows-msvc` |Win32 |
|
||||
//! |64-Bit x86 Windows 7 with MSVC |`x86_64-win7-windows-msvc` |Win32 |
|
||||
//! |64-bit x86 Linux with Musl |`x86_64-unknown-linux-musl` |X11, Wayland |
|
||||
//! |64-bit x86 Linux with 32-bit glibc |`x86_64-unknown-linux-gnux32` |X11, Wayland |
|
||||
//! |64-bit x86 Android |`x86_64-linux-android` |Android |
|
||||
//! |64-bit x64 iOS |`x86_64-apple-ios` |UIKit |
|
||||
//! |64-bit ARM iOS |`aarch64-apple-ios` |UIKit |
|
||||
//! |64-bit ARM Mac Catalyst |`aarch64-apple-ios-macabi` |UIKit |
|
||||
//! |32-bit x86 Android |`i686-linux-android` |Android |
|
||||
//! |64-bit x86 FreeBSD |`x86_64-unknown-freebsd` |X11, Wayland |
|
||||
//! |64-bit x86 NetBSD |`x86_64-unknown-netbsd` |X11 |
|
||||
//! |32-bit x86 Linux with Musl |`i686-unknown-linux-musl` |X11, Wayland |
|
||||
//! |64-bit RISC-V Linux with glibc |`riscv64gc-unknown-linux-gnu` |X11, Wayland |
|
||||
//! |64-bit ARM Linux with glibc |`aarch64-unknown-linux-gnu` |X11, Wayland |
|
||||
//! |64-bit ARM Linux with Musl |`aarch64-unknown-linux-musl` |X11, Wayland |
|
||||
//! |64-bit PowerPC Linux with glibc |`powerpc64le-unknown-linux-gnu` |X11, Wayland |
|
||||
//! |32-Bit ARM Linux with glibc |`armv5te-unknown-linux-gnueabi` |X11, Wayland |
|
||||
//! |64-Bit Linux on IBM Supercomputers |`s390x-unknown-linux-gnu` |X11, Wayland |
|
||||
//! |32-bit ARM Android |`arm-linux-androideabi` |Android |
|
||||
//! |64-bit SPARC Linux with glibc |`sparc64-unknown-linux-gnu` |X11, Wayland |
|
||||
//!
|
||||
//! [`EventLoop`]: event_loop::EventLoop
|
||||
//! [`EventLoop::new()`]: event_loop::EventLoop::new
|
||||
//! [`EventLoop::run_app()`]: event_loop::EventLoop::run_app
|
||||
@@ -154,13 +270,10 @@
|
||||
//! [`Window`]: window::Window
|
||||
//! [`WindowId`]: window::WindowId
|
||||
//! [`WindowAttributes`]: window::WindowAttributes
|
||||
//! [window_new]: window::Window::new
|
||||
//! [`create_window`]: event_loop::ActiveEventLoop::create_window
|
||||
//! [`Window::id()`]: window::Window::id
|
||||
//! [`WindowEvent`]: event::WindowEvent
|
||||
//! [`DeviceEvent`]: event::DeviceEvent
|
||||
//! [`Event::UserEvent`]: event::Event::UserEvent
|
||||
//! [`exiting()`]: crate::application::ApplicationHandler::exiting
|
||||
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
|
||||
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
|
||||
//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.
|
||||
@@ -174,11 +287,13 @@
|
||||
// doc
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
#![warn(clippy::uninlined_format_args)]
|
||||
// TODO: wasm-binding needs to be updated for that to be resolved, for now just silence it.
|
||||
#![cfg_attr(web_platform, allow(unknown_lints, wasm_c_abi))]
|
||||
|
||||
// Re-export DPI types so that users don't have to put it in Cargo.toml.
|
||||
#[doc(inline)]
|
||||
pub use dpi;
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub use rwh_06 as raw_window_handle;
|
||||
|
||||
pub mod application;
|
||||
@@ -189,7 +304,7 @@ pub mod error;
|
||||
mod cursor;
|
||||
pub mod event;
|
||||
pub mod event_loop;
|
||||
mod icon;
|
||||
pub mod icon;
|
||||
pub mod keyboard;
|
||||
pub mod monitor;
|
||||
mod platform_impl;
|
||||
|
||||
256
src/monitor.rs
256
src/monitor.rs
@@ -5,157 +5,113 @@
|
||||
//! methods, which return an iterator of [`MonitorHandle`]:
|
||||
//! - [`ActiveEventLoop::available_monitors`][crate::event_loop::ActiveEventLoop::available_monitors].
|
||||
//! - [`Window::available_monitors`][crate::window::Window::available_monitors].
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::platform_impl;
|
||||
|
||||
/// Describes a fullscreen video mode of a monitor.
|
||||
///
|
||||
/// Can be acquired with [`MonitorHandle::video_modes`].
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoModeHandle {
|
||||
pub(crate) video_mode: platform_impl::VideoModeHandle,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VideoModeHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.video_mode.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for VideoModeHandle {
|
||||
fn partial_cmp(&self, other: &VideoModeHandle) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for VideoModeHandle {
|
||||
fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering {
|
||||
self.monitor().cmp(&other.monitor()).then(
|
||||
self.size()
|
||||
.cmp(&other.size())
|
||||
.then(
|
||||
self.refresh_rate_millihertz()
|
||||
.cmp(&other.refresh_rate_millihertz())
|
||||
.then(self.bit_depth().cmp(&other.bit_depth())),
|
||||
)
|
||||
.reverse(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
/// Returns the resolution of this video mode. This **must not** be used to create your
|
||||
/// rendering surface. Use [`Window::inner_size()`] instead.
|
||||
///
|
||||
/// [`Window::inner_size()`]: crate::window::Window::inner_size
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.video_mode.size()
|
||||
}
|
||||
|
||||
/// Returns the bit depth of this video mode, as in how many bits you have
|
||||
/// available per color. This is generally 24 bits or 32 bits on modern
|
||||
/// systems, depending on whether the alpha channel is counted or not.
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
self.video_mode.bit_depth()
|
||||
}
|
||||
|
||||
/// Returns the refresh rate of this video mode in mHz.
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
self.video_mode.refresh_rate_millihertz()
|
||||
}
|
||||
|
||||
/// Returns the monitor that this video mode is valid for. Each monitor has
|
||||
/// a separate set of valid video modes.
|
||||
#[inline]
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle { inner: self.video_mode.monitor() }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VideoModeHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}x{} {}{}",
|
||||
self.size().width,
|
||||
self.size().height,
|
||||
self.refresh_rate_millihertz().map(|rate| format!("@ {rate} mHz ")).unwrap_or_default(),
|
||||
self.bit_depth().map(|bit_depth| format!("({bit_depth} bpp)")).unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
use crate::utils::{impl_dyn_casting, AsAny};
|
||||
|
||||
/// Handle to a monitor.
|
||||
///
|
||||
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
|
||||
/// Allows you to retrieve basic information and metadata about a monitor.
|
||||
///
|
||||
/// Can be used in [`Window`] creation to place the window on a specific
|
||||
/// monitor.
|
||||
///
|
||||
/// This can be retrieved from one of the following methods, which return an
|
||||
/// iterator of [`MonitorHandle`]s:
|
||||
/// - [`ActiveEventLoop::available_monitors`](crate::event_loop::ActiveEventLoop::available_monitors).
|
||||
/// - [`Window::available_monitors`](crate::window::Window::available_monitors).
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **Web:** A [`MonitorHandle`] created without
|
||||
#[cfg_attr(
|
||||
any(web_platform, docsrs),
|
||||
web_platform,
|
||||
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
|
||||
)]
|
||||
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
|
||||
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
|
||||
/// will always represent the current monitor the browser window is in instead of a specific
|
||||
/// monitor. See
|
||||
#[cfg_attr(
|
||||
any(web_platform, docsrs),
|
||||
web_platform,
|
||||
doc = "[`MonitorHandleExtWeb::is_detailed()`][crate::platform::web::MonitorHandleExtWeb::is_detailed]"
|
||||
)]
|
||||
#[cfg_attr(not(any(web_platform, docsrs)), doc = "`MonitorHandleExtWeb::is_detailed()`")]
|
||||
#[cfg_attr(not(web_platform), doc = "`MonitorHandleExtWeb::is_detailed()`")]
|
||||
/// to check.
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct MonitorHandle {
|
||||
pub(crate) inner: platform_impl::MonitorHandle,
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorHandle(pub(crate) Arc<dyn MonitorHandleProvider>);
|
||||
|
||||
impl std::fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
impl Deref for MonitorHandle {
|
||||
type Target = dyn MonitorHandleProvider;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.as_ref().eq(other.0.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MonitorHandle {}
|
||||
|
||||
/// Provider of the [`MonitorHandle`].
|
||||
pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
|
||||
/// Identifier for this monitor.
|
||||
///
|
||||
/// The representation of this modifier is not guaranteed and should be used only to compare
|
||||
/// monitors.
|
||||
fn id(&self) -> u128;
|
||||
|
||||
/// Native platform identifier of this monitor.
|
||||
///
|
||||
/// # Platform-specific
|
||||
///
|
||||
/// - **Windows**: This is `HMONITOR`.
|
||||
/// - **macOS**: This is `CGDirectDisplayID`.
|
||||
/// - **iOS**: This is `UIScreen*`.
|
||||
/// - **Wayland**: This is the ID of the `wl_output` device.
|
||||
/// - **X11**: This is the ID of the CRTC.
|
||||
/// - **Web**: This is an internal ID not meant for consumption.
|
||||
fn native_id(&self) -> u64;
|
||||
|
||||
/// Returns a human-readable name of the monitor.
|
||||
///
|
||||
/// Returns `None` if the monitor doesn't exist anymore.
|
||||
/// Returns `None` if the monitor doesn't exist anymore or the name couldn't be obtained.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **Web:** Always returns [`None`] without
|
||||
#[cfg_attr(
|
||||
any(web_platform, docsrs),
|
||||
web_platform,
|
||||
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
|
||||
)]
|
||||
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
self.inner.name()
|
||||
}
|
||||
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
|
||||
fn name(&self) -> Option<Cow<'_, str>>;
|
||||
|
||||
/// Returns the top-left corner position of the monitor relative to the larger full
|
||||
/// screen area.
|
||||
/// Returns the top-left corner position of the monitor in desktop coordinates.
|
||||
///
|
||||
/// This position is in the same coordinate system as [`Window::outer_position`].
|
||||
///
|
||||
/// [`Window::outer_position`]: crate::window::Window::outer_position
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **Web:** Always returns [`None`] without
|
||||
#[cfg_attr(
|
||||
any(web_platform, docsrs),
|
||||
web_platform,
|
||||
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
|
||||
)]
|
||||
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
|
||||
#[inline]
|
||||
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
|
||||
self.inner.position()
|
||||
}
|
||||
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
|
||||
fn position(&self) -> Option<PhysicalPosition<i32>>;
|
||||
|
||||
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
|
||||
/// pixels and vice versa, use [`Window::scale_factor`].
|
||||
@@ -169,27 +125,81 @@ impl MonitorHandle {
|
||||
/// - **Android:** Always returns 1.0.
|
||||
/// - **Web:** Always returns `0.0` without
|
||||
#[cfg_attr(
|
||||
any(web_platform, docsrs),
|
||||
web_platform,
|
||||
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
|
||||
)]
|
||||
#[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")]
|
||||
#[cfg_attr(not(web_platform), doc = " detailed monitor permissions.")]
|
||||
///
|
||||
#[rustfmt::skip]
|
||||
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.inner.scale_factor()
|
||||
}
|
||||
fn scale_factor(&self) -> f64;
|
||||
|
||||
/// Returns the currently active video mode of this monitor.
|
||||
#[inline]
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
self.inner.current_video_mode().map(|video_mode| VideoModeHandle { video_mode })
|
||||
}
|
||||
fn current_video_mode(&self) -> Option<VideoMode>;
|
||||
|
||||
/// Returns all fullscreen video modes supported by this monitor.
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
self.inner.video_modes().map(|video_mode| VideoModeHandle { video_mode })
|
||||
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>>;
|
||||
}
|
||||
|
||||
impl PartialEq for dyn MonitorHandleProvider + '_ {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id() == other.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for dyn MonitorHandleProvider + '_ {}
|
||||
|
||||
impl_dyn_casting!(MonitorHandleProvider);
|
||||
|
||||
/// Describes a fullscreen video mode of a monitor.
|
||||
///
|
||||
/// Can be acquired with [`MonitorHandleProvider::video_modes`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: PhysicalSize<u32>,
|
||||
pub(crate) bit_depth: Option<NonZeroU16>,
|
||||
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
/// Returns the resolution of this video mode. This **must not** be used to create your
|
||||
/// rendering surface. Use [`Window::surface_size()`] instead.
|
||||
///
|
||||
/// [`Window::surface_size()`]: crate::window::Window::surface_size
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.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.
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
/// Returns the refresh rate of this video mode in mHz.
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VideoMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}x{} {}{}",
|
||||
self.size.width,
|
||||
self.size.height,
|
||||
self.refresh_rate_millihertz.map(|rate| format!("@ {rate} mHz ")).unwrap_or_default(),
|
||||
self.bit_depth.map(|bit_depth| format!("({bit_depth} bpp)")).unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fullscreen modes.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Fullscreen {
|
||||
Exclusive(MonitorHandle, VideoMode),
|
||||
|
||||
/// Providing `None` to `Borderless` will fullscreen on the current monitor.
|
||||
Borderless(Option<MonitorHandle>),
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
|
||||
//! with `cargo apk`, then the minimal changes would be:
|
||||
//! 1. Remove `ndk-glue` from your `Cargo.toml`
|
||||
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.4",
|
||||
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.10",
|
||||
//! features = [ "android-native-activity" ] }`
|
||||
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
|
||||
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
|
||||
@@ -99,20 +99,21 @@ pub trait WindowExtAndroid {
|
||||
fn config(&self) -> ConfigurationRef;
|
||||
}
|
||||
|
||||
impl WindowExtAndroid for Window {
|
||||
impl WindowExtAndroid for dyn Window + '_ {
|
||||
fn content_rect(&self) -> Rect {
|
||||
self.window.content_rect()
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.content_rect()
|
||||
}
|
||||
|
||||
fn config(&self) -> ConfigurationRef {
|
||||
self.window.config()
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.config()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveEventLoopExtAndroid for &dyn ActiveEventLoop {
|
||||
impl ActiveEventLoopExtAndroid for dyn ActiveEventLoop + '_ {
|
||||
fn android_app(&self) -> &AndroidApp {
|
||||
let event_loop =
|
||||
self.as_any().downcast_ref::<crate::platform_impl::ActiveEventLoop>().unwrap();
|
||||
let event_loop = self.cast_ref::<crate::platform_impl::ActiveEventLoop>().unwrap();
|
||||
&event_loop.app
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,79 @@
|
||||
//! # iOS / UIKit
|
||||
//!
|
||||
//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
|
||||
//! iOS 9.3.
|
||||
//! Winit has [the same iOS version requirements as `rustc`][rustc-ios-version], although it's
|
||||
//! frequently only tested on newer iOS versions.
|
||||
//!
|
||||
//! iOS's main `UIApplicationMain` does some init work that's required by all
|
||||
//! UI-related code (see issue [#1705]). It is best to create your windows
|
||||
//! inside `Event::Resumed`.
|
||||
//! [rustc-ios-version]: https://doc.rust-lang.org/rustc/platform-support/apple-ios.html#os-version
|
||||
//!
|
||||
//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
|
||||
//! ## Running on Mac Catalyst
|
||||
//!
|
||||
//! ## Building app
|
||||
//! Mac Catalyst allows running applications using UIKit on macOS, which can be very useful for
|
||||
//! testing. See [`rustc`'s documentation on Mac Catalyst][rustc-mac-catalyst] for details on how to
|
||||
//! use these targets. To use these with Winit, you'll need to bundle your application before
|
||||
//! running it, otherwise UIKit will exit with an error.
|
||||
//!
|
||||
//! To build ios app you will need rustc built for this targets:
|
||||
//! To run e.g. the `window` example in the Winit repository, you can use [`cargo-bundle`] as
|
||||
//! follows:
|
||||
//!
|
||||
//! - 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() {
|
||||
//! ...
|
||||
//! }
|
||||
//! ```console
|
||||
//! $ cargo +nightly bundle --format=ios --target=aarch64-apple-ios-macabi --example=window
|
||||
//! $ ./target/aarch64-apple-ios-macabi/debug/examples/bundle/ios/winit.app/window
|
||||
//! ```
|
||||
//!
|
||||
//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
|
||||
//! [rustc-mac-catalyst]: https://doc.rust-lang.org/rustc/platform-support/apple-ios-macabi.html
|
||||
//! [`cargo-bundle`]: https://github.com/burtonageo/cargo-bundle
|
||||
//!
|
||||
//! ```ignore
|
||||
//! void start_winit_app();
|
||||
//! ## Introduction to building an app
|
||||
//!
|
||||
//! Building and running your application in the iOS simulator, or on a real device, is a bit more
|
||||
//! complicated than Mac Catalyst - fundamentally, you must use Xcode, since the binary needs to be
|
||||
//! bundled, signed, notarized and uploaded to the device (there is [an open source work-in-progress
|
||||
//! on re-implementing parts of this][apple-platform-rs], but the user-story around it is not yet
|
||||
//! clear).
|
||||
//!
|
||||
//! This means that you're left with effectively two options: Use a tool that manages the Xcode
|
||||
//! configuration for you, or use Xcode directly. [`cargo-dinghy`] and [`cargo-mobile2`] are notable
|
||||
//! projects in the ecosystem that attempt the former, and [`cargo-xcode`] is an excellent project
|
||||
//! that attempts the latter. We will also attempt to describe here how you would go about using
|
||||
//! Xcode directly:
|
||||
//!
|
||||
//! First off, you'll need the correct Rust targets, see [`rustc`'s documentation on iOS][rustc-ios]
|
||||
//! for details. Nowadays, the correct targets are usually `aarch64-apple-ios-sim` for the
|
||||
//! simulator, and `aarch64-apple-ios` for the actual device.
|
||||
//!
|
||||
//! Next, create a new Xcode project using the "App" template. The exact configuration does not
|
||||
//! really matter, as we're going to delete most of it, since it's tailored for Objective-C and/or
|
||||
//! Swift, and Rust/Winit is neither. Specifically, we need to delete:
|
||||
//! - Everything relating to storyboards (unless you want to use e.g. a launch screen). This
|
||||
//! includes the relevant keys in `Info.plist`.
|
||||
//! - All the generated C header, Objective-C and/or Swift files.
|
||||
//!
|
||||
//! Now that we have a fairly clean slate that we can build upon, you can add a "run script" build
|
||||
//! phase to your Xcode target, which will get invoked instead of the "compile sources" and "link
|
||||
//! binary" steps. The basic script should look something like:
|
||||
//!
|
||||
//! ```sh
|
||||
//! # Build desired targets based on `ARCHS` environment variable
|
||||
//! cargo build --target=aarch64-apple-ios --target=armv7s-apple-ios
|
||||
//! # Merge these with `lipo`, and place the result in "$TARGET_BUILD_DIR/$EXECUTABLE_PATH", which
|
||||
//! # is understood by Xcode
|
||||
//! lipo "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" target/aarch64-apple-ios/debug/my_app target/armv7s-apple-ios/debug/my_app
|
||||
//! ```
|
||||
//!
|
||||
//! Use start_winit_app inside your xcode's main function.
|
||||
//! Note that this is very much the overall idea; the script needs to be much more involved to
|
||||
//! properly deal with different target architectures, invoking `lipo` when needed, incremental
|
||||
//! rebuild change detection, and so on. `cargo-xcode` has a script [here][cargo-xcode-script] that
|
||||
//! handles most of this complexity, you might be able to build upon that.
|
||||
//!
|
||||
//! Apologies that we're not able to provide you with more than this; work is in-progress on
|
||||
//! improving the situation, but it's slow-going.
|
||||
//!
|
||||
//! [apple-platform-rs]: https://github.com/indygreg/apple-platform-rs
|
||||
//! [`cargo-dinghy`]: https://github.com/sonos/dinghy
|
||||
//! [`cargo-mobile2`]: https://github.com/tauri-apps/cargo-mobile2
|
||||
//! [`cargo-xcode`]: https://crates.io/crates/cargo-xcode
|
||||
//! [rustc-ios]: https://doc.rust-lang.org/rustc/platform-support/apple-ios.html
|
||||
//! [cargo-xcode-script]: https://gitlab.com/kornelski/cargo-xcode/-/blob/main/src/xcodebuild.sh
|
||||
//!
|
||||
//! ## App lifecycle and events
|
||||
//!
|
||||
@@ -57,19 +85,27 @@
|
||||
//!
|
||||
//! - applicationDidBecomeActive is Resumed
|
||||
//! - applicationWillResignActive is Suspended
|
||||
//! - applicationWillTerminate is LoopExiting
|
||||
//! - applicationWillTerminate corresponds to `Drop`ping the application handler.
|
||||
//!
|
||||
//! Keep in mind that after LoopExiting event is received every attempt to draw with
|
||||
//! opengl will result in segfault.
|
||||
//! Note that an app may not receive the `Drop` event if suspended; it might be SIGKILL'ed.
|
||||
//!
|
||||
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
|
||||
//! ## Custom `UIApplicationDelegate`
|
||||
//!
|
||||
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
|
||||
//! though, you might want to access some of the more niche stuff that [the application
|
||||
//! delegate][app-delegate] provides. This functionality is not exposed directly in Winit, since it
|
||||
//! would increase the API surface by quite a lot. Instead, Winit guarantees that it will not
|
||||
//! register an application delegate, so you can set up a custom one in a nib file instead.
|
||||
//!
|
||||
//! [app-delegate]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::monitor::{MonitorHandle, VideoModeHandle};
|
||||
use crate::monitor::{MonitorHandle, VideoMode};
|
||||
use crate::platform_impl::MonitorHandle as IosMonitorHandle;
|
||||
use crate::window::{Window, WindowAttributes};
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to iOS.
|
||||
@@ -77,10 +113,11 @@ pub trait WindowExtIOS {
|
||||
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
|
||||
///
|
||||
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
||||
/// this to [`MonitorHandle::scale_factor()`].
|
||||
/// this to [`MonitorHandleProvider::scale_factor()`].
|
||||
///
|
||||
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
||||
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
|
||||
/// [`MonitorHandleProvider::scale_factor()`]: crate::monitor::MonitorHandleProvider::scale_factor()
|
||||
fn set_scale_factor(&self, scale_factor: f64);
|
||||
|
||||
/// Sets the valid orientations for the [`Window`].
|
||||
@@ -171,42 +208,49 @@ pub trait WindowExtIOS {
|
||||
fn recognize_rotation_gesture(&self, should_recognize: bool);
|
||||
}
|
||||
|
||||
impl WindowExtIOS for Window {
|
||||
impl WindowExtIOS for dyn Window + '_ {
|
||||
#[inline]
|
||||
fn set_scale_factor(&self, scale_factor: f64) {
|
||||
self.window.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.set_scale_factor(scale_factor));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
||||
self.window.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.set_valid_orientations(valid_orientations));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||
self.window.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
||||
self.window.maybe_queue_on_main(move |w| {
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| {
|
||||
w.set_preferred_screen_edges_deferring_system_gestures(edges)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||
self.window.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.set_prefers_status_bar_hidden(hidden));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
|
||||
self.window.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn recognize_pinch_gesture(&self, should_recognize: bool) {
|
||||
self.window.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -216,7 +260,8 @@ impl WindowExtIOS for Window {
|
||||
minimum_number_of_touches: u8,
|
||||
maximum_number_of_touches: u8,
|
||||
) {
|
||||
self.window.maybe_queue_on_main(move |w| {
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| {
|
||||
w.recognize_pan_gesture(
|
||||
should_recognize,
|
||||
minimum_number_of_touches,
|
||||
@@ -227,12 +272,14 @@ impl WindowExtIOS for Window {
|
||||
|
||||
#[inline]
|
||||
fn recognize_doubletap_gesture(&self, should_recognize: bool) {
|
||||
self.window.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn recognize_rotation_gesture(&self, should_recognize: bool) {
|
||||
self.window.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,10 +288,11 @@ pub trait WindowAttributesExtIOS {
|
||||
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
|
||||
///
|
||||
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
||||
/// this to [`MonitorHandle::scale_factor()`].
|
||||
/// this to [`MonitorHandleProvider::scale_factor()`].
|
||||
///
|
||||
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
||||
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
|
||||
/// [`MonitorHandleProvider::scale_factor()`]: crate::monitor::MonitorHandleProvider::scale_factor()
|
||||
fn with_scale_factor(self, scale_factor: f64) -> Self;
|
||||
|
||||
/// Sets the valid orientations for the [`Window`].
|
||||
@@ -336,23 +384,25 @@ pub trait MonitorHandleExtIOS {
|
||||
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
|
||||
fn ui_screen(&self) -> *mut c_void;
|
||||
|
||||
/// Returns the preferred [`VideoModeHandle`] for this monitor.
|
||||
/// 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) -> VideoModeHandle;
|
||||
fn preferred_video_mode(&self) -> VideoMode;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtIOS for MonitorHandle {
|
||||
#[inline]
|
||||
fn ui_screen(&self) -> *mut c_void {
|
||||
// SAFETY: The marker is only used to get the pointer of the screen
|
||||
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
|
||||
objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
|
||||
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
|
||||
let monitor = self.cast_ref::<IosMonitorHandle>().unwrap();
|
||||
objc2::rc::Retained::as_ptr(monitor.ui_screen(mtm)) as *mut c_void
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn preferred_video_mode(&self) -> VideoModeHandle {
|
||||
VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
|
||||
fn preferred_video_mode(&self) -> VideoMode {
|
||||
let monitor = self.cast_ref::<IosMonitorHandle>().unwrap();
|
||||
monitor.preferred_video_mode()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,80 @@
|
||||
//! # macOS / AppKit
|
||||
//!
|
||||
//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
|
||||
//! itself), and is regularly tested on macOS 10.14.
|
||||
//! Winit has [the same macOS version requirements as `rustc`][rustc-macos-version], and is tested
|
||||
//! once in a while on as low as macOS 10.14.
|
||||
//!
|
||||
//! A lot of functionality expects the application to be ready before you
|
||||
//! start doing anything; this includes creating windows, fetching monitors,
|
||||
//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
|
||||
//! [rustc-macos-version]: https://doc.rust-lang.org/rustc/platform-support/apple-darwin.html#os-version
|
||||
//!
|
||||
//! If you encounter problems, you should try doing your initialization inside
|
||||
//! `Event::Resumed`.
|
||||
//! ## Custom `NSApplicationDelegate`
|
||||
//!
|
||||
//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
|
||||
//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
|
||||
//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
|
||||
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
|
||||
//! though, you might want to do more niche stuff, such as [handle when the user re-activates the
|
||||
//! application][reopen]. Such functionality is not exposed directly in Winit, since it would
|
||||
//! increase the API surface by quite a lot.
|
||||
//!
|
||||
//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc
|
||||
//!
|
||||
//! Instead, Winit guarantees that it will not register an application delegate, so the solution is
|
||||
//! to register your own application delegate, as outlined in the following example (see
|
||||
//! `objc2-app-kit` for more detailed information).
|
||||
#![cfg_attr(target_os = "macos", doc = "```")]
|
||||
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
|
||||
//! use objc2::rc::Retained;
|
||||
//! use objc2::runtime::ProtocolObject;
|
||||
//! use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly};
|
||||
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
|
||||
//! use objc2_foundation::{NSArray, NSURL, NSObject, NSObjectProtocol};
|
||||
//! use winit::event_loop::EventLoop;
|
||||
//!
|
||||
//! define_class!(
|
||||
//! #[unsafe(super(NSObject))]
|
||||
//! #[thread_kind = MainThreadOnly]
|
||||
//! #[name = "AppDelegate"]
|
||||
//! struct AppDelegate;
|
||||
//!
|
||||
//! unsafe impl NSObjectProtocol for AppDelegate {}
|
||||
//!
|
||||
//! unsafe impl NSApplicationDelegate for AppDelegate {
|
||||
//! #[unsafe(method(application:openURLs:))]
|
||||
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
|
||||
//! // Note: To specifically get `application:openURLs:` to work, you _might_
|
||||
//! // have to bundle your application. This is not done in this example.
|
||||
//! println!("open urls: {application:?}, {urls:?}");
|
||||
//! }
|
||||
//! }
|
||||
//! );
|
||||
//!
|
||||
//! impl AppDelegate {
|
||||
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
|
||||
//! unsafe { msg_send![super(Self::alloc(mtm).set_ivars(())), init] }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! let event_loop = EventLoop::new()?;
|
||||
//!
|
||||
//! let mtm = MainThreadMarker::new().unwrap();
|
||||
//! let delegate = AppDelegate::new(mtm);
|
||||
//! // Important: Call `sharedApplication` after `EventLoop::new`,
|
||||
//! // doing it before is not yet supported.
|
||||
//! let app = NSApplication::sharedApplication(mtm);
|
||||
//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
||||
//!
|
||||
//! // event_loop.run_app(&mut my_app);
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
|
||||
use crate::monitor::MonitorHandle;
|
||||
use crate::window::{Window, WindowAttributes};
|
||||
use crate::platform_impl::MonitorHandle as MacOsMonitorHandle;
|
||||
use crate::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to MacOS.
|
||||
pub trait WindowExtMacOS {
|
||||
@@ -35,6 +88,9 @@ pub trait WindowExtMacOS {
|
||||
/// This is how fullscreen used to work on macOS in versions before Lion.
|
||||
/// And allows the user to have a fullscreen window without using another
|
||||
/// space or taking control over the entire monitor.
|
||||
///
|
||||
/// Make sure you only draw your important content inside the safe area so that it does not
|
||||
/// overlap with the notch on newer devices, see [`Window::safe_area`] for details.
|
||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
|
||||
|
||||
/// Returns whether or not the window has shadow.
|
||||
@@ -94,77 +150,130 @@ pub trait WindowExtMacOS {
|
||||
|
||||
/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
|
||||
fn option_as_alt(&self) -> OptionAsAlt;
|
||||
|
||||
/// Disable the Menu Bar and Dock in Simple or Borderless Fullscreen mode. Useful for games.
|
||||
/// The effect is applied when [`WindowExtMacOS::set_simple_fullscreen`] or
|
||||
/// [`Window::set_fullscreen`] is called.
|
||||
fn set_borderless_game(&self, borderless_game: bool);
|
||||
|
||||
/// Getter for the [`WindowExtMacOS::set_borderless_game`].
|
||||
fn is_borderless_game(&self) -> bool;
|
||||
|
||||
/// Makes the titlebar bigger, effectively adding more space around the
|
||||
/// window controls if the titlebar is invisible.
|
||||
fn set_unified_titlebar(&self, unified_titlebar: bool);
|
||||
|
||||
/// Getter for the [`WindowExtMacOS::set_unified_titlebar`].
|
||||
fn unified_titlebar(&self) -> bool;
|
||||
}
|
||||
|
||||
impl WindowExtMacOS for Window {
|
||||
impl WindowExtMacOS for dyn Window + '_ {
|
||||
#[inline]
|
||||
fn simple_fullscreen(&self) -> bool {
|
||||
self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.simple_fullscreen())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
||||
self.window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn has_shadow(&self) -> bool {
|
||||
self.window.maybe_wait_on_main(|w| w.has_shadow())
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.has_shadow())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_has_shadow(&self, has_shadow: bool) {
|
||||
self.window.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.set_has_shadow(has_shadow));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_tabbing_identifier(&self, identifier: &str) {
|
||||
self.window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn tabbing_identifier(&self) -> String {
|
||||
self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.tabbing_identifier())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select_next_tab(&self) {
|
||||
self.window.maybe_queue_on_main(|w| w.select_next_tab())
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.select_next_tab());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select_previous_tab(&self) {
|
||||
self.window.maybe_queue_on_main(|w| w.select_previous_tab())
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.select_previous_tab());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select_tab_at_index(&self, index: usize) {
|
||||
self.window.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.select_tab_at_index(index));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn num_tabs(&self) -> usize {
|
||||
self.window.maybe_wait_on_main(|w| w.num_tabs())
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.num_tabs())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_document_edited(&self) -> bool {
|
||||
self.window.maybe_wait_on_main(|w| w.is_document_edited())
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.is_document_edited())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_document_edited(&self, edited: bool) {
|
||||
self.window.maybe_queue_on_main(move |w| w.set_document_edited(edited))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.set_document_edited(edited));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
|
||||
self.window.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(move |w| w.set_option_as_alt(option_as_alt));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn option_as_alt(&self) -> OptionAsAlt {
|
||||
self.window.maybe_wait_on_main(|w| w.option_as_alt())
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.option_as_alt())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_borderless_game(&self, borderless_game: bool) {
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_borderless_game(&self) -> bool {
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.is_borderless_game())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_unified_titlebar(&self, unified_titlebar: bool) {
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unified_titlebar(&self) -> bool {
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.maybe_wait_on_main(|w| w.unified_titlebar())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +326,17 @@ pub trait WindowAttributesExtMacOS {
|
||||
///
|
||||
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
|
||||
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
|
||||
/// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set.
|
||||
fn with_borderless_game(self, borderless_game: bool) -> Self;
|
||||
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
|
||||
fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
|
||||
/// Use [`NSPanel`] window with [`NonactivatingPanel`] window style mask instead of
|
||||
/// [`NSWindow`].
|
||||
///
|
||||
/// [`NSWindow`]: https://developer.apple.com/documentation/appkit/NSWindow?language=objc
|
||||
/// [`NSPanel`]: https://developer.apple.com/documentation/appkit/NSPanel?language=objc
|
||||
/// [`NonactivatingPanel`]: https://developer.apple.com/documentation/appkit/nswindow/stylemask-swift.struct/nonactivatingpanel?language=objc
|
||||
fn with_panel(self, panel: bool) -> Self;
|
||||
}
|
||||
|
||||
impl WindowAttributesExtMacOS for WindowAttributes {
|
||||
@@ -285,23 +405,44 @@ impl WindowAttributesExtMacOS for WindowAttributes {
|
||||
self.platform_specific.option_as_alt = option_as_alt;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_borderless_game(mut self, borderless_game: bool) -> Self {
|
||||
self.platform_specific.borderless_game = borderless_game;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self {
|
||||
self.platform_specific.unified_titlebar = unified_titlebar;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_panel(mut self, panel: bool) -> Self {
|
||||
self.platform_specific.panel = panel;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventLoopBuilderExtMacOS {
|
||||
/// Sets the activation policy for the application.
|
||||
/// Sets the activation policy for the application. If used, this will override
|
||||
/// any relevant settings provided in the package manifest.
|
||||
/// For instance, `with_activation_policy(ActivationPolicy::Regular)` will prevent
|
||||
/// the application from running as an "agent", even if LSUIElement is set to true.
|
||||
///
|
||||
/// It is set to [`ActivationPolicy::Regular`] by default.
|
||||
/// If unused, the Winit will honor the package manifest.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Set the activation policy to "accessory".
|
||||
///
|
||||
/// ```
|
||||
/// use winit::event_loop::EventLoopBuilder;
|
||||
/// use winit::event_loop::EventLoop;
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// use winit::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
|
||||
///
|
||||
/// let mut builder = EventLoopBuilder::new();
|
||||
/// let mut builder = EventLoop::builder();
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// builder.with_activation_policy(ActivationPolicy::Accessory);
|
||||
/// # if false { // We can't test this part
|
||||
@@ -319,11 +460,11 @@ pub trait EventLoopBuilderExtMacOS {
|
||||
/// Disable creating a default menubar.
|
||||
///
|
||||
/// ```
|
||||
/// use winit::event_loop::EventLoopBuilder;
|
||||
/// use winit::event_loop::EventLoop;
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// use winit::platform::macos::EventLoopBuilderExtMacOS;
|
||||
///
|
||||
/// let mut builder = EventLoopBuilder::new();
|
||||
/// let mut builder = EventLoop::builder();
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// builder.with_default_menu(false);
|
||||
/// # if false { // We can't test this part
|
||||
@@ -342,7 +483,7 @@ pub trait EventLoopBuilderExtMacOS {
|
||||
impl EventLoopBuilderExtMacOS for EventLoopBuilder {
|
||||
#[inline]
|
||||
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
|
||||
self.platform_specific.activation_policy = activation_policy;
|
||||
self.platform_specific.activation_policy = Some(activation_policy);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -361,22 +502,16 @@ impl EventLoopBuilderExtMacOS for EventLoopBuilder {
|
||||
|
||||
/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
|
||||
pub trait MonitorHandleExtMacOS {
|
||||
/// Returns the identifier of the monitor for Cocoa.
|
||||
fn native_id(&self) -> u32;
|
||||
/// Returns a pointer to the NSScreen representing this monitor.
|
||||
fn ns_screen(&self) -> Option<*mut c_void>;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtMacOS for MonitorHandle {
|
||||
#[inline]
|
||||
fn native_id(&self) -> u32 {
|
||||
self.inner.native_identifier()
|
||||
}
|
||||
|
||||
fn ns_screen(&self) -> Option<*mut c_void> {
|
||||
let monitor = self.cast_ref::<MacOsMonitorHandle>().unwrap();
|
||||
// SAFETY: We only use the marker to get a pointer
|
||||
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
|
||||
self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
|
||||
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
|
||||
monitor.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,35 +531,31 @@ pub trait ActiveEventLoopExtMacOS {
|
||||
fn allows_automatic_window_tabbing(&self) -> bool;
|
||||
}
|
||||
|
||||
impl ActiveEventLoopExtMacOS for &dyn ActiveEventLoop {
|
||||
impl ActiveEventLoopExtMacOS for dyn ActiveEventLoop + '_ {
|
||||
fn hide_application(&self) {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non macOS event loop on macOS");
|
||||
event_loop.hide_application()
|
||||
}
|
||||
|
||||
fn hide_other_applications(&self) {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non macOS event loop on macOS");
|
||||
event_loop.hide_other_applications()
|
||||
}
|
||||
|
||||
fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non macOS event loop on macOS");
|
||||
event_loop.set_allows_automatic_window_tabbing(enabled);
|
||||
}
|
||||
|
||||
fn allows_automatic_window_tabbing(&self) -> bool {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non macOS event loop on macOS");
|
||||
event_loop.allows_automatic_window_tabbing()
|
||||
}
|
||||
@@ -449,3 +580,52 @@ pub enum OptionAsAlt {
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
/// Additional events on [`ApplicationHandler`] that are specific to macOS.
|
||||
///
|
||||
/// This can be registered with [`ApplicationHandler::macos_handler`].
|
||||
pub trait ApplicationHandlerExtMacOS: ApplicationHandler {
|
||||
/// The system interpreted a keypress as a standard key binding command.
|
||||
///
|
||||
/// Examples include inserting tabs and newlines, or moving the insertion point, see
|
||||
/// [`NSStandardKeyBindingResponding`] for the full list of key bindings. They are often text
|
||||
/// editing related.
|
||||
///
|
||||
/// This corresponds to the [`doCommandBySelector:`] method on `NSTextInputClient`.
|
||||
///
|
||||
/// The `action` parameter contains the string representation of the selector. Examples include
|
||||
/// `"insertBacktab:"`, `"indent:"` and `"noop:"`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// impl ApplicationHandlerExtMacOS for App {
|
||||
/// fn standard_key_binding(
|
||||
/// &mut self,
|
||||
/// event_loop: &dyn ActiveEventLoop,
|
||||
/// window_id: WindowId,
|
||||
/// action: &str,
|
||||
/// ) {
|
||||
/// match action {
|
||||
/// "moveBackward:" => self.cursor.position -= 1,
|
||||
/// "moveForward:" => self.cursor.position += 1,
|
||||
/// _ => {} // Ignore other actions
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`NSStandardKeyBindingResponding`]: https://developer.apple.com/documentation/appkit/nsstandardkeybindingresponding?language=objc
|
||||
/// [`doCommandBySelector:`]: https://developer.apple.com/documentation/appkit/nstextinputclient/1438256-docommandbyselector?language=objc
|
||||
#[doc(alias = "doCommandBySelector:")]
|
||||
fn standard_key_binding(
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
window_id: WindowId,
|
||||
action: &str,
|
||||
) {
|
||||
let _ = event_loop;
|
||||
let _ = window_id;
|
||||
let _ = action;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,25 +2,26 @@
|
||||
//!
|
||||
//! Only the modules corresponding to the platform you're compiling to will be available.
|
||||
|
||||
#[cfg(any(android_platform, docsrs))]
|
||||
#[cfg(android_platform)]
|
||||
pub mod android;
|
||||
#[cfg(any(ios_platform, docsrs))]
|
||||
#[cfg(ios_platform)]
|
||||
pub mod ios;
|
||||
#[cfg(any(macos_platform, docsrs))]
|
||||
#[cfg(macos_platform)]
|
||||
pub mod macos;
|
||||
#[cfg(any(orbital_platform, docsrs))]
|
||||
#[cfg(orbital_platform)]
|
||||
pub mod orbital;
|
||||
#[cfg(any(x11_platform, wayland_platform, docsrs))]
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
pub mod startup_notify;
|
||||
#[cfg(any(wayland_platform, docsrs))]
|
||||
#[cfg(wayland_platform)]
|
||||
pub mod wayland;
|
||||
#[cfg(any(web_platform, docsrs))]
|
||||
#[cfg(web_platform)]
|
||||
pub mod web;
|
||||
#[cfg(any(windows_platform, docsrs))]
|
||||
#[cfg(windows_platform)]
|
||||
pub mod windows;
|
||||
#[cfg(any(x11_platform, docsrs))]
|
||||
#[cfg(x11_platform)]
|
||||
pub mod x11;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
@@ -41,15 +42,5 @@ pub mod run_on_demand;
|
||||
))]
|
||||
pub mod pump_events;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
orbital_platform,
|
||||
docsrs
|
||||
))]
|
||||
pub mod modifier_supplement;
|
||||
|
||||
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))]
|
||||
pub mod scancode;
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
use crate::event::KeyEvent;
|
||||
use crate::keyboard::Key;
|
||||
|
||||
/// Additional methods for the `KeyEvent` which cannot be implemented on all
|
||||
/// platforms.
|
||||
pub trait KeyEventExtModifierSupplement {
|
||||
/// Identical to `KeyEvent::text` but this is affected by <kbd>Ctrl</kbd>.
|
||||
///
|
||||
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
|
||||
fn text_with_all_modifiers(&self) -> Option<&str>;
|
||||
|
||||
/// This value ignores all modifiers including,
|
||||
/// but not limited to <kbd>Shift</kbd>, <kbd>Caps Lock</kbd>,
|
||||
/// and <kbd>Ctrl</kbd>. In most cases this means that the
|
||||
/// unicode character in the resulting string is lowercase.
|
||||
///
|
||||
/// This is useful for key-bindings / shortcut key combinations.
|
||||
///
|
||||
/// In case `logical_key` reports `Dead`, this will still report the
|
||||
/// key as `Character` according to the current keyboard layout. This value
|
||||
/// cannot be `Dead`.
|
||||
fn key_without_modifiers(&self) -> Key;
|
||||
}
|
||||
|
||||
impl KeyEventExtModifierSupplement for KeyEvent {
|
||||
#[inline]
|
||||
fn text_with_all_modifiers(&self) -> Option<&str> {
|
||||
self.platform_specific.text_with_all_modifiers.as_ref().map(|s| s.as_str())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn key_without_modifiers(&self) -> Key {
|
||||
self.platform_specific.key_without_modifiers.clone()
|
||||
}
|
||||
}
|
||||
@@ -47,19 +47,19 @@ pub trait EventLoopExtPumpEvents {
|
||||
/// buffered and handled outside of Winit include:
|
||||
/// - `RedrawRequested` events, used to schedule rendering.
|
||||
///
|
||||
/// macOS for example uses a `drawRect` callback to drive rendering
|
||||
/// within applications and expects rendering to be finished before
|
||||
/// the `drawRect` callback returns.
|
||||
/// macOS for example uses a `drawRect` callback to drive rendering
|
||||
/// within applications and expects rendering to be finished before
|
||||
/// the `drawRect` callback returns.
|
||||
///
|
||||
/// For portability it's strongly recommended that applications should
|
||||
/// keep their rendering inside the closure provided to Winit.
|
||||
/// For portability it's strongly recommended that applications should
|
||||
/// keep their rendering inside the closure provided to Winit.
|
||||
/// - Any lifecycle events, such as `Suspended` / `Resumed`.
|
||||
///
|
||||
/// The handling of these events needs to be synchronized with the
|
||||
/// operating system and it would never be appropriate to buffer a
|
||||
/// notification that your application has been suspended or resumed and
|
||||
/// then handled that later since there would always be a chance that
|
||||
/// other lifecycle events occur while the event is buffered.
|
||||
/// The handling of these events needs to be synchronized with the
|
||||
/// operating system and it would never be appropriate to buffer a
|
||||
/// notification that your application has been suspended or resumed and
|
||||
/// then handled that later since there would always be a chance that
|
||||
/// other lifecycle events occur while the event is buffered.
|
||||
///
|
||||
/// ## Supported Platforms
|
||||
///
|
||||
|
||||
@@ -33,10 +33,10 @@ pub trait EventLoopExtRunOnDemand {
|
||||
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
|
||||
/// backend it is possible to use
|
||||
#[cfg_attr(
|
||||
any(web_platform, docsrs),
|
||||
web_platform,
|
||||
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
|
||||
)]
|
||||
#[cfg_attr(not(any(web_platform, docsrs)), doc = " `EventLoopExtWeb::spawn_app()`")]
|
||||
#[cfg_attr(not(web_platform), doc = " `EventLoopExtWeb::spawn_app()`")]
|
||||
/// [^1] more than once instead).
|
||||
/// - No [`Window`] state can be carried between separate runs of the event loop.
|
||||
///
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
use std::env;
|
||||
|
||||
use crate::error::NotSupportedError;
|
||||
use crate::error::{NotSupportedError, RequestError};
|
||||
use crate::event_loop::{ActiveEventLoop, AsyncRequestSerial};
|
||||
#[cfg(wayland_platform)]
|
||||
use crate::platform::wayland::ActiveEventLoopExtWayland;
|
||||
@@ -46,7 +46,7 @@ pub trait WindowExtStartupNotify {
|
||||
/// Request a new activation token.
|
||||
///
|
||||
/// The token will be delivered inside
|
||||
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError>;
|
||||
fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError>;
|
||||
}
|
||||
|
||||
pub trait WindowAttributesExtStartupNotify {
|
||||
@@ -57,7 +57,7 @@ pub trait WindowAttributesExtStartupNotify {
|
||||
fn with_activation_token(self, token: ActivationToken) -> Self;
|
||||
}
|
||||
|
||||
impl EventLoopExtStartupNotify for &dyn ActiveEventLoop {
|
||||
impl EventLoopExtStartupNotify for dyn ActiveEventLoop + '_ {
|
||||
fn read_token_from_env(&self) -> Option<ActivationToken> {
|
||||
#[cfg(x11_platform)]
|
||||
let _is_wayland = false;
|
||||
@@ -65,16 +65,26 @@ impl EventLoopExtStartupNotify for &dyn ActiveEventLoop {
|
||||
let _is_wayland = self.is_wayland();
|
||||
|
||||
if _is_wayland {
|
||||
env::var(WAYLAND_VAR).ok().map(ActivationToken::_new)
|
||||
env::var(WAYLAND_VAR).ok().map(ActivationToken::from_raw)
|
||||
} else {
|
||||
env::var(X11_VAR).ok().map(ActivationToken::_new)
|
||||
env::var(X11_VAR).ok().map(ActivationToken::from_raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowExtStartupNotify for Window {
|
||||
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
|
||||
self.window.request_activation_token()
|
||||
impl WindowExtStartupNotify for dyn Window + '_ {
|
||||
fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
|
||||
#[cfg(wayland_platform)]
|
||||
if let Some(window) = self.cast_ref::<crate::platform_impl::wayland::Window>() {
|
||||
return window.request_activation_token();
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
if let Some(window) = self.cast_ref::<crate::platform_impl::x11::window::Window>() {
|
||||
return window.request_activation_token();
|
||||
}
|
||||
|
||||
Err(NotSupportedError::new("startup notify is not supported").into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +108,6 @@ pub fn reset_activation_token_env() {
|
||||
///
|
||||
/// This could be used before running daemon processes.
|
||||
pub fn set_activation_token_env(token: ActivationToken) {
|
||||
env::set_var(X11_VAR, &token._token);
|
||||
env::set_var(WAYLAND_VAR, token._token);
|
||||
env::set_var(X11_VAR, &token.token);
|
||||
env::set_var(WAYLAND_VAR, token.token);
|
||||
}
|
||||
|
||||
@@ -13,11 +13,14 @@
|
||||
//! * `wayland-csd-adwaita` (default).
|
||||
//! * `wayland-csd-adwaita-crossfont`.
|
||||
//! * `wayland-csd-adwaita-notitle`.
|
||||
use crate::application::ApplicationHandler;
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
|
||||
use crate::monitor::MonitorHandle;
|
||||
use crate::platform_impl::wayland::Window;
|
||||
pub use crate::window::Theme;
|
||||
use crate::window::{Window, WindowAttributes};
|
||||
use crate::window::{Window as CoreWindow, WindowAttributes};
|
||||
|
||||
/// Additional methods on [`ActiveEventLoop`] that are specific to Wayland.
|
||||
pub trait ActiveEventLoopExtWayland {
|
||||
@@ -25,14 +28,10 @@ pub trait ActiveEventLoopExtWayland {
|
||||
fn is_wayland(&self) -> bool;
|
||||
}
|
||||
|
||||
pub trait WaylandApplicationHandler: ApplicationHandler + 'static {
|
||||
fn wayland_callback(&mut self);
|
||||
}
|
||||
|
||||
impl ActiveEventLoopExtWayland for &dyn ActiveEventLoop {
|
||||
impl ActiveEventLoopExtWayland for dyn ActiveEventLoop + '_ {
|
||||
#[inline]
|
||||
fn is_wayland(&self) -> bool {
|
||||
self.as_any().downcast_ref::<crate::platform_impl::wayland::ActiveEventLoop>().is_some()
|
||||
self.cast_ref::<crate::platform_impl::wayland::ActiveEventLoop>().is_some()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +39,6 @@ impl ActiveEventLoopExtWayland for &dyn ActiveEventLoop {
|
||||
pub trait EventLoopExtWayland {
|
||||
/// True if the [`EventLoop`] uses Wayland.
|
||||
fn is_wayland(&self) -> bool;
|
||||
|
||||
fn register_wayland_callback<T: WaylandApplicationHandler>(&mut self);
|
||||
}
|
||||
|
||||
impl EventLoopExtWayland for EventLoop {
|
||||
@@ -49,22 +46,6 @@ impl EventLoopExtWayland for EventLoop {
|
||||
fn is_wayland(&self) -> bool {
|
||||
self.event_loop.is_wayland()
|
||||
}
|
||||
|
||||
fn register_wayland_callback<T: WaylandApplicationHandler>(&mut self) {
|
||||
let event_loop = match &self.event_loop {
|
||||
crate::platform_impl::EventLoop::Wayland(event_loop) => &event_loop.active_event_loop,
|
||||
#[cfg(x11_platform)]
|
||||
crate::platform_impl::EventLoop::X(_) => return,
|
||||
};
|
||||
|
||||
event_loop.wayland_callback.set(Some(|app: &mut dyn ApplicationHandler| {
|
||||
app.as_any()
|
||||
.expect("as_any_mut is not implemented")
|
||||
.downcast_mut::<T>()
|
||||
.unwrap()
|
||||
.wayland_callback()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
|
||||
@@ -94,9 +75,19 @@ impl EventLoopBuilderExtWayland for EventLoopBuilder {
|
||||
}
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to Wayland.
|
||||
pub trait WindowExtWayland {}
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
pub trait WindowExtWayland {
|
||||
/// Returns `xdg_toplevel` of the window or [`None`] if the window is X11 window.
|
||||
fn xdg_toplevel(&self) -> Option<NonNull<c_void>>;
|
||||
}
|
||||
|
||||
impl WindowExtWayland for Window {}
|
||||
impl WindowExtWayland for dyn CoreWindow + '_ {
|
||||
#[inline]
|
||||
fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
|
||||
self.cast_ref::<Window>()?.xdg_toplevel()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`WindowAttributes`] that are specific to Wayland.
|
||||
pub trait WindowAttributesExtWayland {
|
||||
@@ -118,16 +109,3 @@ impl WindowAttributesExtWayland for WindowAttributes {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorHandle` that are specific to Wayland.
|
||||
pub trait MonitorHandleExtWayland {
|
||||
/// Returns the inner identifier of the monitor.
|
||||
fn native_id(&self) -> u32;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtWayland for MonitorHandle {
|
||||
#[inline]
|
||||
fn native_id(&self) -> u32 {
|
||||
self.inner.native_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,19 +27,18 @@
|
||||
//! - [`padding`](https://developer.mozilla.org/en-US/docs/Web/CSS/padding)
|
||||
//!
|
||||
//! The following APIs can't take them into account and will therefore provide inaccurate results:
|
||||
//! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`]
|
||||
//! - [`WindowEvent::SurfaceResized`] and [`Window::(set_)surface_size()`]
|
||||
//! - [`WindowEvent::Occluded`]
|
||||
//! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`], and
|
||||
//! [`WindowEvent::Touch`].
|
||||
//! - [`WindowEvent::PointerMoved`], [`WindowEvent::PointerEntered`] and
|
||||
//! [`WindowEvent::PointerLeft`].
|
||||
//! - [`Window::set_outer_position()`]
|
||||
//!
|
||||
//! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized
|
||||
//! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size
|
||||
//! [`WindowEvent::SurfaceResized`]: crate::event::WindowEvent::SurfaceResized
|
||||
//! [`Window::(set_)surface_size()`]: crate::window::Window::surface_size
|
||||
//! [`WindowEvent::Occluded`]: crate::event::WindowEvent::Occluded
|
||||
//! [`WindowEvent::CursorMoved`]: crate::event::WindowEvent::CursorMoved
|
||||
//! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered
|
||||
//! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft
|
||||
//! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch
|
||||
//! [`WindowEvent::PointerMoved`]: crate::event::WindowEvent::PointerMoved
|
||||
//! [`WindowEvent::PointerEntered`]: crate::event::WindowEvent::PointerEntered
|
||||
//! [`WindowEvent::PointerLeft`]: crate::event::WindowEvent::PointerLeft
|
||||
//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position
|
||||
|
||||
use std::cell::Ref;
|
||||
@@ -47,8 +46,8 @@ use std::error::Error;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -58,10 +57,9 @@ use web_sys::HtmlCanvasElement;
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::cursor::CustomCursorSource;
|
||||
use crate::error::NotSupportedError;
|
||||
use crate::event::FingerId;
|
||||
use crate::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use crate::monitor::MonitorHandle;
|
||||
use crate::platform_impl::PlatformCustomCursorSource;
|
||||
use crate::monitor::MonitorHandleProvider;
|
||||
use crate::platform_impl::MonitorHandle as WebMonitorHandle;
|
||||
#[cfg(web_platform)]
|
||||
use crate::platform_impl::{
|
||||
CustomCursorFuture as PlatformCustomCursorFuture,
|
||||
@@ -82,7 +80,7 @@ pub trait WindowExtWeb {
|
||||
|
||||
/// Returns [`true`] if calling `event.preventDefault()` is enabled.
|
||||
///
|
||||
/// See [`Window::set_prevent_default()`] for more details.
|
||||
/// See [`WindowExtWeb::set_prevent_default()`] for more details.
|
||||
fn prevent_default(&self) -> bool;
|
||||
|
||||
/// Sets whether `event.preventDefault()` should be called on events on the
|
||||
@@ -104,22 +102,28 @@ pub trait WindowExtWeb {
|
||||
fn is_cursor_lock_raw(&self) -> bool;
|
||||
}
|
||||
|
||||
impl WindowExtWeb for Window {
|
||||
impl WindowExtWeb for dyn Window + '_ {
|
||||
#[inline]
|
||||
fn canvas(&self) -> Option<Ref<'_, HtmlCanvasElement>> {
|
||||
self.window.canvas()
|
||||
self.cast_ref::<crate::platform_impl::Window>().expect("non Web window on Web").canvas()
|
||||
}
|
||||
|
||||
fn prevent_default(&self) -> bool {
|
||||
self.window.prevent_default()
|
||||
self.cast_ref::<crate::platform_impl::Window>()
|
||||
.expect("non Web window on Web")
|
||||
.prevent_default()
|
||||
}
|
||||
|
||||
fn set_prevent_default(&self, prevent_default: bool) {
|
||||
self.window.set_prevent_default(prevent_default)
|
||||
self.cast_ref::<crate::platform_impl::Window>()
|
||||
.expect("non Web window on Web")
|
||||
.set_prevent_default(prevent_default)
|
||||
}
|
||||
|
||||
fn is_cursor_lock_raw(&self) -> bool {
|
||||
self.window.is_cursor_lock_raw()
|
||||
self.cast_ref::<crate::platform_impl::Window>()
|
||||
.expect("non Web window on Web")
|
||||
.is_cursor_lock_raw()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +140,7 @@ pub trait WindowAttributesExtWeb {
|
||||
/// Sets whether `event.preventDefault()` should be called on events on the
|
||||
/// canvas that have side effects.
|
||||
///
|
||||
/// See [`Window::set_prevent_default()`] for more details.
|
||||
/// See [`WindowExtWeb::set_prevent_default()`] for more details.
|
||||
///
|
||||
/// Enabled by default.
|
||||
fn with_prevent_default(self, prevent_default: bool) -> Self;
|
||||
@@ -243,12 +247,18 @@ pub trait EventLoopExtWeb {
|
||||
///
|
||||
/// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New
|
||||
/// [`MonitorHandle`]s have to be created instead.
|
||||
///
|
||||
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
|
||||
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture;
|
||||
|
||||
/// Returns whether the user has given permission to access detailed monitor information.
|
||||
///
|
||||
/// [`MonitorHandle`]s don't automatically make use of detailed monitor information after
|
||||
/// permission is granted. New [`MonitorHandle`]s have to be created instead.
|
||||
///
|
||||
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
|
||||
///
|
||||
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
|
||||
fn has_detailed_monitor_permission(&self) -> HasMonitorPermissionFuture;
|
||||
}
|
||||
|
||||
@@ -338,21 +348,24 @@ pub trait ActiveEventLoopExtWeb {
|
||||
///
|
||||
/// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New
|
||||
/// [`MonitorHandle`]s have to be created instead.
|
||||
///
|
||||
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
|
||||
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture;
|
||||
|
||||
/// Returns whether the user has given permission to access detailed monitor information.
|
||||
///
|
||||
/// [`MonitorHandle`]s don't automatically make use of detailed monitor information after
|
||||
/// permission is granted. New [`MonitorHandle`]s have to be created instead.
|
||||
///
|
||||
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
|
||||
fn has_detailed_monitor_permission(&self) -> bool;
|
||||
}
|
||||
|
||||
impl ActiveEventLoopExtWeb for &dyn ActiveEventLoop {
|
||||
impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
|
||||
#[inline]
|
||||
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non Web event loop on Web");
|
||||
event_loop.create_custom_cursor_async(source)
|
||||
}
|
||||
@@ -360,8 +373,7 @@ impl ActiveEventLoopExtWeb for &dyn ActiveEventLoop {
|
||||
#[inline]
|
||||
fn set_poll_strategy(&self, strategy: PollStrategy) {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non Web event loop on Web");
|
||||
event_loop.set_poll_strategy(strategy);
|
||||
}
|
||||
@@ -369,8 +381,7 @@ impl ActiveEventLoopExtWeb for &dyn ActiveEventLoop {
|
||||
#[inline]
|
||||
fn poll_strategy(&self) -> PollStrategy {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non Web event loop on Web");
|
||||
event_loop.poll_strategy()
|
||||
}
|
||||
@@ -378,8 +389,7 @@ impl ActiveEventLoopExtWeb for &dyn ActiveEventLoop {
|
||||
#[inline]
|
||||
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non Web event loop on Web");
|
||||
event_loop.set_wait_until_strategy(strategy);
|
||||
}
|
||||
@@ -387,8 +397,7 @@ impl ActiveEventLoopExtWeb for &dyn ActiveEventLoop {
|
||||
#[inline]
|
||||
fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non Web event loop on Web");
|
||||
event_loop.wait_until_strategy()
|
||||
}
|
||||
@@ -396,8 +405,7 @@ impl ActiveEventLoopExtWeb for &dyn ActiveEventLoop {
|
||||
#[inline]
|
||||
fn is_cursor_lock_raw(&self) -> bool {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non Web event loop on Web");
|
||||
event_loop.is_cursor_lock_raw()
|
||||
}
|
||||
@@ -405,8 +413,7 @@ impl ActiveEventLoopExtWeb for &dyn ActiveEventLoop {
|
||||
#[inline]
|
||||
fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non Web event loop on Web");
|
||||
event_loop.has_multiple_screens()
|
||||
}
|
||||
@@ -414,8 +421,7 @@ impl ActiveEventLoopExtWeb for &dyn ActiveEventLoop {
|
||||
#[inline]
|
||||
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non Web event loop on Web");
|
||||
MonitorPermissionFuture(event_loop.request_detailed_monitor_permission())
|
||||
}
|
||||
@@ -423,8 +429,7 @@ impl ActiveEventLoopExtWeb for &dyn ActiveEventLoop {
|
||||
#[inline]
|
||||
fn has_detailed_monitor_permission(&self) -> bool {
|
||||
let event_loop = self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
|
||||
.expect("non Web event loop on Web");
|
||||
event_loop.has_detailed_monitor_permission()
|
||||
}
|
||||
@@ -481,73 +486,6 @@ pub enum WaitUntilStrategy {
|
||||
Worker,
|
||||
}
|
||||
|
||||
pub trait CustomCursorExtWeb {
|
||||
/// Returns if this cursor is an animation.
|
||||
fn is_animation(&self) -> bool;
|
||||
|
||||
/// Creates a new cursor from a URL pointing to an image.
|
||||
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
|
||||
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
|
||||
///
|
||||
/// [PNG]: https://en.wikipedia.org/wiki/PNG
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource;
|
||||
|
||||
/// Crates a new animated cursor from multiple [`CustomCursor`]s.
|
||||
/// Supplied `cursors` can't be empty or other animations.
|
||||
fn from_animation(
|
||||
duration: Duration,
|
||||
cursors: Vec<CustomCursor>,
|
||||
) -> Result<CustomCursorSource, BadAnimation>;
|
||||
}
|
||||
|
||||
impl CustomCursorExtWeb for CustomCursor {
|
||||
fn is_animation(&self) -> bool {
|
||||
self.inner.animation
|
||||
}
|
||||
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource {
|
||||
CustomCursorSource { inner: PlatformCustomCursorSource::Url { url, hotspot_x, hotspot_y } }
|
||||
}
|
||||
|
||||
fn from_animation(
|
||||
duration: Duration,
|
||||
cursors: Vec<CustomCursor>,
|
||||
) -> Result<CustomCursorSource, BadAnimation> {
|
||||
if cursors.is_empty() {
|
||||
return Err(BadAnimation::Empty);
|
||||
}
|
||||
|
||||
if cursors.iter().any(CustomCursor::is_animation) {
|
||||
return Err(BadAnimation::Animation);
|
||||
}
|
||||
|
||||
Ok(CustomCursorSource {
|
||||
inner: PlatformCustomCursorSource::Animation { duration, cursors },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An error produced when using [`CustomCursor::from_animation`] with invalid arguments.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum BadAnimation {
|
||||
/// Produced when no cursors were supplied.
|
||||
Empty,
|
||||
/// Produced when a supplied cursor is an animation.
|
||||
Animation,
|
||||
}
|
||||
|
||||
impl fmt::Display for BadAnimation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Empty => write!(f, "No cursors supplied"),
|
||||
Self::Animation => write!(f, "A supplied cursor is an animation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadAnimation {}
|
||||
|
||||
#[cfg(not(web_platform))]
|
||||
struct PlatformCustomCursorFuture;
|
||||
|
||||
@@ -558,7 +496,7 @@ impl Future for CustomCursorFuture {
|
||||
type Output = Result<CustomCursor, CustomCursorError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Pin::new(&mut self.0).poll(cx).map_ok(|cursor| CustomCursor { inner: cursor })
|
||||
Pin::new(&mut self.0).poll(cx).map_ok(|cursor| CustomCursor(Arc::new(cursor)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,6 +578,8 @@ impl Future for HasMonitorPermissionFuture {
|
||||
}
|
||||
|
||||
/// Additional methods on [`MonitorHandle`] that are specific to the Web.
|
||||
///
|
||||
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
|
||||
pub trait MonitorHandleExtWeb {
|
||||
/// Returns whether the screen is internal to the device or external.
|
||||
///
|
||||
@@ -667,28 +607,31 @@ pub trait MonitorHandleExtWeb {
|
||||
/// specific monitor.
|
||||
///
|
||||
/// See [`ActiveEventLoopExtWeb::request_detailed_monitor_permission()`].
|
||||
///
|
||||
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
|
||||
fn is_detailed(&self) -> bool;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtWeb for MonitorHandle {
|
||||
impl MonitorHandleExtWeb for dyn MonitorHandleProvider + '_ {
|
||||
fn is_internal(&self) -> Option<bool> {
|
||||
self.inner.is_internal()
|
||||
self.cast_ref::<WebMonitorHandle>().unwrap().is_internal()
|
||||
}
|
||||
|
||||
fn orientation(&self) -> OrientationData {
|
||||
self.inner.orientation()
|
||||
self.cast_ref::<WebMonitorHandle>().unwrap().orientation()
|
||||
}
|
||||
|
||||
fn request_lock(&self, orientation_lock: OrientationLock) -> OrientationLockFuture {
|
||||
OrientationLockFuture(self.inner.request_lock(orientation_lock))
|
||||
let future = self.cast_ref::<WebMonitorHandle>().unwrap().request_lock(orientation_lock);
|
||||
OrientationLockFuture(future)
|
||||
}
|
||||
|
||||
fn unlock(&self) -> Result<(), OrientationLockError> {
|
||||
self.inner.unlock()
|
||||
self.cast_ref::<WebMonitorHandle>().unwrap().unlock()
|
||||
}
|
||||
|
||||
fn is_detailed(&self) -> bool {
|
||||
self.inner.is_detailed()
|
||||
self.cast_ref::<WebMonitorHandle>().unwrap().is_detailed()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,10 +642,9 @@ pub struct OrientationData {
|
||||
pub orientation: Orientation,
|
||||
/// [`true`] if the [`orientation`](Self::orientation) is flipped upside down.
|
||||
pub flipped: bool,
|
||||
/// [`true`] if the [`Orientation`] is the most natural one for the screen regardless of being
|
||||
/// flipped. Computer monitors are commonly naturally landscape mode, while mobile phones
|
||||
/// are commonly naturally portrait mode.
|
||||
pub natural: bool,
|
||||
/// The most natural orientation for the screen. Computer monitors are commonly naturally
|
||||
/// landscape mode, while mobile phones are commonly naturally portrait mode.
|
||||
pub natural: Orientation,
|
||||
}
|
||||
|
||||
/// Screen orientation.
|
||||
@@ -726,14 +668,14 @@ pub enum OrientationLock {
|
||||
/// User is locked to landscape mode.
|
||||
Landscape {
|
||||
/// - [`None`]: User is locked to both upright or upside down landscape mode.
|
||||
/// - [`false`]: User is locked to upright landscape mode.
|
||||
/// - [`true`]: User is locked to upright landscape mode.
|
||||
/// - [`false`]: User is locked to upside down landscape mode.
|
||||
flipped: Option<bool>,
|
||||
},
|
||||
/// User is locked to portrait mode.
|
||||
Portrait {
|
||||
/// - [`None`]: User is locked to both upright or upside down portrait mode.
|
||||
/// - [`false`]: User is locked to upright portrait mode.
|
||||
/// - [`true`]: User is locked to upright portrait mode.
|
||||
/// - [`false`]: User is locked to upside down portrait mode.
|
||||
flipped: Option<bool>,
|
||||
},
|
||||
@@ -770,16 +712,3 @@ impl Display for OrientationLockError {
|
||||
}
|
||||
|
||||
impl Error for OrientationLockError {}
|
||||
|
||||
/// Additional methods on [`FingerId`] that are specific to Web.
|
||||
pub trait FingerIdExtWeb {
|
||||
/// Indicates if the finger represents the first contact in a multi-touch interaction.
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_primary(self) -> bool;
|
||||
}
|
||||
|
||||
impl FingerIdExtWeb for FingerId {
|
||||
fn is_primary(self) -> bool {
|
||||
self.0.is_primary()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,28 @@
|
||||
//! tested regularly.
|
||||
use std::borrow::Borrow;
|
||||
use std::ffi::c_void;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(windows_platform)]
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
|
||||
use crate::dpi::PhysicalSize;
|
||||
use crate::event::{DeviceId, FingerId};
|
||||
use crate::event::DeviceId;
|
||||
use crate::event_loop::EventLoopBuilder;
|
||||
use crate::monitor::MonitorHandle;
|
||||
use crate::window::{BadIcon, Icon, Window, WindowAttributes};
|
||||
use crate::icon::BadIcon;
|
||||
use crate::platform_impl::RaiiIcon;
|
||||
use crate::window::{Icon, Window, WindowAttributes};
|
||||
|
||||
/// Window Handle type used by Win32 API
|
||||
pub type HWND = isize;
|
||||
pub type HWND = *mut c_void;
|
||||
/// Menu Handle type used by Win32 API
|
||||
pub type HMENU = isize;
|
||||
pub type HMENU = *mut c_void;
|
||||
/// Monitor Handle type used by Win32 API
|
||||
pub type HMONITOR = isize;
|
||||
pub type HMONITOR = *mut c_void;
|
||||
|
||||
/// Describes a system-drawn backdrop material of a window.
|
||||
///
|
||||
@@ -115,50 +120,24 @@ pub enum CornerPreference {
|
||||
///
|
||||
/// See [`WindowBorrowExtWindows::any_thread`] for more information.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnyThread<W>(W);
|
||||
pub struct AnyThread<W: Window>(W);
|
||||
|
||||
impl<W: Borrow<Window>> AnyThread<W> {
|
||||
impl<W: Window> AnyThread<W> {
|
||||
/// Get a reference to the inner window.
|
||||
#[inline]
|
||||
pub fn get_ref(&self) -> &Window {
|
||||
self.0.borrow()
|
||||
}
|
||||
|
||||
/// Get a reference to the inner object.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &W {
|
||||
pub fn get_ref(&self) -> &dyn Window {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Unwrap and get the inner window.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> W {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Borrow<Window>> AsRef<Window> for AnyThread<W> {
|
||||
fn as_ref(&self) -> &Window {
|
||||
self.get_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Borrow<Window>> Borrow<Window> for AnyThread<W> {
|
||||
fn borrow(&self) -> &Window {
|
||||
self.get_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Borrow<Window>> std::ops::Deref for AnyThread<W> {
|
||||
type Target = Window;
|
||||
|
||||
impl<W: Window> Deref for AnyThread<W> {
|
||||
type Target = W;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.get_ref()
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl<W: Borrow<Window>> rwh_06::HasWindowHandle for AnyThread<W> {
|
||||
impl<W: Window> rwh_06::HasWindowHandle for AnyThread<W> {
|
||||
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
|
||||
// SAFETY: The top level user has asserted this is only used safely.
|
||||
unsafe { self.get_ref().window_handle_any_thread() }
|
||||
@@ -341,7 +320,7 @@ pub trait WindowExtWindows {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use winit::window::Window;
|
||||
/// # fn scope(window: Window) {
|
||||
/// # fn scope(window: Box<dyn Window>) {
|
||||
/// use std::thread;
|
||||
///
|
||||
/// use winit::platform::windows::WindowExtWindows;
|
||||
@@ -359,41 +338,46 @@ pub trait WindowExtWindows {
|
||||
/// });
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "rwh_06")]
|
||||
unsafe fn window_handle_any_thread(
|
||||
&self,
|
||||
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError>;
|
||||
}
|
||||
|
||||
impl WindowExtWindows for Window {
|
||||
impl WindowExtWindows for dyn Window + '_ {
|
||||
#[inline]
|
||||
fn set_enable(&self, enabled: bool) {
|
||||
self.window.set_enable(enabled)
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.set_enable(enabled)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
|
||||
self.window.set_taskbar_icon(taskbar_icon)
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.set_taskbar_icon(taskbar_icon)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_skip_taskbar(&self, skip: bool) {
|
||||
self.window.set_skip_taskbar(skip)
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.set_skip_taskbar(skip)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_undecorated_shadow(&self, shadow: bool) {
|
||||
self.window.set_undecorated_shadow(shadow)
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.set_undecorated_shadow(shadow)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_system_backdrop(&self, backdrop_type: BackdropType) {
|
||||
self.window.set_system_backdrop(backdrop_type)
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.set_system_backdrop(backdrop_type)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_border_color(&self, color: Option<Color>) {
|
||||
self.window.set_border_color(color.unwrap_or(Color::NONE))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.set_border_color(color.unwrap_or(Color::NONE))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -401,25 +385,28 @@ impl WindowExtWindows for Window {
|
||||
// The windows docs don't mention NONE as a valid options but it works in practice and is
|
||||
// useful to circumvent the Windows option "Show accent color on title bars and
|
||||
// window borders"
|
||||
self.window.set_title_background_color(color.unwrap_or(Color::NONE))
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.set_title_background_color(color.unwrap_or(Color::NONE))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_title_text_color(&self, color: Color) {
|
||||
self.window.set_title_text_color(color)
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.set_title_text_color(color)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_corner_preference(&self, preference: CornerPreference) {
|
||||
self.window.set_corner_preference(preference)
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
window.set_corner_preference(preference)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
unsafe fn window_handle_any_thread(
|
||||
&self,
|
||||
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
|
||||
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
|
||||
unsafe {
|
||||
let handle = self.window.rwh_06_no_thread_check()?;
|
||||
let handle = window.rwh_06_no_thread_check()?;
|
||||
|
||||
// SAFETY: The handle is valid in this context.
|
||||
Ok(rwh_06::WindowHandle::borrow_raw(handle))
|
||||
@@ -430,7 +417,7 @@ impl WindowExtWindows for Window {
|
||||
/// Additional methods for anything that dereference to [`Window`].
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
pub trait WindowBorrowExtWindows: Borrow<Window> + Sized {
|
||||
pub trait WindowBorrowExtWindows: Borrow<dyn Window> + Sized {
|
||||
/// Create an object that allows accessing the inner window handle in a thread-unsafe way.
|
||||
///
|
||||
/// It is possible to call [`window_handle_any_thread`] to get around Windows's thread
|
||||
@@ -447,22 +434,17 @@ pub trait WindowBorrowExtWindows: Borrow<Window> + Sized {
|
||||
/// Win32 APIs.
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
#[cfg_attr(
|
||||
feature = "rwh_06",
|
||||
doc = "[`HasWindowHandle`]: rwh_06::HasWindowHandle",
|
||||
doc = "[`window_handle_any_thread`]: WindowExtWindows::window_handle_any_thread"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(feature = "rwh_06"),
|
||||
doc = "[`HasWindowHandle`]: #only-available-with-rwh_06",
|
||||
doc = "[`window_handle_any_thread`]: #only-available-with-rwh_06"
|
||||
)]
|
||||
unsafe fn any_thread(self) -> AnyThread<Self> {
|
||||
/// [`HasWindowHandle`]: rwh_06::HasWindowHandle
|
||||
/// [`window_handle_any_thread`]: WindowExtWindows::window_handle_any_thread
|
||||
unsafe fn any_thread(self) -> AnyThread<Self>
|
||||
where
|
||||
Self: Window,
|
||||
{
|
||||
AnyThread(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Borrow<Window> + Sized> WindowBorrowExtWindows for W {}
|
||||
impl<W: Borrow<dyn Window> + Sized> WindowBorrowExtWindows for W {}
|
||||
|
||||
/// Additional methods on `WindowAttributes` that are specific to Windows.
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
@@ -638,27 +620,6 @@ impl WindowAttributesExtWindows for WindowAttributes {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorHandle` that are specific to Windows.
|
||||
pub trait MonitorHandleExtWindows {
|
||||
/// Returns the name of the monitor adapter specific to the Win32 API.
|
||||
fn native_id(&self) -> String;
|
||||
|
||||
/// Returns the handle of the monitor - `HMONITOR`.
|
||||
fn hmonitor(&self) -> HMONITOR;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtWindows for MonitorHandle {
|
||||
#[inline]
|
||||
fn native_id(&self) -> String {
|
||||
self.inner.native_identifier()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hmonitor(&self) -> HMONITOR {
|
||||
self.inner.hmonitor()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `DeviceId` that are specific to Windows.
|
||||
pub trait DeviceIdExtWindows {
|
||||
/// Returns an identifier that persistently refers to this specific device.
|
||||
@@ -667,29 +628,36 @@ pub trait DeviceIdExtWindows {
|
||||
fn persistent_identifier(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
#[cfg(windows_platform)]
|
||||
impl DeviceIdExtWindows for DeviceId {
|
||||
#[inline]
|
||||
fn persistent_identifier(&self) -> Option<String> {
|
||||
self.0.persistent_identifier()
|
||||
let raw_id = self.into_raw();
|
||||
if raw_id != 0 {
|
||||
crate::platform_impl::raw_input::get_raw_input_device_name(raw_id as HANDLE)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `FingerId` that are specific to Windows.
|
||||
pub trait FingerIdExtWindows {
|
||||
/// Indicates if the finger represents the first contact in a multi-touch interaction.
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_primary(self) -> bool;
|
||||
/// Windows specific `Icon`.
|
||||
///
|
||||
/// Windows icons can be created from files, or from the [`embedded resources`](https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files).
|
||||
///
|
||||
/// The `ICON` resource definition statement use the following syntax:
|
||||
/// ```rc
|
||||
/// nameID ICON filename
|
||||
/// ```
|
||||
/// `nameID` is a unique name or a 16-bit unsigned integer value identifying the resource,
|
||||
/// `filename` is the name of the file that contains the resource.
|
||||
///
|
||||
/// More information about the `ICON` resource can be found at [`Microsoft Learn`](https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource) portal.
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct WinIcon {
|
||||
pub(crate) inner: Arc<RaiiIcon>,
|
||||
}
|
||||
|
||||
impl FingerIdExtWindows for FingerId {
|
||||
#[inline]
|
||||
fn is_primary(self) -> bool {
|
||||
self.0.is_primary()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Icon` that are specific to Windows.
|
||||
pub trait IconExtWindows: Sized {
|
||||
impl WinIcon {
|
||||
/// Create an icon from a file path.
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
@@ -697,30 +665,88 @@ pub trait IconExtWindows: Sized {
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
|
||||
-> Result<Self, BadIcon>;
|
||||
pub fn from_path<P: AsRef<Path>>(
|
||||
path: P,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
Self::from_path_impl(path, size)
|
||||
}
|
||||
|
||||
/// Create an icon from a resource embedded in this executable or library.
|
||||
/// Create an icon from a resource embedded in this executable or library by its ordinal id.
|
||||
///
|
||||
/// The valid `ordinal` values range from 1 to [`u16::MAX`] (inclusive). The value `0` is an
|
||||
/// invalid ordinal id, but it can be used with [`from_resource_name`] as `"0"`.
|
||||
///
|
||||
/// [`from_resource_name`]: Self::from_resource_name
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
|
||||
}
|
||||
|
||||
impl IconExtWindows for Icon {
|
||||
fn from_path<P: AsRef<Path>>(
|
||||
path: P,
|
||||
pub fn from_resource(
|
||||
resource_id: u16,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
let win_icon = crate::platform_impl::WinIcon::from_path(path, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
Self::from_resource_impl(resource_id, size)
|
||||
}
|
||||
|
||||
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
|
||||
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
/// Create an icon from a resource embedded in this executable or library by its name.
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// Consider the following resource definition statements:
|
||||
/// ```rc
|
||||
/// app ICON "app.ico"
|
||||
/// 1 ICON "a.ico"
|
||||
/// 0027 ICON "custom.ico"
|
||||
/// 0 ICON "alt.ico"
|
||||
/// ```
|
||||
///
|
||||
/// Due to some internal implementation details of the resource embedding/loading process on
|
||||
/// Windows platform, strings that can be interpreted as 16-bit unsigned integers (`"1"`,
|
||||
/// `"002"`, etc.) cannot be used as valid resource names, and instead should be passed into
|
||||
/// [`from_resource`]:
|
||||
///
|
||||
/// [`from_resource`]: Self::from_resource
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use winit::platform::windows::WinIcon;
|
||||
///
|
||||
/// assert!(WinIcon::from_resource_name("app", None).is_ok());
|
||||
/// assert!(WinIcon::from_resource(1, None).is_ok());
|
||||
/// assert!(WinIcon::from_resource(27, None).is_ok());
|
||||
/// assert!(WinIcon::from_resource_name("27", None).is_err());
|
||||
/// assert!(WinIcon::from_resource_name("0027", None).is_err());
|
||||
/// ```
|
||||
///
|
||||
/// While `0` cannot be used as an ordinal id (see [`from_resource`]), it can be used as a
|
||||
/// name:
|
||||
///
|
||||
/// [`from_resource`]: IconExtWindows::from_resource
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use winit::platform::windows::WinIcon;
|
||||
/// # use winit::window::Icon;
|
||||
/// assert!(WinIcon::from_resource_name("0", None).is_ok());
|
||||
/// assert!(WinIcon::from_resource(0, None).is_err());
|
||||
/// ```
|
||||
pub fn from_resource_name(
|
||||
resource_name: &str,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
Self::from_resource_name_impl(resource_name, size)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WinIcon> for Icon {
|
||||
fn from(value: WinIcon) -> Self {
|
||||
Self(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::dpi::Size;
|
||||
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
|
||||
use crate::monitor::MonitorHandle;
|
||||
use crate::window::{Window, WindowAttributes};
|
||||
use crate::window::{Window as CoreWindow, WindowAttributes};
|
||||
|
||||
/// X window type. Maps directly to
|
||||
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
|
||||
@@ -80,9 +79,7 @@ pub type XWindow = u32;
|
||||
#[inline]
|
||||
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
|
||||
// Append new hook.
|
||||
unsafe {
|
||||
crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
|
||||
}
|
||||
crate::platform_impl::x11::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
|
||||
}
|
||||
|
||||
/// Additional methods on [`ActiveEventLoop`] that are specific to X11.
|
||||
@@ -91,10 +88,10 @@ pub trait ActiveEventLoopExtX11 {
|
||||
fn is_x11(&self) -> bool;
|
||||
}
|
||||
|
||||
impl ActiveEventLoopExtX11 for &dyn ActiveEventLoop {
|
||||
impl ActiveEventLoopExtX11 for dyn ActiveEventLoop + '_ {
|
||||
#[inline]
|
||||
fn is_x11(&self) -> bool {
|
||||
self.as_any().downcast_ref::<crate::platform_impl::x11::ActiveEventLoop>().is_some()
|
||||
self.cast_ref::<crate::platform_impl::x11::ActiveEventLoop>().is_some()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,9 +135,11 @@ impl EventLoopBuilderExtX11 for EventLoopBuilder {
|
||||
}
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to X11.
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
pub trait WindowExtX11 {}
|
||||
|
||||
impl WindowExtX11 for Window {}
|
||||
impl WindowExtX11 for dyn CoreWindow {}
|
||||
|
||||
/// Additional methods on [`WindowAttributes`] that are specific to X11.
|
||||
pub trait WindowAttributesExtX11 {
|
||||
@@ -169,13 +168,13 @@ pub trait WindowAttributesExtX11 {
|
||||
///
|
||||
/// ```
|
||||
/// # use winit::dpi::{LogicalSize, PhysicalSize};
|
||||
/// # use winit::window::Window;
|
||||
/// # use winit::window::{Window, WindowAttributes};
|
||||
/// # use winit::platform::x11::WindowAttributesExtX11;
|
||||
/// // Specify the size in logical dimensions like this:
|
||||
/// Window::default_attributes().with_base_size(LogicalSize::new(400.0, 200.0));
|
||||
/// WindowAttributes::default().with_base_size(LogicalSize::new(400.0, 200.0));
|
||||
///
|
||||
/// // Or specify the size in physical dimensions like this:
|
||||
/// Window::default_attributes().with_base_size(PhysicalSize::new(400, 200));
|
||||
/// WindowAttributes::default().with_base_size(PhysicalSize::new(400, 200));
|
||||
/// ```
|
||||
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
|
||||
|
||||
@@ -184,12 +183,12 @@ pub trait WindowAttributesExtX11 {
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use winit::window::Window;
|
||||
/// use winit::window::{Window, WindowAttributes};
|
||||
/// use winit::event_loop::ActiveEventLoop;
|
||||
/// use winit::platform::x11::{XWindow, WindowAttributesExtX11};
|
||||
/// # fn create_window(event_loop: &dyn ActiveEventLoop) -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?;
|
||||
/// let window_attributes = Window::default_attributes().with_embed_parent_window(parent_window_id);
|
||||
/// let window_attributes = WindowAttributes::default().with_embed_parent_window(parent_window_id);
|
||||
/// let window = event_loop.create_window(window_attributes)?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
@@ -240,16 +239,3 @@ impl WindowAttributesExtX11 for WindowAttributes {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorHandle` that are specific to X11.
|
||||
pub trait MonitorHandleExtX11 {
|
||||
/// Returns the inner identifier of the monitor.
|
||||
fn native_id(&self) -> u32;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtX11 for MonitorHandle {
|
||||
#[inline]
|
||||
fn native_id(&self) -> u32 {
|
||||
self.inner.native_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +143,8 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
|
||||
Keycode::AltLeft => KeyCode::AltLeft,
|
||||
Keycode::AltRight => KeyCode::AltRight,
|
||||
|
||||
Keycode::MetaLeft => KeyCode::SuperLeft,
|
||||
Keycode::MetaRight => KeyCode::SuperRight,
|
||||
Keycode::MetaLeft => KeyCode::MetaLeft,
|
||||
Keycode::MetaRight => KeyCode::MetaRight,
|
||||
|
||||
Keycode::LeftBracket => KeyCode::BracketLeft,
|
||||
Keycode::RightBracket => KeyCode::BracketRight,
|
||||
@@ -309,7 +309,7 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
|
||||
ShiftLeft => Key::Named(NamedKey::Shift),
|
||||
ShiftRight => Key::Named(NamedKey::Shift),
|
||||
Tab => Key::Named(NamedKey::Tab),
|
||||
Space => Key::Named(NamedKey::Space),
|
||||
Space => Key::Character(" ".into()),
|
||||
Sym => Key::Named(NamedKey::Symbol),
|
||||
Explorer => Key::Named(NamedKey::LaunchWebBrowser),
|
||||
Envelope => Key::Named(NamedKey::LaunchMail),
|
||||
@@ -340,8 +340,8 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
|
||||
CtrlRight => Key::Named(NamedKey::Control),
|
||||
CapsLock => Key::Named(NamedKey::CapsLock),
|
||||
ScrollLock => Key::Named(NamedKey::ScrollLock),
|
||||
MetaLeft => Key::Named(NamedKey::Super),
|
||||
MetaRight => Key::Named(NamedKey::Super),
|
||||
MetaLeft => Key::Named(NamedKey::Meta),
|
||||
MetaRight => Key::Named(NamedKey::Meta),
|
||||
Function => Key::Named(NamedKey::Fn),
|
||||
Sysrq => Key::Named(NamedKey::PrintScreen),
|
||||
Break => Key::Named(NamedKey::Pause),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,52 +1,103 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
|
||||
use objc2_foundation::{MainThreadMarker, NSObject};
|
||||
use std::cell::Cell;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::app_state::ApplicationDelegate;
|
||||
use super::DEVICE_ID;
|
||||
use dispatch2::MainThreadBound;
|
||||
use objc2::runtime::{Imp, Sel};
|
||||
use objc2::sel;
|
||||
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
|
||||
use objc2_foundation::MainThreadMarker;
|
||||
|
||||
use super::app_state::AppState;
|
||||
use crate::event::{DeviceEvent, ElementState};
|
||||
|
||||
declare_class!(
|
||||
pub(super) struct WinitApplication;
|
||||
type SendEvent = extern "C-unwind" fn(&NSApplication, Sel, &NSEvent);
|
||||
|
||||
unsafe impl ClassType for WinitApplication {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSApplication;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitApplication";
|
||||
}
|
||||
static ORIGINAL: MainThreadBound<Cell<Option<SendEvent>>> = {
|
||||
// SAFETY: Creating in a `const` context, where there is no concept of the main thread.
|
||||
MainThreadBound::new(Cell::new(None), unsafe { MainThreadMarker::new_unchecked() })
|
||||
};
|
||||
|
||||
impl DeclaredClass for WinitApplication {}
|
||||
extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) {
|
||||
let mtm = MainThreadMarker::from(app);
|
||||
|
||||
unsafe impl WinitApplication {
|
||||
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
|
||||
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
|
||||
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
|
||||
#[method(sendEvent:)]
|
||||
fn send_event(&self, event: &NSEvent) {
|
||||
// For posterity, there are some undocumented event types
|
||||
// (https://github.com/servo/cocoa-rs/issues/155)
|
||||
// but that doesn't really matter here.
|
||||
let event_type = unsafe { event.r#type() };
|
||||
let modifier_flags = unsafe { event.modifierFlags() };
|
||||
if event_type == NSEventType::KeyUp
|
||||
&& modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand)
|
||||
{
|
||||
if let Some(key_window) = self.keyWindow() {
|
||||
key_window.sendEvent(event);
|
||||
}
|
||||
} else {
|
||||
let delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
|
||||
maybe_dispatch_device_event(&delegate, event);
|
||||
unsafe { msg_send![super(self), sendEvent: event] }
|
||||
}
|
||||
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
|
||||
// Overriding `sendEvent:` fixes that. (https://stackoverflow.com/a/15294196)
|
||||
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
|
||||
//
|
||||
// For posterity, there are some undocumented event types
|
||||
// (https://github.com/servo/cocoa-rs/issues/155)
|
||||
// but that doesn't really matter here.
|
||||
let event_type = unsafe { event.r#type() };
|
||||
let modifier_flags = unsafe { event.modifierFlags() };
|
||||
if event_type == NSEventType::KeyUp && modifier_flags.contains(NSEventModifierFlags::Command) {
|
||||
if let Some(key_window) = app.keyWindow() {
|
||||
key_window.sendEvent(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) {
|
||||
// Events are generally scoped to the window level, so the best way
|
||||
// to get device events is to listen for them on NSApplication.
|
||||
let app_state = AppState::get(mtm);
|
||||
maybe_dispatch_device_event(&app_state, event);
|
||||
|
||||
let original = ORIGINAL.get(mtm).get().expect("no existing sendEvent: handler set");
|
||||
original(app, sel, event)
|
||||
}
|
||||
|
||||
/// Override the [`sendEvent:`][NSApplication::sendEvent] method on the given application class.
|
||||
///
|
||||
/// The previous implementation created a subclass of [`NSApplication`], however we would like to
|
||||
/// give the user full control over their `NSApplication`, so we override the method here using
|
||||
/// method swizzling instead.
|
||||
///
|
||||
/// This _should_ also allow two versions of Winit to exist in the same application.
|
||||
///
|
||||
/// See the following links for more info on method swizzling:
|
||||
/// - <https://nshipster.com/method-swizzling/>
|
||||
/// - <https://spin.atomicobject.com/method-swizzling-objective-c/>
|
||||
/// - <https://web.archive.org/web/20130308110627/http://cocoadev.com/wiki/MethodSwizzling>
|
||||
///
|
||||
/// NOTE: This function assumes that the passed in application object is the one returned from
|
||||
/// [`NSApplication::sharedApplication`], i.e. the one and only global shared application object.
|
||||
/// For testing though, we allow it to be a different object.
|
||||
pub(crate) fn override_send_event(global_app: &NSApplication) {
|
||||
let mtm = MainThreadMarker::from(global_app);
|
||||
let class = global_app.class();
|
||||
|
||||
let method =
|
||||
class.instance_method(sel!(sendEvent:)).expect("NSApplication must have sendEvent: method");
|
||||
|
||||
// SAFETY: Converting our `sendEvent:` implementation to an IMP.
|
||||
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
|
||||
|
||||
// If we've already overridden the method, don't do anything.
|
||||
// FIXME(madsmtm): Use `std::ptr::fn_addr_eq` (Rust 1.85) once available in MSRV.
|
||||
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
|
||||
if overridden == method.implementation() {
|
||||
return;
|
||||
}
|
||||
|
||||
// SAFETY: Our implementation has:
|
||||
// 1. The same signature as `sendEvent:`.
|
||||
// 2. Does not impose extra safety requirements on callers.
|
||||
let original = unsafe { method.set_implementation(overridden) };
|
||||
|
||||
// SAFETY: This is the actual signature of `sendEvent:`.
|
||||
let original = unsafe { mem::transmute::<Imp, SendEvent>(original) };
|
||||
|
||||
// NOTE: If NSApplication was safe to use from multiple threads, then this would potentially be
|
||||
// a (checked) race-condition, since one could call `sendEvent:` before the original had been
|
||||
// stored here.
|
||||
//
|
||||
// It is only usable from the main thread, however, so we're good!
|
||||
ORIGINAL.get(mtm).set(Some(original));
|
||||
}
|
||||
|
||||
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
|
||||
let event_type = unsafe { event.r#type() };
|
||||
#[allow(non_upper_case_globals)]
|
||||
match event_type {
|
||||
@@ -58,8 +109,8 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
|
||||
let delta_y = unsafe { event.deltaY() } as f64;
|
||||
|
||||
if delta_x != 0.0 || delta_y != 0.0 {
|
||||
delegate.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseMotion {
|
||||
app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, None, DeviceEvent::PointerMotion {
|
||||
delta: (delta_x, delta_y),
|
||||
});
|
||||
});
|
||||
@@ -67,8 +118,8 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
|
||||
},
|
||||
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
|
||||
let button = unsafe { event.buttonNumber() } as u32;
|
||||
delegate.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
|
||||
app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, None, DeviceEvent::Button {
|
||||
button,
|
||||
state: ElementState::Pressed,
|
||||
});
|
||||
@@ -76,8 +127,8 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
|
||||
},
|
||||
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
|
||||
let button = unsafe { event.buttonNumber() } as u32;
|
||||
delegate.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
|
||||
app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, None, DeviceEvent::Button {
|
||||
button,
|
||||
state: ElementState::Released,
|
||||
});
|
||||
@@ -86,3 +137,52 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{define_class, msg_send, ClassType};
|
||||
use objc2_app_kit::NSResponder;
|
||||
use objc2_foundation::NSObject;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_override() {
|
||||
// FIXME(madsmtm): Ensure this always runs (maybe use cargo-nextest or `--test-threads=1`?)
|
||||
let Some(mtm) = MainThreadMarker::new() else { return };
|
||||
|
||||
// Create a new application, without making it the shared application.
|
||||
let app = unsafe { NSApplication::new(mtm) };
|
||||
override_send_event(&app);
|
||||
// Test calling twice works.
|
||||
override_send_event(&app);
|
||||
|
||||
// FIXME(madsmtm): Can't test this yet, need some way to mock AppState.
|
||||
// unsafe {
|
||||
// let event = super::super::event::dummy_event().unwrap();
|
||||
// app.sendEvent(&event)
|
||||
// }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_class() {
|
||||
let Some(_mtm) = MainThreadMarker::new() else { return };
|
||||
|
||||
define_class!(
|
||||
#[unsafe(super(NSApplication, NSResponder, NSObject))]
|
||||
#[name = "TestApplication"]
|
||||
pub(super) struct TestApplication;
|
||||
|
||||
impl TestApplication {
|
||||
#[unsafe(method(sendEvent:))]
|
||||
fn send_event(&self, _event: &NSEvent) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let app: Retained<TestApplication> = unsafe { msg_send![TestApplication::class(), new] };
|
||||
override_send_event(&app);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::cell::{Cell, OnceCell, RefCell};
|
||||
use std::mem;
|
||||
use std::rc::Weak;
|
||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
|
||||
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
|
||||
use dispatch2::MainThreadBound;
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
|
||||
use objc2_foundation::NSNotification;
|
||||
|
||||
use super::event_handler::EventHandler;
|
||||
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
|
||||
use super::super::event_handler::EventHandler;
|
||||
use super::super::event_loop_proxy::EventLoopProxy;
|
||||
use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop, PanicInfo};
|
||||
use super::menu;
|
||||
use super::observer::{EventLoopWaker, RunLoop};
|
||||
use super::{menu, WindowId};
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::event::{StartCause, WindowEvent};
|
||||
use crate::event_loop::ControlFlow;
|
||||
use crate::window::WindowId as RootWindowId;
|
||||
use crate::window::WindowId;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct AppState {
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
mtm: MainThreadMarker,
|
||||
activation_policy: Option<NSApplicationActivationPolicy>,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
run_loop: RunLoop,
|
||||
proxy_wake_up: Arc<AtomicBool>,
|
||||
event_loop_proxy: Arc<EventLoopProxy>,
|
||||
event_handler: EventHandler,
|
||||
stop_on_launch: Cell<bool>,
|
||||
stop_before_wait: Cell<bool>,
|
||||
@@ -46,48 +47,29 @@ pub(super) struct AppState {
|
||||
// as such should be careful to not add fields that, in turn, strongly reference those.
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ApplicationDelegate;
|
||||
// SAFETY: Creating `MainThreadBound` in a `const` context, where there is no concept of the
|
||||
// main thread.
|
||||
static GLOBAL: MainThreadBound<OnceCell<Rc<AppState>>> =
|
||||
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
|
||||
|
||||
unsafe impl ClassType for ApplicationDelegate {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitApplicationDelegate";
|
||||
}
|
||||
|
||||
impl DeclaredClass for ApplicationDelegate {
|
||||
type Ivars = AppState;
|
||||
}
|
||||
|
||||
unsafe impl NSObjectProtocol for ApplicationDelegate {}
|
||||
|
||||
unsafe impl NSApplicationDelegate for ApplicationDelegate {
|
||||
#[method(applicationDidFinishLaunching:)]
|
||||
fn app_did_finish_launching(&self, notification: &NSNotification) {
|
||||
self.did_finish_launching(notification)
|
||||
}
|
||||
|
||||
#[method(applicationWillTerminate:)]
|
||||
fn app_will_terminate(&self, notification: &NSNotification) {
|
||||
self.will_terminate(notification)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl ApplicationDelegate {
|
||||
pub(super) fn new(
|
||||
impl AppState {
|
||||
pub(super) fn setup_global(
|
||||
mtm: MainThreadMarker,
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
activation_policy: Option<NSApplicationActivationPolicy>,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
) -> Retained<Self> {
|
||||
let this = mtm.alloc().set_ivars(AppState {
|
||||
) -> Rc<Self> {
|
||||
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
|
||||
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
|
||||
}));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
mtm,
|
||||
activation_policy,
|
||||
proxy_wake_up: Arc::new(AtomicBool::new(false)),
|
||||
default_menu,
|
||||
activate_ignoring_other_apps,
|
||||
run_loop: RunLoop::main(mtm),
|
||||
event_loop_proxy,
|
||||
event_handler: EventHandler::new(),
|
||||
stop_on_launch: Cell::new(false),
|
||||
stop_before_wait: Cell::new(false),
|
||||
@@ -102,33 +84,56 @@ impl ApplicationDelegate {
|
||||
wait_timeout: Cell::new(None),
|
||||
pending_redraw: RefCell::new(vec![]),
|
||||
});
|
||||
unsafe { msg_send_id![super(this), init] }
|
||||
|
||||
GLOBAL.get(mtm).set(this.clone()).expect("application state can only be set once");
|
||||
this
|
||||
}
|
||||
|
||||
// NOTE: This will, globally, only be run once, no matter how many
|
||||
// `EventLoop`s the user creates.
|
||||
fn did_finish_launching(&self, _notification: &NSNotification) {
|
||||
trace_scope!("applicationDidFinishLaunching:");
|
||||
self.ivars().is_launched.set(true);
|
||||
pub fn get(mtm: MainThreadMarker) -> Rc<Self> {
|
||||
GLOBAL
|
||||
.get(mtm)
|
||||
.get()
|
||||
.expect("tried to get application state before it was registered")
|
||||
.clone()
|
||||
}
|
||||
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
// NOTE: This notification will, globally, only be emitted once,
|
||||
// no matter how many `EventLoop`s the user creates.
|
||||
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
|
||||
trace_scope!("NSApplicationDidFinishLaunchingNotification");
|
||||
self.is_launched.set(true);
|
||||
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
// We need to delay setting the activation policy and activating the app
|
||||
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
||||
// menu bar is initially unresponsive on macOS 10.15.
|
||||
app.setActivationPolicy(self.ivars().activation_policy);
|
||||
if let Some(activation_policy) = self.activation_policy {
|
||||
app.setActivationPolicy(activation_policy);
|
||||
} else {
|
||||
// If no activation policy is explicitly provided, and the application
|
||||
// is bundled, do not set the activation policy at all, to allow the
|
||||
// package manifest to define the behavior via LSUIElement.
|
||||
//
|
||||
// See:
|
||||
// - https://github.com/rust-windowing/winit/issues/261
|
||||
// - https://github.com/rust-windowing/winit/issues/3958
|
||||
let is_bundled =
|
||||
unsafe { NSRunningApplication::currentApplication().bundleIdentifier().is_some() };
|
||||
if !is_bundled {
|
||||
app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
|
||||
}
|
||||
}
|
||||
|
||||
window_activation_hack(&app);
|
||||
#[allow(deprecated)]
|
||||
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
|
||||
app.activateIgnoringOtherApps(self.activate_ignoring_other_apps);
|
||||
|
||||
if self.ivars().default_menu {
|
||||
if self.default_menu {
|
||||
// The menubar initialization should be before the `NewEvents` event, to allow
|
||||
// overriding of the default menu even if it's created
|
||||
menu::initialize(&app);
|
||||
}
|
||||
|
||||
self.ivars().waker.borrow_mut().start();
|
||||
self.waker.borrow_mut().start();
|
||||
|
||||
self.set_is_running(true);
|
||||
self.dispatch_init_events();
|
||||
@@ -138,81 +143,67 @@ impl ApplicationDelegate {
|
||||
//
|
||||
// In this case we still want to consider Winit's `EventLoop` to be "running",
|
||||
// so we call `start_running()` above.
|
||||
if self.ivars().stop_on_launch.get() {
|
||||
if self.stop_on_launch.get() {
|
||||
// NOTE: the original idea had been to only stop the underlying `RunLoop`
|
||||
// for the app but that didn't work as expected (`-[NSApplication run]`
|
||||
// effectively ignored the attempt to stop the RunLoop and re-started it).
|
||||
//
|
||||
// So we return from `pump_events` by stopping the application.
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
stop_app_immediately(&app);
|
||||
}
|
||||
}
|
||||
|
||||
fn will_terminate(&self, _notification: &NSNotification) {
|
||||
trace_scope!("applicationWillTerminate:");
|
||||
// TODO: Notify every window that it will be destroyed, like done in iOS?
|
||||
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
|
||||
trace_scope!("NSApplicationWillTerminateNotification");
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
notify_windows_of_exit(&app);
|
||||
self.event_handler.terminate();
|
||||
self.internal_exit();
|
||||
}
|
||||
|
||||
pub fn get(mtm: MainThreadMarker) -> Retained<Self> {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let delegate =
|
||||
unsafe { app.delegate() }.expect("a delegate was not configured on the application");
|
||||
if delegate.is_kind_of::<Self>() {
|
||||
// SAFETY: Just checked that the delegate is an instance of `ApplicationDelegate`
|
||||
unsafe { Retained::cast(delegate) }
|
||||
} else {
|
||||
panic!("tried to get a delegate that was not the one Winit has registered")
|
||||
}
|
||||
}
|
||||
|
||||
/// Place the event handler in the application delegate for the duration
|
||||
/// Place the event handler in the application state for the duration
|
||||
/// of the given closure.
|
||||
pub fn set_event_handler<R>(
|
||||
&self,
|
||||
handler: &mut dyn ApplicationHandler,
|
||||
handler: impl ApplicationHandler,
|
||||
closure: impl FnOnce() -> R,
|
||||
) -> R {
|
||||
self.ivars().event_handler.set(handler, closure)
|
||||
self.event_handler.set(Box::new(handler), closure)
|
||||
}
|
||||
|
||||
pub fn proxy_wake_up(&self) -> Arc<AtomicBool> {
|
||||
self.ivars().proxy_wake_up.clone()
|
||||
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
|
||||
&self.event_loop_proxy
|
||||
}
|
||||
|
||||
/// If `pump_events` is called to progress the event loop then we
|
||||
/// bootstrap the event loop via `-[NSApplication run]` but will use
|
||||
/// `CFRunLoopRunInMode` for subsequent calls to `pump_events`.
|
||||
pub fn set_stop_on_launch(&self) {
|
||||
self.ivars().stop_on_launch.set(true);
|
||||
self.stop_on_launch.set(true);
|
||||
}
|
||||
|
||||
pub fn set_stop_before_wait(&self, value: bool) {
|
||||
self.ivars().stop_before_wait.set(value)
|
||||
self.stop_before_wait.set(value)
|
||||
}
|
||||
|
||||
pub fn set_stop_after_wait(&self, value: bool) {
|
||||
self.ivars().stop_after_wait.set(value)
|
||||
self.stop_after_wait.set(value)
|
||||
}
|
||||
|
||||
pub fn set_stop_on_redraw(&self, value: bool) {
|
||||
self.ivars().stop_on_redraw.set(value)
|
||||
self.stop_on_redraw.set(value)
|
||||
}
|
||||
|
||||
pub fn set_wait_timeout(&self, value: Option<Instant>) {
|
||||
self.ivars().wait_timeout.set(value)
|
||||
self.wait_timeout.set(value)
|
||||
}
|
||||
|
||||
/// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits.
|
||||
///
|
||||
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
|
||||
/// and we won't need to re-launch the app if subsequent EventLoops are run.
|
||||
pub fn internal_exit(&self) {
|
||||
self.with_handler(|app, event_loop| {
|
||||
app.exiting(event_loop);
|
||||
});
|
||||
|
||||
pub fn internal_exit(self: &Rc<Self>) {
|
||||
self.set_is_running(false);
|
||||
self.set_stop_on_redraw(false);
|
||||
self.set_stop_before_wait(false);
|
||||
@@ -221,67 +212,66 @@ impl ApplicationDelegate {
|
||||
}
|
||||
|
||||
pub fn is_launched(&self) -> bool {
|
||||
self.ivars().is_launched.get()
|
||||
self.is_launched.get()
|
||||
}
|
||||
|
||||
pub fn set_is_running(&self, value: bool) {
|
||||
self.ivars().is_running.set(value)
|
||||
self.is_running.set(value)
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.ivars().is_running.get()
|
||||
self.is_running.get()
|
||||
}
|
||||
|
||||
pub fn exit(&self) {
|
||||
self.ivars().exit.set(true)
|
||||
self.exit.set(true)
|
||||
}
|
||||
|
||||
pub fn clear_exit(&self) {
|
||||
self.ivars().exit.set(false)
|
||||
self.exit.set(false)
|
||||
}
|
||||
|
||||
pub fn exiting(&self) -> bool {
|
||||
self.ivars().exit.get()
|
||||
self.exit.get()
|
||||
}
|
||||
|
||||
pub fn set_control_flow(&self, value: ControlFlow) {
|
||||
self.ivars().control_flow.set(value)
|
||||
self.control_flow.set(value)
|
||||
}
|
||||
|
||||
pub fn control_flow(&self) -> ControlFlow {
|
||||
self.ivars().control_flow.get()
|
||||
self.control_flow.get()
|
||||
}
|
||||
|
||||
pub fn handle_redraw(&self, window_id: WindowId) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
pub fn handle_redraw(self: &Rc<Self>, window_id: WindowId) {
|
||||
// Redraw request might come out of order from the OS.
|
||||
// -> Don't go back into the event handler when our callstack originates from there
|
||||
if !self.ivars().event_handler.in_use() {
|
||||
if !self.event_handler.in_use() {
|
||||
self.with_handler(|app, event_loop| {
|
||||
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
|
||||
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
|
||||
});
|
||||
|
||||
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
|
||||
// events as a way to ensure that `pump_events` can't block an external loop
|
||||
// indefinitely
|
||||
if self.ivars().stop_on_redraw.get() {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
if self.stop_on_redraw.get() {
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
stop_app_immediately(&app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_redraw(&self, window_id: WindowId) {
|
||||
let mut pending_redraw = self.ivars().pending_redraw.borrow_mut();
|
||||
let mut pending_redraw = self.pending_redraw.borrow_mut();
|
||||
if !pending_redraw.contains(&window_id) {
|
||||
pending_redraw.push(window_id);
|
||||
}
|
||||
self.ivars().run_loop.wakeup();
|
||||
self.run_loop.wakeup();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn maybe_queue_with_handler(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop) + 'static,
|
||||
) {
|
||||
// Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.)
|
||||
@@ -290,26 +280,28 @@ impl ApplicationDelegate {
|
||||
// However, it is not documented which actions do this, and which ones are done immediately,
|
||||
// so to make sure that we don't encounter re-entrancy issues, we first check if we're
|
||||
// currently handling another event, and if we are, we queue the event instead.
|
||||
if !self.ivars().event_handler.in_use() {
|
||||
if !self.event_handler.in_use() {
|
||||
self.with_handler(callback);
|
||||
} else {
|
||||
tracing::debug!("had to queue event since another is currently being handled");
|
||||
let this = self.retain();
|
||||
self.ivars().run_loop.queue_closure(move || {
|
||||
let this = Rc::clone(self);
|
||||
self.run_loop.queue_closure(move || {
|
||||
this.with_handler(callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn with_handler(&self, callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop)) {
|
||||
let event_loop =
|
||||
ActiveEventLoop { delegate: self.retain(), mtm: MainThreadMarker::from(self) };
|
||||
self.ivars().event_handler.handle(callback, &event_loop);
|
||||
fn with_handler(
|
||||
self: &Rc<Self>,
|
||||
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop),
|
||||
) {
|
||||
let event_loop = ActiveEventLoop { app_state: Rc::clone(self), mtm: self.mtm };
|
||||
self.event_handler.handle(|app| callback(app, &event_loop));
|
||||
}
|
||||
|
||||
/// dispatch `NewEvents(Init)` + `Resumed`
|
||||
pub fn dispatch_init_events(&self) {
|
||||
pub fn dispatch_init_events(self: &Rc<Self>) {
|
||||
self.with_handler(|app, event_loop| app.new_events(event_loop, StartCause::Init));
|
||||
// NB: For consistency all platforms must call `can_create_surfaces` even though macOS
|
||||
// applications don't themselves have a formal surface destroy/create lifecycle.
|
||||
@@ -317,23 +309,22 @@ impl ApplicationDelegate {
|
||||
}
|
||||
|
||||
// Called by RunLoopObserver after finishing waiting for new events
|
||||
pub fn wakeup(&self, panic_info: Weak<PanicInfo>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
pub fn wakeup(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
|
||||
let panic_info = panic_info
|
||||
.upgrade()
|
||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
||||
|
||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
|
||||
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ivars().stop_after_wait.get() {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
if self.stop_after_wait.get() {
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
stop_app_immediately(&app);
|
||||
}
|
||||
|
||||
let start = self.ivars().start_time.get().unwrap();
|
||||
let start = self.start_time.get().unwrap();
|
||||
let cause = match self.control_flow() {
|
||||
ControlFlow::Poll => StartCause::Poll,
|
||||
ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
|
||||
@@ -350,8 +341,7 @@ impl ApplicationDelegate {
|
||||
}
|
||||
|
||||
// Called by RunLoopObserver before waiting for new events
|
||||
pub fn cleared(&self, panic_info: Weak<PanicInfo>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
pub fn cleared(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
|
||||
let panic_info = panic_info
|
||||
.upgrade()
|
||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
||||
@@ -359,18 +349,14 @@ impl ApplicationDelegate {
|
||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
|
||||
// we're about to return to the `CFRunLoop` to poll for new events?
|
||||
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
|
||||
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ivars().proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
|
||||
self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
|
||||
}
|
||||
|
||||
let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut());
|
||||
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
|
||||
for window_id in redraw {
|
||||
self.with_handler(|app, event_loop| {
|
||||
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
|
||||
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
|
||||
});
|
||||
}
|
||||
self.with_handler(|app, event_loop| {
|
||||
@@ -378,22 +364,23 @@ impl ApplicationDelegate {
|
||||
});
|
||||
|
||||
if self.exiting() {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
stop_app_immediately(&app);
|
||||
notify_windows_of_exit(&app);
|
||||
}
|
||||
|
||||
if self.ivars().stop_before_wait.get() {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
if self.stop_before_wait.get() {
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
stop_app_immediately(&app);
|
||||
}
|
||||
self.ivars().start_time.set(Some(Instant::now()));
|
||||
let wait_timeout = self.ivars().wait_timeout.get(); // configured by pump_events
|
||||
self.start_time.set(Some(Instant::now()));
|
||||
let wait_timeout = self.wait_timeout.get(); // configured by pump_events
|
||||
let app_timeout = match self.control_flow() {
|
||||
ControlFlow::Wait => None,
|
||||
ControlFlow::Poll => Some(Instant::now()),
|
||||
ControlFlow::WaitUntil(instant) => Some(instant),
|
||||
};
|
||||
self.ivars().waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout));
|
||||
self.waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,26 +390,3 @@ impl ApplicationDelegate {
|
||||
fn min_timeout(a: Option<Instant>, b: Option<Instant>) -> Option<Instant> {
|
||||
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
|
||||
}
|
||||
|
||||
/// A hack to make activation of multiple windows work when creating them before
|
||||
/// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`.
|
||||
///
|
||||
/// Alternative to this would be the user calling `window.set_visible(true)` in
|
||||
/// `StartCause::Init`.
|
||||
///
|
||||
/// If this becomes too bothersome to maintain, it can probably be removed
|
||||
/// without too much damage.
|
||||
fn window_activation_hack(app: &NSApplication) {
|
||||
// TODO: Proper ordering of the windows
|
||||
app.windows().into_iter().for_each(|window| {
|
||||
// Call `makeKeyAndOrderFront` if it was called on the window in `WinitWindow::new`
|
||||
// This way we preserve the user's desired initial visibility status
|
||||
// TODO: Also filter on the type/"level" of the window, and maybe other things?
|
||||
if window.isVisible() {
|
||||
tracing::trace!("Activating visible window");
|
||||
window.makeKeyAndOrderFront(None);
|
||||
} else {
|
||||
tracing::trace!("Skipping activating invisible window");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,33 +4,44 @@ use std::sync::OnceLock;
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{msg_send_id, sel, ClassType};
|
||||
use objc2::{available, msg_send, sel, AllocAnyThread, ClassType};
|
||||
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
|
||||
use objc2_foundation::{
|
||||
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
|
||||
NSString,
|
||||
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString,
|
||||
};
|
||||
|
||||
use super::OsError;
|
||||
use crate::cursor::{CursorImage, OnlyCursorImageSource};
|
||||
use crate::error::ExternalError;
|
||||
use crate::cursor::{CursorImage, CustomCursorProvider, CustomCursorSource};
|
||||
use crate::error::{NotSupportedError, RequestError};
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct CustomCursor(pub(crate) Retained<NSCursor>);
|
||||
|
||||
impl CustomCursorProvider for CustomCursor {
|
||||
fn is_animated(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: NSCursor is immutable and thread-safe
|
||||
// TODO(madsmtm): Put this logic in objc2-app-kit itself
|
||||
unsafe impl Send for CustomCursor {}
|
||||
unsafe impl Sync for CustomCursor {}
|
||||
|
||||
impl CustomCursor {
|
||||
pub(crate) fn new(cursor: OnlyCursorImageSource) -> Result<CustomCursor, ExternalError> {
|
||||
cursor_from_image(&cursor.0).map(Self)
|
||||
pub(crate) fn new(cursor: CustomCursorSource) -> Result<CustomCursor, RequestError> {
|
||||
let cursor = match cursor {
|
||||
CustomCursorSource::Image(cursor_image) => cursor_image,
|
||||
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
|
||||
return Err(NotSupportedError::new("unsupported cursor kind").into())
|
||||
},
|
||||
};
|
||||
|
||||
cursor_from_image(&cursor).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCursor>, ExternalError> {
|
||||
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCursor>, RequestError> {
|
||||
let width = cursor.width;
|
||||
let height = cursor.height;
|
||||
|
||||
@@ -48,7 +59,7 @@ pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCurso
|
||||
width as isize * 4,
|
||||
32,
|
||||
)
|
||||
}.ok_or_else(|| ExternalError::Os(os_error!(OsError::CreationError("parent view should be installed in a window"))))?;
|
||||
}.ok_or_else(|| os_error!("parent view should be installed in a window"))?;
|
||||
let bitmap_data = unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.rgba.len()) };
|
||||
bitmap_data.copy_from_slice(&cursor.rgba);
|
||||
|
||||
@@ -68,8 +79,8 @@ pub(crate) fn default_cursor() -> Retained<NSCursor> {
|
||||
|
||||
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
|
||||
let cls = NSCursor::class();
|
||||
if cls.responds_to(sel) {
|
||||
let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
|
||||
if unsafe { msg_send![cls, respondsToSelector: sel] } {
|
||||
let cursor: Retained<NSCursor> = unsafe { msg_send![cls, performSelector: sel] };
|
||||
Some(cursor)
|
||||
} else {
|
||||
tracing::warn!("cursor `{sel}` appears to be invalid");
|
||||
@@ -131,25 +142,21 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Retained<NSCursor> {
|
||||
// TODO: Handle PLists better
|
||||
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
|
||||
let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe {
|
||||
msg_send_id![
|
||||
msg_send![
|
||||
<NSDictionary<NSObject, NSObject>>::class(),
|
||||
dictionaryWithContentsOfFile: &*info_path,
|
||||
]
|
||||
};
|
||||
let mut x = 0.0;
|
||||
if let Some(n) = info.get(&*ns_string!("hotx")) {
|
||||
if n.is_kind_of::<NSNumber>() {
|
||||
let ptr: *const NSObject = n;
|
||||
let ptr: *const NSNumber = ptr.cast();
|
||||
x = unsafe { &*ptr }.as_cgfloat()
|
||||
if let Some(n) = info.objectForKey(ns_string!("hotx")) {
|
||||
if let Ok(n) = n.downcast::<NSNumber>() {
|
||||
x = n.as_cgfloat();
|
||||
}
|
||||
}
|
||||
let mut y = 0.0;
|
||||
if let Some(n) = info.get(&*ns_string!("hotx")) {
|
||||
if n.is_kind_of::<NSNumber>() {
|
||||
let ptr: *const NSObject = n;
|
||||
let ptr: *const NSNumber = ptr.cast();
|
||||
y = unsafe { &*ptr }.as_cgfloat()
|
||||
if let Some(n) = info.objectForKey(ns_string!("hoty")) {
|
||||
if let Ok(n) = n.downcast::<NSNumber>() {
|
||||
y = n.as_cgfloat();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +196,7 @@ pub(crate) fn invisible_cursor() -> Retained<NSCursor> {
|
||||
CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone()
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
|
||||
match icon {
|
||||
CursorIcon::Default => default_cursor(),
|
||||
@@ -209,7 +217,9 @@ pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
|
||||
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
|
||||
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
|
||||
CursorIcon::Help => _helpCursor(),
|
||||
CursorIcon::ZoomIn if available!(macos = 15.0) => unsafe { NSCursor::zoomInCursor() },
|
||||
CursorIcon::ZoomIn => _zoomInCursor(),
|
||||
CursorIcon::ZoomOut if available!(macos = 15.0) => unsafe { NSCursor::zoomOutCursor() },
|
||||
CursorIcon::ZoomOut => _zoomOutCursor(),
|
||||
CursorIcon::NeResize => _windowResizeNorthEastCursor(),
|
||||
CursorIcon::NwResize => _windowResizeNorthWestCursor(),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::ffi::c_void;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use core_foundation::base::CFRelease;
|
||||
use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
|
||||
use dispatch2::run_on_main;
|
||||
use objc2::rc::Retained;
|
||||
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
|
||||
use objc2_foundation::{run_on_main, NSPoint};
|
||||
use objc2_core_foundation::{CFData, CFRetained};
|
||||
use objc2_foundation::NSPoint;
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use super::ffi;
|
||||
@@ -14,37 +14,29 @@ use crate::keyboard::{
|
||||
PhysicalKey,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct KeyEventExtra {
|
||||
pub text_with_all_modifiers: Option<SmolStr>,
|
||||
pub key_without_modifiers: Key,
|
||||
}
|
||||
|
||||
/// Ignores ALL modifiers.
|
||||
pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
let mut string = [0; 16];
|
||||
let input_source;
|
||||
let layout;
|
||||
unsafe {
|
||||
input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource();
|
||||
if input_source.is_null() {
|
||||
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
let layout_data =
|
||||
ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData);
|
||||
if layout_data.is_null() {
|
||||
CFRelease(input_source as *mut c_void);
|
||||
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout;
|
||||
}
|
||||
let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else {
|
||||
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
};
|
||||
let input_source = unsafe { CFRetained::from_raw(ptr) };
|
||||
|
||||
let layout_data = unsafe {
|
||||
ffi::TISGetInputSourceProperty(&input_source, ffi::kTISPropertyUnicodeKeyLayoutData)
|
||||
};
|
||||
let Some(layout_data) = (unsafe { layout_data.cast::<CFData>().as_ref() }) else {
|
||||
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
};
|
||||
|
||||
let layout = layout_data.byte_ptr().cast();
|
||||
let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() });
|
||||
|
||||
let mut result_len = 0;
|
||||
let mut dead_keys = 0;
|
||||
let modifiers = 0;
|
||||
let mut string = [0; 16];
|
||||
let translate_result = unsafe {
|
||||
ffi::UCKeyTranslate(
|
||||
layout,
|
||||
@@ -59,9 +51,6 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
string.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
CFRelease(input_source as *mut c_void);
|
||||
}
|
||||
if translate_result != 0 {
|
||||
tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result);
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
@@ -92,17 +81,12 @@ fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
|
||||
/// Create `KeyEvent` for the given `NSEvent`.
|
||||
///
|
||||
/// This function shouldn't be called when the IME input is in process.
|
||||
pub(crate) fn create_key_event(
|
||||
ns_event: &NSEvent,
|
||||
is_press: bool,
|
||||
is_repeat: bool,
|
||||
key_override: Option<PhysicalKey>,
|
||||
) -> KeyEvent {
|
||||
pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bool) -> KeyEvent {
|
||||
use ElementState::{Pressed, Released};
|
||||
let state = if is_press { Pressed } else { Released };
|
||||
|
||||
let scancode = unsafe { ns_event.keyCode() };
|
||||
let mut physical_key = key_override.unwrap_or_else(|| scancode_to_physicalkey(scancode as u32));
|
||||
let mut physical_key = scancode_to_physicalkey(scancode as u32);
|
||||
|
||||
// NOTE: The logical key should heed both SHIFT and ALT if possible.
|
||||
// For instance:
|
||||
@@ -111,20 +95,15 @@ pub(crate) fn create_key_event(
|
||||
// * Pressing CTRL SHIFT A: logical key should also be "A"
|
||||
// This is not easy to tease out of `NSEvent`, but we do our best.
|
||||
|
||||
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
|
||||
let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
|
||||
let text_with_all_modifiers = if characters.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let characters =
|
||||
unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
|
||||
if characters.is_empty() {
|
||||
None
|
||||
} else {
|
||||
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
|
||||
// The key may be one of the funky function keys
|
||||
physical_key = extra_function_key_to_code(scancode, &characters);
|
||||
}
|
||||
Some(SmolStr::new(characters))
|
||||
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
|
||||
// The key may be one of the funky function keys
|
||||
physical_key = extra_function_key_to_code(scancode, &characters);
|
||||
}
|
||||
Some(SmolStr::new(characters))
|
||||
};
|
||||
|
||||
let key_from_code = code_to_key(physical_key, scancode);
|
||||
@@ -133,8 +112,8 @@ pub(crate) fn create_key_event(
|
||||
let key_without_modifiers = get_modifierless_char(scancode);
|
||||
|
||||
let modifiers = unsafe { ns_event.modifierFlags() };
|
||||
let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl);
|
||||
let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand);
|
||||
let has_ctrl = modifiers.contains(NSEventModifierFlags::Control);
|
||||
let has_cmd = modifiers.contains(NSEventModifierFlags::Command);
|
||||
|
||||
let logical_key = match text_with_all_modifiers.as_ref() {
|
||||
// Only checking for ctrl and cmd here, not checking for alt because we DO want to
|
||||
@@ -172,7 +151,8 @@ pub(crate) fn create_key_event(
|
||||
repeat: is_repeat,
|
||||
state,
|
||||
text,
|
||||
platform_specific: KeyEventExtra { text_with_all_modifiers, key_without_modifiers },
|
||||
text_with_all_modifiers,
|
||||
key_without_modifiers,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,28 +162,34 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
|
||||
PhysicalKey::Unidentified(code) => return Key::Unidentified(code.into()),
|
||||
};
|
||||
|
||||
// Roughly same handling as Firefox and Chromium:
|
||||
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMKeyName.h
|
||||
// https://chromium.googlesource.com/chromium/src.git/+/010a75a426c4a2292955a52f480e9251cacf750e/ui/events/keycodes/keyboard_code_conversion_mac.mm#100
|
||||
Key::Named(match code {
|
||||
KeyCode::Enter => NamedKey::Enter,
|
||||
KeyCode::Tab => NamedKey::Tab,
|
||||
KeyCode::Space => NamedKey::Space,
|
||||
KeyCode::Space => return Key::Character(" ".into()),
|
||||
KeyCode::Backspace => NamedKey::Backspace,
|
||||
KeyCode::Escape => NamedKey::Escape,
|
||||
KeyCode::SuperRight => NamedKey::Super,
|
||||
KeyCode::SuperLeft => NamedKey::Super,
|
||||
KeyCode::MetaRight => NamedKey::Meta,
|
||||
KeyCode::MetaLeft => NamedKey::Meta,
|
||||
KeyCode::ShiftLeft => NamedKey::Shift,
|
||||
KeyCode::AltLeft => NamedKey::Alt,
|
||||
KeyCode::ControlLeft => NamedKey::Control,
|
||||
KeyCode::ShiftRight => NamedKey::Shift,
|
||||
KeyCode::AltRight => NamedKey::Alt,
|
||||
KeyCode::ControlRight => NamedKey::Control,
|
||||
KeyCode::CapsLock => NamedKey::CapsLock,
|
||||
|
||||
KeyCode::NumLock => NamedKey::NumLock,
|
||||
KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp,
|
||||
KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown,
|
||||
KeyCode::AudioVolumeMute => NamedKey::AudioVolumeMute,
|
||||
|
||||
// Other numpad keys all generate text on macOS (if I understand correctly)
|
||||
KeyCode::NumpadEnter => NamedKey::Enter,
|
||||
|
||||
KeyCode::Fn => NamedKey::Fn,
|
||||
KeyCode::F1 => NamedKey::F1,
|
||||
KeyCode::F2 => NamedKey::F2,
|
||||
KeyCode::F3 => NamedKey::F3,
|
||||
@@ -224,17 +210,27 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
|
||||
KeyCode::F18 => NamedKey::F18,
|
||||
KeyCode::F19 => NamedKey::F19,
|
||||
KeyCode::F20 => NamedKey::F20,
|
||||
KeyCode::F21 => NamedKey::F21,
|
||||
KeyCode::F22 => NamedKey::F22,
|
||||
KeyCode::F23 => NamedKey::F23,
|
||||
KeyCode::F24 => NamedKey::F24,
|
||||
|
||||
KeyCode::Insert => NamedKey::Insert,
|
||||
KeyCode::Home => NamedKey::Home,
|
||||
KeyCode::PageUp => NamedKey::PageUp,
|
||||
KeyCode::Delete => NamedKey::Delete,
|
||||
KeyCode::End => NamedKey::End,
|
||||
KeyCode::Help => NamedKey::Help,
|
||||
KeyCode::PageDown => NamedKey::PageDown,
|
||||
KeyCode::ArrowLeft => NamedKey::ArrowLeft,
|
||||
KeyCode::ArrowRight => NamedKey::ArrowRight,
|
||||
KeyCode::ArrowDown => NamedKey::ArrowDown,
|
||||
KeyCode::ArrowUp => NamedKey::ArrowUp,
|
||||
KeyCode::ContextMenu => NamedKey::ContextMenu,
|
||||
|
||||
KeyCode::Lang2 => NamedKey::Eisu,
|
||||
KeyCode::Lang1 => NamedKey::KanjiMode,
|
||||
|
||||
_ => return Key::Unidentified(NativeKey::MacOS(scancode)),
|
||||
})
|
||||
}
|
||||
@@ -246,8 +242,8 @@ pub fn code_to_location(key: PhysicalKey) -> KeyLocation {
|
||||
};
|
||||
|
||||
match code {
|
||||
KeyCode::SuperRight => KeyLocation::Right,
|
||||
KeyCode::SuperLeft => KeyLocation::Left,
|
||||
KeyCode::MetaRight => KeyLocation::Right,
|
||||
KeyCode::MetaLeft => KeyLocation::Left,
|
||||
KeyCode::ShiftLeft => KeyLocation::Left,
|
||||
KeyCode::AltLeft => KeyLocation::Left,
|
||||
KeyCode::ControlLeft => KeyLocation::Left,
|
||||
@@ -318,28 +314,21 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
|
||||
let mut state = ModifiersState::empty();
|
||||
let mut pressed_mods = ModifiersKeys::empty();
|
||||
|
||||
state
|
||||
.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSEventModifierFlagShift));
|
||||
state.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::Shift));
|
||||
pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK));
|
||||
|
||||
state.set(
|
||||
ModifiersState::CONTROL,
|
||||
flags.contains(NSEventModifierFlags::NSEventModifierFlagControl),
|
||||
);
|
||||
state.set(ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::Control));
|
||||
pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK));
|
||||
|
||||
state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSEventModifierFlagOption));
|
||||
state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::Option));
|
||||
pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK));
|
||||
|
||||
state.set(
|
||||
ModifiersState::SUPER,
|
||||
flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand),
|
||||
);
|
||||
pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK));
|
||||
state.set(ModifiersState::META, flags.contains(NSEventModifierFlags::Command));
|
||||
pressed_mods.set(ModifiersKeys::LMETA, flags.contains(NX_DEVICELCMDKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RMETA, flags.contains(NX_DEVICERCMDKEYMASK));
|
||||
|
||||
Modifiers { state, pressed_mods }
|
||||
}
|
||||
@@ -377,6 +366,7 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
|
||||
KeyCode::KeyX => Some(0x07),
|
||||
KeyCode::KeyC => Some(0x08),
|
||||
KeyCode::KeyV => Some(0x09),
|
||||
KeyCode::IntlBackslash => Some(0x0a),
|
||||
KeyCode::KeyB => Some(0x0b),
|
||||
KeyCode::KeyQ => Some(0x0c),
|
||||
KeyCode::KeyW => Some(0x0d),
|
||||
@@ -419,21 +409,24 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
|
||||
KeyCode::Backquote => Some(0x32),
|
||||
KeyCode::Backspace => Some(0x33),
|
||||
KeyCode::Escape => Some(0x35),
|
||||
KeyCode::SuperRight => Some(0x36),
|
||||
KeyCode::SuperLeft => Some(0x37),
|
||||
KeyCode::MetaRight => Some(0x36),
|
||||
KeyCode::MetaLeft => Some(0x37),
|
||||
KeyCode::ShiftLeft => Some(0x38),
|
||||
KeyCode::CapsLock => Some(0x39),
|
||||
KeyCode::AltLeft => Some(0x3a),
|
||||
KeyCode::ControlLeft => Some(0x3b),
|
||||
KeyCode::ShiftRight => Some(0x3c),
|
||||
KeyCode::AltRight => Some(0x3d),
|
||||
KeyCode::ControlRight => Some(0x3e),
|
||||
KeyCode::Fn => Some(0x3f),
|
||||
KeyCode::F17 => Some(0x40),
|
||||
KeyCode::NumpadDecimal => Some(0x41),
|
||||
KeyCode::NumpadMultiply => Some(0x43),
|
||||
KeyCode::NumpadAdd => Some(0x45),
|
||||
KeyCode::NumLock => Some(0x47),
|
||||
KeyCode::AudioVolumeUp => Some(0x49),
|
||||
KeyCode::AudioVolumeDown => Some(0x4a),
|
||||
KeyCode::AudioVolumeUp => Some(0x48),
|
||||
KeyCode::AudioVolumeDown => Some(0x49),
|
||||
KeyCode::AudioVolumeMute => Some(0x4a),
|
||||
KeyCode::NumpadDivide => Some(0x4b),
|
||||
KeyCode::NumpadEnter => Some(0x4c),
|
||||
KeyCode::NumpadSubtract => Some(0x4e),
|
||||
@@ -452,17 +445,22 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
|
||||
KeyCode::Numpad8 => Some(0x5b),
|
||||
KeyCode::Numpad9 => Some(0x5c),
|
||||
KeyCode::IntlYen => Some(0x5d),
|
||||
KeyCode::IntlRo => Some(0x5e),
|
||||
KeyCode::NumpadComma => Some(0x5f),
|
||||
KeyCode::F5 => Some(0x60),
|
||||
KeyCode::F6 => Some(0x61),
|
||||
KeyCode::F7 => Some(0x62),
|
||||
KeyCode::F3 => Some(0x63),
|
||||
KeyCode::F8 => Some(0x64),
|
||||
KeyCode::F9 => Some(0x65),
|
||||
KeyCode::Lang2 => Some(0x66),
|
||||
KeyCode::F11 => Some(0x67),
|
||||
KeyCode::Lang1 => Some(0x68),
|
||||
KeyCode::F13 => Some(0x69),
|
||||
KeyCode::F16 => Some(0x6a),
|
||||
KeyCode::F14 => Some(0x6b),
|
||||
KeyCode::F10 => Some(0x6d),
|
||||
KeyCode::ContextMenu => Some(0x6e),
|
||||
KeyCode::F12 => Some(0x6f),
|
||||
KeyCode::F15 => Some(0x71),
|
||||
KeyCode::Insert => Some(0x72),
|
||||
@@ -478,11 +476,26 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
|
||||
KeyCode::ArrowRight => Some(0x7c),
|
||||
KeyCode::ArrowDown => Some(0x7d),
|
||||
KeyCode::ArrowUp => Some(0x7e),
|
||||
KeyCode::Power => Some(0x7f),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
|
||||
// Follows what Chromium and Firefox do:
|
||||
// https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc
|
||||
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
|
||||
//
|
||||
// See also:
|
||||
// Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
|
||||
//
|
||||
// Also see https://developer.apple.com/documentation/appkit/function-key-unicode-values:
|
||||
//
|
||||
// > the system handles some function keys at a lower level and your app never sees them.
|
||||
// > Examples include the Volume Up key, Volume Down key, Volume Mute key, Eject key, and
|
||||
// > Function key found on many Macs.
|
||||
//
|
||||
// So the handling of some of these is mostly for show.
|
||||
PhysicalKey::Code(match scancode {
|
||||
0x00 => KeyCode::KeyA,
|
||||
0x01 => KeyCode::KeyS,
|
||||
@@ -494,7 +507,11 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
|
||||
0x07 => KeyCode::KeyX,
|
||||
0x08 => KeyCode::KeyC,
|
||||
0x09 => KeyCode::KeyV,
|
||||
// 0x0a => World 1,
|
||||
// This key is typically located near LeftShift key, roughly the same location as backquote
|
||||
// (`) on Windows' US layout.
|
||||
//
|
||||
// The keycap varies on international keyboards.
|
||||
0x0a => KeyCode::IntlBackslash,
|
||||
0x0b => KeyCode::KeyB,
|
||||
0x0c => KeyCode::KeyQ,
|
||||
0x0d => KeyCode::KeyW,
|
||||
@@ -536,10 +553,10 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
|
||||
0x31 => KeyCode::Space,
|
||||
0x32 => KeyCode::Backquote,
|
||||
0x33 => KeyCode::Backspace,
|
||||
// 0x34 => unknown,
|
||||
// 0x34 => unknown, // kVK_Powerbook_KeypadEnter
|
||||
0x35 => KeyCode::Escape,
|
||||
0x36 => KeyCode::SuperRight,
|
||||
0x37 => KeyCode::SuperLeft,
|
||||
0x36 => KeyCode::MetaRight,
|
||||
0x37 => KeyCode::MetaLeft,
|
||||
0x38 => KeyCode::ShiftLeft,
|
||||
0x39 => KeyCode::CapsLock,
|
||||
0x3a => KeyCode::AltLeft,
|
||||
@@ -555,15 +572,10 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
|
||||
// 0x44 => unknown,
|
||||
0x45 => KeyCode::NumpadAdd,
|
||||
// 0x46 => unknown,
|
||||
0x47 => KeyCode::NumLock,
|
||||
// 0x48 => KeyCode::NumpadClear,
|
||||
|
||||
// TODO: (Artur) for me, kVK_VolumeUp is 0x48
|
||||
// macOS 10.11
|
||||
// /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/
|
||||
// Versions/A/Headers/Events.h
|
||||
0x49 => KeyCode::AudioVolumeUp,
|
||||
0x4a => KeyCode::AudioVolumeDown,
|
||||
0x47 => KeyCode::NumLock, // kVK_ANSI_KeypadClear
|
||||
0x48 => KeyCode::AudioVolumeUp,
|
||||
0x49 => KeyCode::AudioVolumeDown,
|
||||
0x4a => KeyCode::AudioVolumeMute,
|
||||
0x4b => KeyCode::NumpadDivide,
|
||||
0x4c => KeyCode::NumpadEnter,
|
||||
// 0x4d => unknown,
|
||||
@@ -583,23 +595,23 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
|
||||
0x5b => KeyCode::Numpad8,
|
||||
0x5c => KeyCode::Numpad9,
|
||||
0x5d => KeyCode::IntlYen,
|
||||
// 0x5e => JIS Ro,
|
||||
// 0x5f => unknown,
|
||||
0x5e => KeyCode::IntlRo,
|
||||
0x5f => KeyCode::NumpadComma,
|
||||
0x60 => KeyCode::F5,
|
||||
0x61 => KeyCode::F6,
|
||||
0x62 => KeyCode::F7,
|
||||
0x63 => KeyCode::F3,
|
||||
0x64 => KeyCode::F8,
|
||||
0x65 => KeyCode::F9,
|
||||
// 0x66 => JIS Eisuu (macOS),
|
||||
0x66 => KeyCode::Lang2,
|
||||
0x67 => KeyCode::F11,
|
||||
// 0x68 => JIS Kanna (macOS),
|
||||
0x68 => KeyCode::Lang1,
|
||||
0x69 => KeyCode::F13,
|
||||
0x6a => KeyCode::F16,
|
||||
0x6b => KeyCode::F14,
|
||||
// 0x6c => unknown,
|
||||
0x6d => KeyCode::F10,
|
||||
// 0x6e => unknown,
|
||||
0x6e => KeyCode::ContextMenu,
|
||||
0x6f => KeyCode::F12,
|
||||
// 0x70 => unknown,
|
||||
0x71 => KeyCode::F15,
|
||||
@@ -616,11 +628,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
|
||||
0x7c => KeyCode::ArrowRight,
|
||||
0x7d => KeyCode::ArrowDown,
|
||||
0x7e => KeyCode::ArrowUp,
|
||||
// 0x7f => unknown,
|
||||
|
||||
// 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as
|
||||
// backquote (`) on Windows' US layout.
|
||||
0xa => KeyCode::Backquote,
|
||||
0x7f => KeyCode::Power, // On 10.7 and 10.8 only
|
||||
_ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,49 +1,51 @@
|
||||
use std::any::Any;
|
||||
use std::cell::Cell;
|
||||
use std::os::raw::c_void;
|
||||
use std::fmt;
|
||||
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
|
||||
use std::ptr;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use core_foundation::base::{CFIndex, CFRelease};
|
||||
use core_foundation::runloop::{
|
||||
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
|
||||
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
use objc2::rc::{autoreleasepool, Retained};
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2::{msg_send_id, sel, ClassType};
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
|
||||
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
|
||||
use objc2::{available, MainThreadMarker};
|
||||
use objc2_app_kit::{
|
||||
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
|
||||
NSApplicationWillTerminateNotification, NSWindow,
|
||||
};
|
||||
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
|
||||
use rwh_06::HasDisplayHandle;
|
||||
|
||||
use super::app::WinitApplication;
|
||||
use super::app_state::ApplicationDelegate;
|
||||
use super::super::notification_center::create_observer;
|
||||
use super::app::override_send_event;
|
||||
use super::app_state::AppState;
|
||||
use super::cursor::CustomCursor;
|
||||
use super::event::dummy_event;
|
||||
use super::monitor;
|
||||
use super::observer::setup_control_flow_observers;
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::error::{EventLoopError, ExternalError};
|
||||
use crate::error::{EventLoopError, RequestError};
|
||||
use crate::event_loop::{
|
||||
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
|
||||
EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as RootOwnedDisplayHandle,
|
||||
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
||||
};
|
||||
use crate::monitor::MonitorHandle as RootMonitorHandle;
|
||||
use crate::monitor::MonitorHandle as CoreMonitorHandle;
|
||||
use crate::platform::macos::ActivationPolicy;
|
||||
use crate::platform::pump_events::PumpStatus;
|
||||
use crate::platform_impl::Window;
|
||||
use crate::window::{
|
||||
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, Window as RootWindow,
|
||||
};
|
||||
use crate::window::{CustomCursor as CoreCustomCursor, CustomCursorSource, Theme};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PanicInfo {
|
||||
inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for PanicInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PanicInfo").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
// WARNING:
|
||||
// As long as this struct is used through its `impl`, it is UnwindSafe.
|
||||
// (If `get_mut` is called on `inner`, unwind safety may get broken.)
|
||||
@@ -71,15 +73,11 @@ impl PanicInfo {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActiveEventLoop {
|
||||
pub(super) delegate: Retained<ApplicationDelegate>,
|
||||
pub(super) app_state: Rc<AppState>,
|
||||
pub(super) mtm: MainThreadMarker,
|
||||
}
|
||||
|
||||
impl ActiveEventLoop {
|
||||
pub(super) fn app_delegate(&self) -> &ApplicationDelegate {
|
||||
&self.delegate
|
||||
}
|
||||
|
||||
pub(crate) fn hide_application(&self) {
|
||||
NSApplication::sharedApplication(self.mtm).hide(None)
|
||||
}
|
||||
@@ -98,33 +96,35 @@ impl ActiveEventLoop {
|
||||
}
|
||||
|
||||
impl RootActiveEventLoop for ActiveEventLoop {
|
||||
fn create_proxy(&self) -> RootEventLoopProxy {
|
||||
let event_loop_proxy = EventLoopProxy::new(self.delegate.proxy_wake_up());
|
||||
RootEventLoopProxy { event_loop_proxy }
|
||||
fn create_proxy(&self) -> CoreEventLoopProxy {
|
||||
CoreEventLoopProxy::new(self.app_state.event_loop_proxy().clone())
|
||||
}
|
||||
|
||||
fn create_window(
|
||||
&self,
|
||||
window_attributes: crate::window::WindowAttributes,
|
||||
) -> Result<crate::window::Window, crate::error::OsError> {
|
||||
let window = Window::new(self, window_attributes)?;
|
||||
Ok(RootWindow { window })
|
||||
) -> Result<Box<dyn crate::window::Window>, RequestError> {
|
||||
Ok(Box::new(Window::new(self, window_attributes)?))
|
||||
}
|
||||
|
||||
fn create_custom_cursor(
|
||||
&self,
|
||||
source: CustomCursorSource,
|
||||
) -> Result<RootCustomCursor, ExternalError> {
|
||||
Ok(RootCustomCursor { inner: CustomCursor::new(source.inner)? })
|
||||
) -> Result<CoreCustomCursor, RequestError> {
|
||||
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(source)?)))
|
||||
}
|
||||
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
|
||||
Box::new(monitor::available_monitors().into_iter().map(|inner| RootMonitorHandle { inner }))
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
|
||||
Box::new(
|
||||
monitor::available_monitors()
|
||||
.into_iter()
|
||||
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
|
||||
)
|
||||
}
|
||||
|
||||
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
|
||||
let monitor = monitor::primary_monitor();
|
||||
Some(RootMonitorHandle { inner: monitor })
|
||||
Some(CoreMonitorHandle(Arc::new(monitor)))
|
||||
}
|
||||
|
||||
fn listen_device_events(&self, _allowed: DeviceEvents) {}
|
||||
@@ -132,7 +132,8 @@ impl RootActiveEventLoop for ActiveEventLoop {
|
||||
fn system_theme(&self) -> Option<Theme> {
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
|
||||
if app.respondsToSelector(sel!(effectiveAppearance)) {
|
||||
// Dark appearance was introduced in macOS 10.14
|
||||
if available!(macos = 10.14) {
|
||||
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
|
||||
} else {
|
||||
Some(Theme::Light)
|
||||
@@ -140,36 +141,30 @@ impl RootActiveEventLoop for ActiveEventLoop {
|
||||
}
|
||||
|
||||
fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||
self.delegate.set_control_flow(control_flow)
|
||||
self.app_state.set_control_flow(control_flow)
|
||||
}
|
||||
|
||||
fn control_flow(&self) -> ControlFlow {
|
||||
self.delegate.control_flow()
|
||||
self.app_state.control_flow()
|
||||
}
|
||||
|
||||
fn exit(&self) {
|
||||
self.delegate.exit()
|
||||
self.app_state.exit()
|
||||
}
|
||||
|
||||
fn exiting(&self) -> bool {
|
||||
self.delegate.exiting()
|
||||
self.app_state.exiting()
|
||||
}
|
||||
|
||||
fn owned_display_handle(&self) -> RootOwnedDisplayHandle {
|
||||
RootOwnedDisplayHandle { platform: OwnedDisplayHandle }
|
||||
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
|
||||
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
|
||||
@@ -177,36 +172,36 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoop {
|
||||
/// Store a reference to the application for convenience.
|
||||
///
|
||||
/// We intentionally don't store `WinitApplication` since we want to have
|
||||
/// the possibility of swapping that out at some point.
|
||||
app: Retained<NSApplication>,
|
||||
/// The application delegate that we've registered.
|
||||
///
|
||||
/// The delegate is only weakly referenced by NSApplication, so we must
|
||||
/// keep it around here as well.
|
||||
delegate: Retained<ApplicationDelegate>,
|
||||
app_state: Rc<AppState>,
|
||||
|
||||
window_target: ActiveEventLoop,
|
||||
panic_info: Rc<PanicInfo>,
|
||||
|
||||
// Since macOS 10.11, we no longer need to remove the observers before they are deallocated;
|
||||
// the system instead cleans it up next time it would have posted a notification to it.
|
||||
//
|
||||
// Though we do still need to keep the observers around to prevent them from being deallocated.
|
||||
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PlatformSpecificEventLoopAttributes {
|
||||
pub(crate) activation_policy: ActivationPolicy,
|
||||
pub(crate) activation_policy: Option<ActivationPolicy>,
|
||||
pub(crate) default_menu: bool,
|
||||
pub(crate) activate_ignoring_other_apps: bool,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificEventLoopAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
activation_policy: Default::default(), // Regular
|
||||
default_menu: true,
|
||||
activate_ignoring_other_apps: true,
|
||||
}
|
||||
Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,41 +212,62 @@ impl EventLoop {
|
||||
let mtm = MainThreadMarker::new()
|
||||
.expect("on macOS, `EventLoop` must be created on the main thread!");
|
||||
|
||||
let app: Retained<NSApplication> =
|
||||
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
|
||||
|
||||
if !app.is_kind_of::<WinitApplication>() {
|
||||
panic!(
|
||||
"`winit` requires control over the principal class. You must create the event \
|
||||
loop before other parts of your application initialize NSApplication"
|
||||
);
|
||||
}
|
||||
|
||||
let activation_policy = match attributes.activation_policy {
|
||||
ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular,
|
||||
ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory,
|
||||
ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited,
|
||||
None => None,
|
||||
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
|
||||
Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory),
|
||||
Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited),
|
||||
};
|
||||
|
||||
let delegate = ApplicationDelegate::new(
|
||||
let app_state = AppState::setup_global(
|
||||
mtm,
|
||||
activation_policy,
|
||||
attributes.default_menu,
|
||||
attributes.activate_ignoring_other_apps,
|
||||
);
|
||||
|
||||
autoreleasepool(|_| {
|
||||
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
||||
});
|
||||
// Initialize the application (if it has not already been).
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
|
||||
// Override `sendEvent:` on the application to forward to our application state.
|
||||
override_send_event(&app);
|
||||
|
||||
let center = unsafe { NSNotificationCenter::defaultCenter() };
|
||||
|
||||
let weak_app_state = Rc::downgrade(&app_state);
|
||||
let _did_finish_launching_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationDidFinishLaunching:`
|
||||
unsafe { NSApplicationDidFinishLaunchingNotification },
|
||||
move |notification| {
|
||||
if let Some(app_state) = weak_app_state.upgrade() {
|
||||
app_state.did_finish_launching(notification);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let weak_app_state = Rc::downgrade(&app_state);
|
||||
let _will_terminate_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationWillTerminate:`
|
||||
unsafe { NSApplicationWillTerminateNotification },
|
||||
move |notification| {
|
||||
if let Some(app_state) = weak_app_state.upgrade() {
|
||||
app_state.will_terminate(notification);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let panic_info: Rc<PanicInfo> = Default::default();
|
||||
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
|
||||
|
||||
Ok(EventLoop {
|
||||
app,
|
||||
delegate: delegate.clone(),
|
||||
window_target: ActiveEventLoop { delegate, mtm },
|
||||
app_state: app_state.clone(),
|
||||
window_target: ActiveEventLoop { app_state, mtm },
|
||||
panic_info,
|
||||
_did_finish_launching_observer,
|
||||
_will_terminate_observer,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -269,25 +285,25 @@ impl EventLoop {
|
||||
// redundant wake ups.
|
||||
pub fn run_app_on_demand<A: ApplicationHandler>(
|
||||
&mut self,
|
||||
mut app: A,
|
||||
app: A,
|
||||
) -> Result<(), EventLoopError> {
|
||||
self.delegate.clear_exit();
|
||||
self.delegate.set_event_handler(&mut app, || {
|
||||
self.app_state.clear_exit();
|
||||
self.app_state.set_event_handler(app, || {
|
||||
autoreleasepool(|_| {
|
||||
// clear / normalize pump_events state
|
||||
self.delegate.set_wait_timeout(None);
|
||||
self.delegate.set_stop_before_wait(false);
|
||||
self.delegate.set_stop_after_wait(false);
|
||||
self.delegate.set_stop_on_redraw(false);
|
||||
self.app_state.set_wait_timeout(None);
|
||||
self.app_state.set_stop_before_wait(false);
|
||||
self.app_state.set_stop_after_wait(false);
|
||||
self.app_state.set_stop_on_redraw(false);
|
||||
|
||||
if self.delegate.is_launched() {
|
||||
debug_assert!(!self.delegate.is_running());
|
||||
self.delegate.set_is_running(true);
|
||||
self.delegate.dispatch_init_events();
|
||||
if self.app_state.is_launched() {
|
||||
debug_assert!(!self.app_state.is_running());
|
||||
self.app_state.set_is_running(true);
|
||||
self.app_state.dispatch_init_events();
|
||||
}
|
||||
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
|
||||
self.app.run();
|
||||
|
||||
// While the app is running it's possible that we catch a panic
|
||||
// to avoid unwinding across an objective-c ffi boundary, which
|
||||
@@ -298,7 +314,7 @@ impl EventLoop {
|
||||
resume_unwind(panic);
|
||||
}
|
||||
|
||||
self.delegate.internal_exit()
|
||||
self.app_state.internal_exit()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -308,51 +324,49 @@ impl EventLoop {
|
||||
pub fn pump_app_events<A: ApplicationHandler>(
|
||||
&mut self,
|
||||
timeout: Option<Duration>,
|
||||
mut app: A,
|
||||
app: A,
|
||||
) -> PumpStatus {
|
||||
self.delegate.set_event_handler(&mut app, || {
|
||||
self.app_state.set_event_handler(app, || {
|
||||
autoreleasepool(|_| {
|
||||
// As a special case, if the application hasn't been launched yet then we at least
|
||||
// run the loop until it has fully launched.
|
||||
if !self.delegate.is_launched() {
|
||||
debug_assert!(!self.delegate.is_running());
|
||||
if !self.app_state.is_launched() {
|
||||
debug_assert!(!self.app_state.is_running());
|
||||
|
||||
self.delegate.set_stop_on_launch();
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
self.app_state.set_stop_on_launch();
|
||||
self.app.run();
|
||||
|
||||
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
|
||||
// has launched
|
||||
} else if !self.delegate.is_running() {
|
||||
} else if !self.app_state.is_running() {
|
||||
// Even though the application may have been launched, it's possible we aren't
|
||||
// running if the `EventLoop` was run before and has since
|
||||
// exited. This indicates that we just starting to re-run
|
||||
// the same `EventLoop` again.
|
||||
self.delegate.set_is_running(true);
|
||||
self.delegate.dispatch_init_events();
|
||||
self.app_state.set_is_running(true);
|
||||
self.app_state.dispatch_init_events();
|
||||
} else {
|
||||
// Only run for as long as the given `Duration` allows so we don't block the
|
||||
// external loop.
|
||||
match timeout {
|
||||
Some(Duration::ZERO) => {
|
||||
self.delegate.set_wait_timeout(None);
|
||||
self.delegate.set_stop_before_wait(true);
|
||||
self.app_state.set_wait_timeout(None);
|
||||
self.app_state.set_stop_before_wait(true);
|
||||
},
|
||||
Some(duration) => {
|
||||
self.delegate.set_stop_before_wait(false);
|
||||
self.app_state.set_stop_before_wait(false);
|
||||
let timeout = Instant::now() + duration;
|
||||
self.delegate.set_wait_timeout(Some(timeout));
|
||||
self.delegate.set_stop_after_wait(true);
|
||||
self.app_state.set_wait_timeout(Some(timeout));
|
||||
self.app_state.set_stop_after_wait(true);
|
||||
},
|
||||
None => {
|
||||
self.delegate.set_wait_timeout(None);
|
||||
self.delegate.set_stop_before_wait(false);
|
||||
self.delegate.set_stop_after_wait(true);
|
||||
self.app_state.set_wait_timeout(None);
|
||||
self.app_state.set_stop_before_wait(false);
|
||||
self.app_state.set_stop_after_wait(true);
|
||||
},
|
||||
}
|
||||
self.delegate.set_stop_on_redraw(true);
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
self.app_state.set_stop_on_redraw(true);
|
||||
self.app.run();
|
||||
}
|
||||
|
||||
// While the app is running it's possible that we catch a panic
|
||||
@@ -364,8 +378,8 @@ impl EventLoop {
|
||||
resume_unwind(panic);
|
||||
}
|
||||
|
||||
if self.delegate.exiting() {
|
||||
self.delegate.internal_exit();
|
||||
if self.app_state.exiting() {
|
||||
self.app_state.internal_exit();
|
||||
PumpStatus::Exit(0)
|
||||
} else {
|
||||
PumpStatus::Continue
|
||||
@@ -375,16 +389,12 @@ impl EventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub(crate) struct OwnedDisplayHandle;
|
||||
|
||||
impl OwnedDisplayHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
||||
Ok(rwh_06::AppKitDisplayHandle::new().into())
|
||||
impl HasDisplayHandle for OwnedDisplayHandle {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
|
||||
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +407,22 @@ pub(super) fn stop_app_immediately(app: &NSApplication) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Tell all windows to close.
|
||||
///
|
||||
/// This will synchronously trigger `WindowEvent::Destroyed` within
|
||||
/// `windowWillClose:`, giving the application one last chance to handle
|
||||
/// those events. It doesn't matter if the user also ends up closing the
|
||||
/// windows in `Window`'s `Drop` impl, once a window has been closed once, it
|
||||
/// stays closed.
|
||||
///
|
||||
/// This ensures that no windows linger on after the event loop has exited,
|
||||
/// see <https://github.com/rust-windowing/winit/issues/4135>.
|
||||
pub(super) fn notify_windows_of_exit(app: &NSApplication) {
|
||||
for window in app.windows() {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
/// Catches panics that happen inside `f` and when a panic
|
||||
/// happens, stops the `sharedApplication`
|
||||
#[inline]
|
||||
@@ -422,65 +448,3 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopProxy {
|
||||
proxy_wake_up: Arc<AtomicBool>,
|
||||
source: CFRunLoopSourceRef,
|
||||
}
|
||||
|
||||
unsafe impl Send for EventLoopProxy {}
|
||||
unsafe impl Sync for EventLoopProxy {}
|
||||
|
||||
impl Drop for EventLoopProxy {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRelease(self.source as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for EventLoopProxy {
|
||||
fn clone(&self) -> Self {
|
||||
EventLoopProxy::new(self.proxy_wake_up.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
fn new(proxy_wake_up: Arc<AtomicBool>) -> Self {
|
||||
unsafe {
|
||||
// just wake up the eventloop
|
||||
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
|
||||
|
||||
// adding a Source to the main CFRunLoop lets us wake it up and
|
||||
// process user events through the normal OS EventLoop mechanisms.
|
||||
let rl = CFRunLoopGetMain();
|
||||
let mut context = CFRunLoopSourceContext {
|
||||
version: 0,
|
||||
info: ptr::null_mut(),
|
||||
retain: None,
|
||||
release: None,
|
||||
copyDescription: None,
|
||||
equal: None,
|
||||
hash: None,
|
||||
schedule: None,
|
||||
cancel: None,
|
||||
perform: event_loop_proxy_handler,
|
||||
};
|
||||
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(rl);
|
||||
|
||||
EventLoopProxy { proxy_wake_up, source }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wake_up(&self) {
|
||||
self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
|
||||
unsafe {
|
||||
// let the main thread know there's a new event
|
||||
CFRunLoopSourceSignal(self.source);
|
||||
let rl = CFRunLoopGetMain();
|
||||
CFRunLoopWakeUp(rl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,18 @@
|
||||
// TODO: Upstream these
|
||||
|
||||
#![allow(dead_code, non_snake_case, non_upper_case_globals)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
use core_foundation::array::CFArrayRef;
|
||||
use core_foundation::dictionary::CFDictionaryRef;
|
||||
use core_foundation::string::CFStringRef;
|
||||
use core_foundation::uuid::CFUUIDRef;
|
||||
use core_graphics::base::CGError;
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplayConfigRef};
|
||||
use objc2::ffi::NSInteger;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2_core_foundation::{cf_type, CFString, CFUUID};
|
||||
use objc2_core_graphics::CGDirectDisplayID;
|
||||
|
||||
pub type CGDisplayFadeInterval = f32;
|
||||
pub type CGDisplayReservationInterval = f32;
|
||||
pub type CGDisplayBlendFraction = f32;
|
||||
|
||||
pub const kCGDisplayBlendNormal: f32 = 0.0;
|
||||
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
|
||||
|
||||
pub type CGDisplayFadeReservationToken = u32;
|
||||
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
|
||||
|
||||
pub type Boolean = u8;
|
||||
pub const FALSE: Boolean = 0;
|
||||
pub const TRUE: Boolean = 1;
|
||||
|
||||
pub const kCGErrorSuccess: i32 = 0;
|
||||
pub const kCGErrorFailure: i32 = 1000;
|
||||
pub const kCGErrorIllegalArgument: i32 = 1001;
|
||||
pub const kCGErrorInvalidConnection: i32 = 1002;
|
||||
pub const kCGErrorInvalidContext: i32 = 1003;
|
||||
pub const kCGErrorCannotComplete: i32 = 1004;
|
||||
pub const kCGErrorNotImplemented: i32 = 1006;
|
||||
pub const kCGErrorRangeCheck: i32 = 1007;
|
||||
pub const kCGErrorTypeCheck: i32 = 1008;
|
||||
pub const kCGErrorInvalidOperation: i32 = 1010;
|
||||
pub const kCGErrorNoneAvailable: i32 = 1011;
|
||||
|
||||
pub const IO1BitIndexedPixels: &str = "P";
|
||||
pub const IO2BitIndexedPixels: &str = "PP";
|
||||
pub const IO4BitIndexedPixels: &str = "PPPP";
|
||||
pub const IO8BitIndexedPixels: &str = "PPPPPPPP";
|
||||
pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB";
|
||||
pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB";
|
||||
|
||||
pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
|
||||
pub const kIO64BitDirectPixels: &str = "-16R16G16B16";
|
||||
|
||||
pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16";
|
||||
pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
|
||||
|
||||
pub const IOYUV422Pixels: &str = "Y4U2V2";
|
||||
pub const IO8BitOverlayPixels: &str = "O8";
|
||||
|
||||
pub type CGWindowLevel = i32;
|
||||
pub type CGDisplayModeRef = *mut c_void;
|
||||
|
||||
// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework.
|
||||
// However, that framework was only introduced "publicly" in macOS 10.13.
|
||||
@@ -67,54 +23,13 @@ pub type CGDisplayModeRef = *mut c_void;
|
||||
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> *mut CFUUID;
|
||||
|
||||
pub fn CGDisplayGetDisplayIDFromUUID(uuid: &CFUUID) -> CGDirectDisplayID;
|
||||
}
|
||||
|
||||
#[link(name = "CoreGraphics", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGRestorePermanentDisplayConfiguration();
|
||||
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
|
||||
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
|
||||
pub fn CGConfigureDisplayFadeEffect(
|
||||
config: CGDisplayConfigRef,
|
||||
fadeOutSeconds: CGDisplayFadeInterval,
|
||||
fadeInSeconds: CGDisplayFadeInterval,
|
||||
fadeRed: f32,
|
||||
fadeGreen: f32,
|
||||
fadeBlue: f32,
|
||||
) -> CGError;
|
||||
pub fn CGAcquireDisplayFadeReservation(
|
||||
seconds: CGDisplayReservationInterval,
|
||||
token: *mut CGDisplayFadeReservationToken,
|
||||
) -> CGError;
|
||||
pub fn CGDisplayFade(
|
||||
token: CGDisplayFadeReservationToken,
|
||||
duration: CGDisplayFadeInterval,
|
||||
startBlend: CGDisplayBlendFraction,
|
||||
endBlend: CGDisplayBlendFraction,
|
||||
redBlend: f32,
|
||||
greenBlend: f32,
|
||||
blueBlend: f32,
|
||||
synchronous: Boolean,
|
||||
) -> CGError;
|
||||
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
|
||||
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
|
||||
pub fn CGDisplaySetDisplayMode(
|
||||
display: CGDirectDisplayID,
|
||||
mode: CGDisplayModeRef,
|
||||
options: CFDictionaryRef,
|
||||
) -> CGError;
|
||||
pub fn CGDisplayCopyAllDisplayModes(
|
||||
display: CGDirectDisplayID,
|
||||
options: CFDictionaryRef,
|
||||
) -> CFArrayRef;
|
||||
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
|
||||
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
|
||||
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
|
||||
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
|
||||
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
|
||||
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
|
||||
|
||||
// Wildly used private APIs; Apple uses them for their Terminal.app.
|
||||
pub fn CGSMainConnectionID() -> *mut AnyObject;
|
||||
pub fn CGSSetWindowBackgroundBlurRadius(
|
||||
@@ -124,50 +39,12 @@ extern "C" {
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
mod core_video {
|
||||
use super::*;
|
||||
|
||||
#[link(name = "CoreVideo", kind = "framework")]
|
||||
extern "C" {}
|
||||
|
||||
// CVBase.h
|
||||
|
||||
pub type CVTimeFlags = i32; // int32_t
|
||||
pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CVTime {
|
||||
pub time_value: i64, // int64_t
|
||||
pub time_scale: i32, // int32_t
|
||||
pub flags: i32, // int32_t
|
||||
}
|
||||
|
||||
// CVReturn.h
|
||||
|
||||
pub type CVReturn = i32; // int32_t
|
||||
pub const kCVReturnSuccess: CVReturn = 0;
|
||||
|
||||
// CVDisplayLink.h
|
||||
|
||||
pub type CVDisplayLinkRef = *mut c_void;
|
||||
|
||||
extern "C" {
|
||||
pub fn CVDisplayLinkCreateWithCGDisplay(
|
||||
displayID: CGDirectDisplayID,
|
||||
displayLinkOut: *mut CVDisplayLinkRef,
|
||||
) -> CVReturn;
|
||||
pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod(
|
||||
displayLink: CVDisplayLinkRef,
|
||||
) -> CVTime;
|
||||
pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef);
|
||||
}
|
||||
}
|
||||
|
||||
pub use core_video::*;
|
||||
#[repr(transparent)]
|
||||
pub struct TISInputSource(std::ffi::c_void);
|
||||
pub type TISInputSourceRef = *mut TISInputSource;
|
||||
|
||||
cf_type!(
|
||||
unsafe impl TISInputSource {}
|
||||
);
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct UCKeyboardLayout(std::ffi::c_void);
|
||||
@@ -184,15 +61,15 @@ pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1;
|
||||
|
||||
#[link(name = "Carbon", kind = "framework")]
|
||||
extern "C" {
|
||||
pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef;
|
||||
pub static kTISPropertyUnicodeKeyLayoutData: &'static CFString;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn TISGetInputSourceProperty(
|
||||
inputSource: TISInputSourceRef,
|
||||
propertyKey: CFStringRef,
|
||||
inputSource: &TISInputSource,
|
||||
propertyKey: &CFString,
|
||||
) -> *mut c_void;
|
||||
|
||||
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef;
|
||||
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut TISInputSource;
|
||||
|
||||
pub fn LMGetKbdType() -> u8;
|
||||
|
||||
@@ -210,45 +87,3 @@ extern "C" {
|
||||
unicodeString: *mut UniChar,
|
||||
) -> OSStatus;
|
||||
}
|
||||
|
||||
// CGWindowLevel.h
|
||||
//
|
||||
// Note: There are two different things at play in this header:
|
||||
// `CGWindowLevel` and `CGWindowLevelKey`.
|
||||
//
|
||||
// It seems like there was a push towards using "key" values instead of the
|
||||
// raw window level values, and then you were supposed to use
|
||||
// `CGWindowLevelForKey` to get the actual level.
|
||||
//
|
||||
// But the values that `NSWindowLevel` has are compiled in, and as such has
|
||||
// to remain ABI compatible, so they're safe for us to hardcode as well.
|
||||
#[allow(dead_code, non_upper_case_globals)]
|
||||
mod window_level {
|
||||
const kCGNumReservedWindowLevels: i32 = 16;
|
||||
const kCGNumReservedBaseWindowLevels: i32 = 5;
|
||||
|
||||
pub const kCGBaseWindowLevel: i32 = i32::MIN;
|
||||
pub const kCGMinimumWindowLevel: i32 = kCGBaseWindowLevel + kCGNumReservedBaseWindowLevels;
|
||||
pub const kCGMaximumWindowLevel: i32 = i32::MAX - kCGNumReservedWindowLevels;
|
||||
|
||||
pub const kCGDesktopWindowLevel: i32 = kCGMinimumWindowLevel + 20;
|
||||
pub const kCGDesktopIconWindowLevel: i32 = kCGDesktopWindowLevel + 20;
|
||||
pub const kCGBackstopMenuLevel: i32 = -20;
|
||||
pub const kCGNormalWindowLevel: i32 = 0;
|
||||
pub const kCGFloatingWindowLevel: i32 = 3;
|
||||
pub const kCGTornOffMenuWindowLevel: i32 = 3;
|
||||
pub const kCGModalPanelWindowLevel: i32 = 8;
|
||||
pub const kCGUtilityWindowLevel: i32 = 19;
|
||||
pub const kCGDockWindowLevel: i32 = 20;
|
||||
pub const kCGMainMenuWindowLevel: i32 = 24;
|
||||
pub const kCGStatusWindowLevel: i32 = 25;
|
||||
pub const kCGPopUpMenuWindowLevel: i32 = 101;
|
||||
pub const kCGOverlayWindowLevel: i32 = 102;
|
||||
pub const kCGHelpWindowLevel: i32 = 200;
|
||||
pub const kCGDraggingWindowLevel: i32 = 500;
|
||||
pub const kCGScreenSaverWindowLevel: i32 = 1000;
|
||||
pub const kCGAssistiveTechHighWindowLevel: i32 = 1500;
|
||||
pub const kCGCursorWindowLevel: i32 = kCGMaximumWindowLevel - 1;
|
||||
}
|
||||
|
||||
pub use window_level::*;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::sel;
|
||||
use objc2::{sel, MainThreadMarker};
|
||||
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
|
||||
use objc2_foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
|
||||
use objc2_foundation::{ns_string, NSProcessInfo, NSString};
|
||||
|
||||
struct KeyEquivalent<'a> {
|
||||
key: &'a NSString,
|
||||
@@ -48,10 +48,7 @@ pub fn initialize(app: &NSApplication) {
|
||||
Some(sel!(hideOtherApplications:)),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("h"),
|
||||
masks: Some(
|
||||
NSEventModifierFlags::NSEventModifierFlagOption
|
||||
| NSEventModifierFlags::NSEventModifierFlagCommand,
|
||||
),
|
||||
masks: Some(NSEventModifierFlags::Option | NSEventModifierFlags::Command),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ mod app;
|
||||
mod app_state;
|
||||
mod cursor;
|
||||
mod event;
|
||||
mod event_handler;
|
||||
mod event_loop;
|
||||
mod ffi;
|
||||
mod menu;
|
||||
@@ -15,54 +14,10 @@ mod view;
|
||||
mod window;
|
||||
mod window_delegate;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey, KeyEventExtra};
|
||||
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey};
|
||||
pub(crate) use self::event_loop::{
|
||||
ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle,
|
||||
PlatformSpecificEventLoopAttributes,
|
||||
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
|
||||
};
|
||||
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
|
||||
pub(crate) use self::window::{Window, WindowId};
|
||||
pub(crate) use self::monitor::MonitorHandle;
|
||||
pub(crate) use self::window::Window;
|
||||
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;
|
||||
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
|
||||
use crate::event::DeviceId as RootDeviceId;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub const fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
// Constant device ID; to be removed when if backend is updated to report real device IDs.
|
||||
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct FingerId;
|
||||
|
||||
impl FingerId {
|
||||
pub const fn dummy() -> Self {
|
||||
FingerId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OsError {
|
||||
CGError(core_graphics::base::CGError),
|
||||
CreationError(&'static str),
|
||||
}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
OsError::CGError(e) => f.pad(&format!("CGError {e}")),
|
||||
OsError::CreationError(e) => f.pad(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::ptr::NonNull;
|
||||
use std::{fmt, ptr};
|
||||
|
||||
use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex};
|
||||
use core_foundation::base::{CFRelease, TCFType};
|
||||
use core_foundation::string::CFString;
|
||||
use core_graphics::display::{
|
||||
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
|
||||
};
|
||||
use dispatch2::run_on_main;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_app_kit::NSScreen;
|
||||
use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect};
|
||||
use objc2_core_foundation::{CFArray, CFRetained, CFUUID};
|
||||
use objc2_core_graphics::{
|
||||
CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyAllDisplayModes, CGDisplayCopyDisplayMode,
|
||||
CGDisplayMode, CGDisplayModelNumber, CGGetActiveDisplayList, CGMainDisplayID,
|
||||
};
|
||||
use objc2_core_video::{kCVReturnSuccess, CVDisplayLink, CVTimeFlags};
|
||||
use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect};
|
||||
use tracing::warn;
|
||||
|
||||
use super::ffi;
|
||||
use super::util::cgerr;
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::monitor::{MonitorHandleProvider, VideoMode};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VideoModeHandle {
|
||||
size: PhysicalSize<u32>,
|
||||
bit_depth: Option<NonZeroU16>,
|
||||
refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
pub(crate) mode: VideoMode,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
pub(crate) native_mode: NativeDisplayMode,
|
||||
}
|
||||
|
||||
impl PartialEq for VideoModeHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.size == other.size
|
||||
&& self.bit_depth == other.bit_depth
|
||||
&& self.refresh_rate_millihertz == other.refresh_rate_millihertz
|
||||
&& self.monitor == other.monitor
|
||||
self.monitor == other.monitor && self.mode == other.mode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,6 @@ impl Eq for VideoModeHandle {}
|
||||
|
||||
impl std::hash::Hash for VideoModeHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.size.hash(state);
|
||||
self.bit_depth.hash(state);
|
||||
self.refresh_rate_millihertz.hash(state);
|
||||
self.monitor.hash(state);
|
||||
}
|
||||
}
|
||||
@@ -50,46 +47,28 @@ impl std::hash::Hash for VideoModeHandle {
|
||||
impl std::fmt::Debug for VideoModeHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("VideoModeHandle")
|
||||
.field("size", &self.size)
|
||||
.field("bit_depth", &self.bit_depth)
|
||||
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz)
|
||||
.field("mode", &self.mode)
|
||||
.field("monitor", &self.monitor)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct NativeDisplayMode(pub CFRetained<CGDisplayMode>);
|
||||
|
||||
unsafe impl Send for NativeDisplayMode {}
|
||||
unsafe impl Sync for NativeDisplayMode {}
|
||||
|
||||
impl Drop for NativeDisplayMode {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ffi::CGDisplayModeRelease(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for NativeDisplayMode {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
ffi::CGDisplayModeRetain(self.0);
|
||||
}
|
||||
NativeDisplayMode(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
fn new(
|
||||
monitor: MonitorHandle,
|
||||
mode: NativeDisplayMode,
|
||||
native_mode: NativeDisplayMode,
|
||||
refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
) -> Self {
|
||||
unsafe {
|
||||
#[allow(deprecated)]
|
||||
let pixel_encoding =
|
||||
CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode.0))
|
||||
.to_string();
|
||||
CGDisplayMode::pixel_encoding(Some(&native_mode.0)).unwrap().to_string();
|
||||
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
|
||||
32
|
||||
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
|
||||
@@ -100,132 +79,139 @@ impl VideoModeHandle {
|
||||
unimplemented!()
|
||||
};
|
||||
|
||||
VideoModeHandle {
|
||||
let mode = VideoMode {
|
||||
size: PhysicalSize::new(
|
||||
ffi::CGDisplayModeGetPixelWidth(mode.0) as u32,
|
||||
ffi::CGDisplayModeGetPixelHeight(mode.0) as u32,
|
||||
CGDisplayMode::pixel_width(Some(&native_mode.0)) as u32,
|
||||
CGDisplayMode::pixel_height(Some(&native_mode.0)) as u32,
|
||||
),
|
||||
refresh_rate_millihertz,
|
||||
bit_depth: NonZeroU16::new(bit_depth),
|
||||
monitor: monitor.clone(),
|
||||
native_mode: mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.monitor.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorHandle(CGDirectDisplayID);
|
||||
|
||||
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
|
||||
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
|
||||
// unique identifier that persists even across system reboots
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
||||
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
|
||||
VideoModeHandle { mode, monitor: monitor.clone(), native_mode }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MonitorHandle {}
|
||||
|
||||
impl PartialOrd for MonitorHandle {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for MonitorHandle {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
||||
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for MonitorHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||
if let Ok(displays) = CGDisplay::active_displays() {
|
||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||
for display in displays {
|
||||
monitors.push_back(MonitorHandle(display));
|
||||
}
|
||||
monitors
|
||||
} else {
|
||||
VecDeque::with_capacity(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary_monitor() -> MonitorHandle {
|
||||
MonitorHandle(CGDisplay::main().id)
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MonitorHandle")
|
||||
.field("name", &self.name())
|
||||
.field("native_identifier", &self.native_identifier())
|
||||
.field("position", &self.position())
|
||||
.field("scale_factor", &self.scale_factor())
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
/// `CGDirectDisplayID` is documented as:
|
||||
/// > a framebuffer, a color correction (gamma) table, and possibly an attached monitor.
|
||||
///
|
||||
/// That is, it doesn't actually represent the monitor itself. Instead, we use the UUID of the
|
||||
/// monitor, as retrieved from `CGDisplayCreateUUIDFromDisplayID` (this makes the monitor ID stable,
|
||||
/// even across reboots and video mode changes).
|
||||
///
|
||||
/// NOTE: I'd be perfectly valid to store `[u8; 16]` in here instead, we only store `CFUUID` to
|
||||
/// avoid having to re-create it when we want to fetch the display ID.
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct MonitorHandle(CFRetained<CFUUID>);
|
||||
|
||||
impl MonitorHandle {
|
||||
pub fn new(id: CGDirectDisplayID) -> Self {
|
||||
MonitorHandle(id)
|
||||
/// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle.
|
||||
fn uuid(&self) -> u128 {
|
||||
u128::from_ne_bytes(self.0.uuid_bytes().into())
|
||||
}
|
||||
|
||||
fn display_id(&self) -> CGDirectDisplayID {
|
||||
unsafe { ffi::CGDisplayGetDisplayIDFromUUID(&self.0) }
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn new(display_id: CGDirectDisplayID) -> Option<Self> {
|
||||
// kCGNullDirectDisplay
|
||||
if display_id == 0 {
|
||||
// `CGDisplayCreateUUIDFromDisplayID` checks kCGNullDirectDisplay internally.
|
||||
warn!("constructing monitor from invalid display ID 0; falling back to main monitor");
|
||||
}
|
||||
// SAFETY: Valid to call.
|
||||
let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(display_id) };
|
||||
let ptr = NonNull::new(ptr)?;
|
||||
// SAFETY: `CGDisplayCreateUUIDFromDisplayID` is a "create" function, so the pointer has
|
||||
// +1 retain count.
|
||||
let uuid = unsafe { CFRetained::from_raw(ptr) };
|
||||
Some(Self(uuid))
|
||||
}
|
||||
|
||||
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
let current_display_mode =
|
||||
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.display_id()) }.unwrap());
|
||||
refresh_rate_millihertz(self.display_id(), ¤t_display_mode)
|
||||
}
|
||||
|
||||
pub fn video_mode_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
let refresh_rate_millihertz = self.refresh_rate_millihertz();
|
||||
let monitor = self.clone();
|
||||
|
||||
let array = unsafe { CGDisplayCopyAllDisplayModes(self.display_id(), None) };
|
||||
let modes = if let Some(array) = array {
|
||||
// SAFETY: `CGDisplayCopyAllDisplayModes` is documented to return an array of
|
||||
// display modes.
|
||||
unsafe { CFRetained::cast_unchecked::<CFArray<CGDisplayMode>>(array) }
|
||||
} else {
|
||||
// Occasionally, certain CalDigit Thunderbolt Hubs report a spurious monitor during
|
||||
// sleep/wake/cycling monitors. It tends to have null or 1 video mode only.
|
||||
// See <https://github.com/bevyengine/bevy/issues/17827>.
|
||||
warn!(monitor = ?self, "failed to get a list of display modes");
|
||||
CFArray::empty()
|
||||
};
|
||||
|
||||
modes.into_iter().map(move |mode| {
|
||||
let cg_refresh_rate_hertz = unsafe { CGDisplayMode::refresh_rate(Some(&mode)) };
|
||||
|
||||
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
|
||||
// isn't a CRT
|
||||
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0.0 {
|
||||
NonZeroU32::new((cg_refresh_rate_hertz * 1000.0).round() as u32)
|
||||
} else {
|
||||
refresh_rate_millihertz
|
||||
};
|
||||
|
||||
VideoModeHandle::new(monitor.clone(), NativeDisplayMode(mode), refresh_rate_millihertz)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
|
||||
let uuid = self.uuid();
|
||||
NSScreen::screens(mtm).into_iter().find(|screen| {
|
||||
let other_native_id = get_display_id(screen);
|
||||
if let Some(other) = MonitorHandle::new(other_native_id) {
|
||||
uuid == other.uuid()
|
||||
} else {
|
||||
// Display ID was just fetched from live NSScreen, but can still result in `None`
|
||||
// with certain Thunderbolt docked monitors.
|
||||
warn!(other_native_id, "comparing against screen with invalid display ID");
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandleProvider for MonitorHandle {
|
||||
fn id(&self) -> u128 {
|
||||
self.uuid()
|
||||
}
|
||||
|
||||
fn native_id(&self) -> u64 {
|
||||
self.display_id() as _
|
||||
}
|
||||
|
||||
// TODO: Be smarter about this:
|
||||
//
|
||||
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
|
||||
pub fn name(&self) -> Option<String> {
|
||||
let MonitorHandle(display_id) = *self;
|
||||
let screen_num = CGDisplay::new(display_id).model_number();
|
||||
Some(format!("Monitor #{screen_num}"))
|
||||
fn name(&self) -> Option<std::borrow::Cow<'_, str>> {
|
||||
let screen_num = unsafe { CGDisplayModelNumber(self.display_id()) };
|
||||
Some(format!("Monitor #{screen_num}").into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_identifier(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
|
||||
fn position(&self) -> Option<PhysicalPosition<i32>> {
|
||||
// This is already in screen coordinates. If we were using `NSScreen`,
|
||||
// then a conversion would've been needed:
|
||||
// flip_window_screen_coordinates(self.ns_screen(mtm)?.frame())
|
||||
let bounds = unsafe { CGDisplayBounds(self.native_identifier()) };
|
||||
let bounds = unsafe { CGDisplayBounds(self.display_id()) };
|
||||
let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y);
|
||||
Some(position.to_physical(self.scale_factor()))
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
fn scale_factor(&self) -> f64 {
|
||||
run_on_main(|mtm| {
|
||||
match self.ns_screen(mtm) {
|
||||
Some(screen) => screen.backingScaleFactor() as f64,
|
||||
@@ -234,67 +220,58 @@ impl MonitorHandle {
|
||||
})
|
||||
}
|
||||
|
||||
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
let current_display_mode =
|
||||
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
|
||||
refresh_rate_millihertz(self.0, ¤t_display_mode)
|
||||
fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
let mode =
|
||||
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.display_id()) }.unwrap());
|
||||
let refresh_rate_millihertz = refresh_rate_millihertz(self.display_id(), &mode);
|
||||
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode)
|
||||
}
|
||||
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
|
||||
let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode);
|
||||
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz))
|
||||
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
|
||||
Box::new(self.video_mode_handles().map(|mode| mode.mode))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||
let mut expected_count = 0;
|
||||
let res = cgerr(unsafe { CGGetActiveDisplayList(0, ptr::null_mut(), &mut expected_count) });
|
||||
if res.is_err() {
|
||||
return VecDeque::with_capacity(0);
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
let refresh_rate_millihertz = self.refresh_rate_millihertz();
|
||||
let monitor = self.clone();
|
||||
let mut displays: Vec<CGDirectDisplayID> = vec![0; expected_count as usize];
|
||||
let mut actual_count = 0;
|
||||
let res = cgerr(unsafe {
|
||||
CGGetActiveDisplayList(expected_count, displays.as_mut_ptr(), &mut actual_count)
|
||||
});
|
||||
displays.truncate(actual_count as usize);
|
||||
|
||||
unsafe {
|
||||
let modes = {
|
||||
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
|
||||
assert!(!array.is_null(), "failed to get list of display modes");
|
||||
let array_count = CFArrayGetCount(array);
|
||||
let modes: Vec<_> = (0..array_count)
|
||||
.map(move |i| {
|
||||
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
|
||||
ffi::CGDisplayModeRetain(mode);
|
||||
mode
|
||||
})
|
||||
.collect();
|
||||
CFRelease(array as *const _);
|
||||
modes
|
||||
};
|
||||
|
||||
modes.into_iter().map(move |mode| {
|
||||
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
|
||||
|
||||
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
|
||||
// isn't a CRT
|
||||
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0 {
|
||||
NonZeroU32::new((cg_refresh_rate_hertz * 1000) as u32)
|
||||
} else {
|
||||
refresh_rate_millihertz
|
||||
};
|
||||
|
||||
VideoModeHandle::new(
|
||||
monitor.clone(),
|
||||
NativeDisplayMode(mode),
|
||||
refresh_rate_millihertz,
|
||||
)
|
||||
})
|
||||
}
|
||||
if res.is_err() {
|
||||
return VecDeque::with_capacity(0);
|
||||
}
|
||||
|
||||
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
|
||||
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
|
||||
NSScreen::screens(mtm).into_iter().find(|screen| {
|
||||
let other_native_id = get_display_id(screen);
|
||||
let other_uuid = unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID)
|
||||
};
|
||||
uuid == other_uuid
|
||||
})
|
||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||
for display in displays {
|
||||
// Display ID just fetched from `CGGetActiveDisplayList`, should be fine to unwrap.
|
||||
monitors.push_back(MonitorHandle::new(display).expect("invalid display ID"));
|
||||
}
|
||||
monitors
|
||||
}
|
||||
|
||||
pub fn primary_monitor() -> MonitorHandle {
|
||||
// Display ID just fetched from `CGMainDisplayID`, should be fine to unwrap.
|
||||
MonitorHandle::new(unsafe { CGMainDisplayID() }).expect("invalid display ID")
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MonitorHandle")
|
||||
.field("name", &self.name())
|
||||
.field("uuid", &self.uuid())
|
||||
.field("display_id", &self.display_id())
|
||||
.field("position", &self.position())
|
||||
.field("scale_factor", &self.scale_factor())
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,15 +283,14 @@ pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
|
||||
|
||||
// Retrieve the CGDirectDisplayID associated with this screen
|
||||
//
|
||||
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
|
||||
// to be an NSNumber. See documentation for `deviceDescription` for details:
|
||||
// The value from @"NSScreenNumber" in deviceDescription is guaranteed
|
||||
// to be an NSNumber. See documentation for details:
|
||||
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
|
||||
let obj = device_description
|
||||
.get(key)
|
||||
.expect("failed getting screen display id from device description");
|
||||
let obj: *const AnyObject = obj;
|
||||
let obj: *const NSNumber = obj.cast();
|
||||
let obj: &NSNumber = unsafe { &*obj };
|
||||
.objectForKey(key)
|
||||
.expect("failed getting screen display id from device description")
|
||||
.downcast::<NSNumber>()
|
||||
.expect("NSScreenNumber must be NSNumber");
|
||||
|
||||
obj.as_u32()
|
||||
})
|
||||
@@ -335,7 +311,7 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
|
||||
// It is intentional that we use `CGMainDisplayID` (as opposed to
|
||||
// `NSScreen::mainScreen`), because that's what the screen coordinates
|
||||
// are relative to, no matter which display the window is currently on.
|
||||
let main_screen_height = CGDisplay::main().bounds().size.height;
|
||||
let main_screen_height = unsafe { CGDisplayBounds(CGMainDisplayID()) }.size.height;
|
||||
|
||||
let y = main_screen_height - frame.size.height - frame.origin.y;
|
||||
NSPoint::new(frame.origin.x, y)
|
||||
@@ -343,26 +319,81 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
|
||||
|
||||
fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option<NonZeroU32> {
|
||||
unsafe {
|
||||
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode.0);
|
||||
let refresh_rate = CGDisplayMode::refresh_rate(Some(&mode.0));
|
||||
if refresh_rate > 0.0 {
|
||||
return NonZeroU32::new((refresh_rate * 1000.0).round() as u32);
|
||||
}
|
||||
|
||||
let mut display_link = std::ptr::null_mut();
|
||||
if ffi::CVDisplayLinkCreateWithCGDisplay(id, &mut display_link) != ffi::kCVReturnSuccess {
|
||||
#[allow(deprecated)]
|
||||
if CVDisplayLink::create_with_cg_display(id, NonNull::from(&mut display_link))
|
||||
!= kCVReturnSuccess
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
|
||||
ffi::CVDisplayLinkRelease(display_link);
|
||||
let display_link = CFRetained::from_raw(NonNull::new(display_link).unwrap());
|
||||
#[allow(deprecated)]
|
||||
let time = display_link.nominal_output_video_refresh_period();
|
||||
|
||||
// This value is indefinite if an invalid display link was specified
|
||||
if time.flags & ffi::kCVTimeIsIndefinite != 0 {
|
||||
if time.flags & CVTimeFlags::IsIndefinite.0 != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
(time.time_scale as i64)
|
||||
.checked_div(time.time_value)
|
||||
(time.timeScale as i64)
|
||||
.checked_div(time.timeValue)
|
||||
.map(|v| (v * 1000) as u32)
|
||||
.and_then(NonZeroU32::new)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn uuid_stable() {
|
||||
let handle_a = MonitorHandle::new(1).unwrap();
|
||||
let handle_b = MonitorHandle::new(1).unwrap();
|
||||
assert_eq!(handle_a, handle_b);
|
||||
assert_eq!(handle_a.display_id(), handle_b.display_id());
|
||||
assert_eq!(handle_a.uuid(), handle_b.uuid());
|
||||
|
||||
let handle_a = primary_monitor();
|
||||
let handle_b = primary_monitor();
|
||||
assert_eq!(handle_a, handle_b);
|
||||
assert_eq!(handle_a.display_id(), handle_b.display_id());
|
||||
assert_eq!(handle_a.uuid(), handle_b.uuid());
|
||||
}
|
||||
|
||||
/// Test the MonitorHandle::new fallback.
|
||||
#[test]
|
||||
fn monitorhandle_from_zero() {
|
||||
let handle0 = MonitorHandle::new(0).unwrap();
|
||||
let handle1 = MonitorHandle::new(1).unwrap();
|
||||
assert_eq!(handle0, handle1);
|
||||
assert_eq!(handle0.display_id(), handle1.display_id());
|
||||
assert_eq!(handle0.uuid(), handle1.uuid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_invalid_id() {
|
||||
// Assume there are never this many monitors connected.
|
||||
assert!(MonitorHandle::new(10000).is_none());
|
||||
}
|
||||
|
||||
/// Test that calling `CGDisplayGetDisplayIDFromUUID` on an invalid UUID returns an invalid
|
||||
/// display ID.
|
||||
#[test]
|
||||
fn invalid_monitor_handle() {
|
||||
// `CGMainDisplayID` must be called to avoid:
|
||||
// ```
|
||||
// Assertion failed: (did_initialize), function CGS_REQUIRE_INIT, file CGInitialization.c, line 44.
|
||||
// ```
|
||||
// See https://github.com/JXA-Cookbook/JXA-Cookbook/issues/27#issuecomment-277517668
|
||||
let _ = unsafe { CGMainDisplayID() };
|
||||
|
||||
let handle = MonitorHandle(CFUUID::new(None).unwrap());
|
||||
assert_eq!(handle.display_id(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,22 +9,16 @@ use std::ptr;
|
||||
use std::rc::Weak;
|
||||
use std::time::Instant;
|
||||
|
||||
use block2::Block;
|
||||
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
|
||||
use core_foundation::date::CFAbsoluteTimeGetCurrent;
|
||||
use core_foundation::runloop::{
|
||||
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
|
||||
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
|
||||
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
|
||||
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
|
||||
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{
|
||||
kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFAbsoluteTimeGetCurrent, CFIndex, CFRetained,
|
||||
CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, CFRunLoopObserverCallBack,
|
||||
CFRunLoopObserverContext, CFRunLoopTimer,
|
||||
};
|
||||
use objc2_foundation::MainThreadMarker;
|
||||
use tracing::error;
|
||||
|
||||
use super::app_state::ApplicationDelegate;
|
||||
use super::app_state::AppState;
|
||||
use super::event_loop::{stop_app_on_panic, PanicInfo};
|
||||
use super::ffi;
|
||||
|
||||
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
|
||||
where
|
||||
@@ -48,8 +42,8 @@ where
|
||||
}
|
||||
|
||||
// begin is queued with the highest priority to ensure it is processed before other observers
|
||||
extern "C" fn control_flow_begin_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
extern "C-unwind" fn control_flow_begin_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
panic_info: *mut c_void,
|
||||
) {
|
||||
@@ -57,9 +51,9 @@ extern "C" fn control_flow_begin_handler(
|
||||
control_flow_handler(panic_info, |panic_info| {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopAfterWaiting => {
|
||||
CFRunLoopActivity::AfterWaiting => {
|
||||
// trace!("Triggered `CFRunLoopAfterWaiting`");
|
||||
ApplicationDelegate::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
|
||||
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
|
||||
// trace!("Completed `CFRunLoopAfterWaiting`");
|
||||
},
|
||||
_ => unreachable!(),
|
||||
@@ -70,8 +64,8 @@ extern "C" fn control_flow_begin_handler(
|
||||
|
||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||
// without that, LoopExiting would get sent after AboutToWait
|
||||
extern "C" fn control_flow_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
extern "C-unwind" fn control_flow_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
panic_info: *mut c_void,
|
||||
) {
|
||||
@@ -79,12 +73,12 @@ extern "C" fn control_flow_end_handler(
|
||||
control_flow_handler(panic_info, |panic_info| {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => {
|
||||
CFRunLoopActivity::BeforeWaiting => {
|
||||
// trace!("Triggered `CFRunLoopBeforeWaiting`");
|
||||
ApplicationDelegate::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
|
||||
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
|
||||
// trace!("Completed `CFRunLoopBeforeWaiting`");
|
||||
},
|
||||
kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen
|
||||
CFRunLoopActivity::Exit => (), /* unimplemented!(), // not expected to ever happen */
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
@@ -92,44 +86,32 @@ extern "C" fn control_flow_end_handler(
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RunLoop(CFRunLoopRef);
|
||||
|
||||
impl Default for RunLoop {
|
||||
fn default() -> Self {
|
||||
Self(ptr::null_mut())
|
||||
}
|
||||
}
|
||||
pub struct RunLoop(CFRetained<CFRunLoop>);
|
||||
|
||||
impl RunLoop {
|
||||
pub fn main(mtm: MainThreadMarker) -> Self {
|
||||
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
|
||||
// scheduling (and scheduling a non-`Send` block) to that thread is allowed.
|
||||
let _ = mtm;
|
||||
RunLoop(unsafe { CFRunLoopGetMain() })
|
||||
RunLoop(CFRunLoop::main().unwrap())
|
||||
}
|
||||
|
||||
pub fn wakeup(&self) {
|
||||
unsafe { CFRunLoopWakeUp(self.0) }
|
||||
self.0.wake_up();
|
||||
}
|
||||
|
||||
unsafe fn add_observer(
|
||||
&self,
|
||||
flags: CFOptionFlags,
|
||||
flags: CFRunLoopActivity,
|
||||
// The lower the value, the sooner this will run
|
||||
priority: CFIndex,
|
||||
handler: CFRunLoopObserverCallBack,
|
||||
context: *mut CFRunLoopObserverContext,
|
||||
) {
|
||||
let observer = unsafe {
|
||||
CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
flags,
|
||||
ffi::TRUE, // Indicates we want this to run repeatedly
|
||||
priority, // The lower the value, the sooner this will run
|
||||
handler,
|
||||
context,
|
||||
)
|
||||
};
|
||||
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
|
||||
let observer =
|
||||
unsafe { CFRunLoopObserver::new(None, flags.0, true, priority, handler, context) }
|
||||
.unwrap();
|
||||
self.0.add_observer(Some(&observer), unsafe { kCFRunLoopCommonModes });
|
||||
}
|
||||
|
||||
/// Submit a closure to run on the main thread as the next step in the run loop, before other
|
||||
@@ -166,10 +148,6 @@ impl RunLoop {
|
||||
/// put the event at the very front of the queue, to be handled as soon as possible after
|
||||
/// handling whatever event it's currently handling.
|
||||
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
|
||||
extern "C" {
|
||||
fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: &Block<dyn Fn()>);
|
||||
}
|
||||
|
||||
// Convert `FnOnce()` to `Block<dyn Fn()>`.
|
||||
let closure = Cell::new(Some(closure));
|
||||
let block = block2::RcBlock::new(move || {
|
||||
@@ -195,10 +173,10 @@ impl RunLoop {
|
||||
// and be delivered to the application afterwards.
|
||||
//
|
||||
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
|
||||
let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef };
|
||||
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
|
||||
|
||||
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
|
||||
unsafe { CFRunLoopPerformBlock(self.0, mode, &block) }
|
||||
unsafe { self.0.perform_block(Some(mode), Some(&block)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,15 +191,15 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
|
||||
copyDescription: None,
|
||||
};
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopAfterWaiting,
|
||||
CFRunLoopActivity::AfterWaiting,
|
||||
CFIndex::MIN,
|
||||
control_flow_begin_handler,
|
||||
Some(control_flow_begin_handler),
|
||||
&mut context as *mut _,
|
||||
);
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting,
|
||||
CFIndex::MAX,
|
||||
control_flow_end_handler,
|
||||
Some(control_flow_end_handler),
|
||||
&mut context as *mut _,
|
||||
);
|
||||
}
|
||||
@@ -229,7 +207,7 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoopWaker {
|
||||
timer: CFRunLoopTimerRef,
|
||||
timer: CFRetained<CFRunLoopTimer>,
|
||||
|
||||
/// An arbitrary instant in the past, that will trigger an immediate wake
|
||||
/// We save this as the `next_fire_date` for consistency so we can
|
||||
@@ -244,30 +222,28 @@ pub struct EventLoopWaker {
|
||||
|
||||
impl Drop for EventLoopWaker {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRunLoopTimerInvalidate(self.timer);
|
||||
CFRelease(self.timer as _);
|
||||
}
|
||||
self.timer.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker {
|
||||
pub(crate) fn new() -> Self {
|
||||
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
||||
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _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(),
|
||||
let timer = CFRunLoopTimer::new(
|
||||
None,
|
||||
f64::MAX,
|
||||
0.000_000_1,
|
||||
0,
|
||||
0,
|
||||
wakeup_main_loop,
|
||||
Some(wakeup_main_loop),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
|
||||
)
|
||||
.unwrap();
|
||||
CFRunLoop::main().unwrap().add_timer(Some(&timer), kCFRunLoopCommonModes);
|
||||
Self { timer, start_instant: Instant::now(), next_fire_date: None }
|
||||
}
|
||||
}
|
||||
@@ -275,14 +251,14 @@ impl EventLoopWaker {
|
||||
pub fn stop(&mut self) {
|
||||
if self.next_fire_date.is_some() {
|
||||
self.next_fire_date = None;
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
|
||||
self.timer.set_next_fire_date(f64::MAX);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
if self.next_fire_date != Some(self.start_instant) {
|
||||
self.next_fire_date = Some(self.start_instant);
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
|
||||
self.timer.set_next_fire_date(f64::MIN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,13 +271,11 @@ impl EventLoopWaker {
|
||||
Some(instant) => {
|
||||
if self.next_fire_date != Some(instant) {
|
||||
self.next_fire_date = Some(instant);
|
||||
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)
|
||||
}
|
||||
let current = CFAbsoluteTimeGetCurrent();
|
||||
let duration = instant - now;
|
||||
let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0
|
||||
+ duration.as_secs() as f64;
|
||||
self.timer.set_next_fire_date(current + fsecs);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use objc2_core_graphics::CGError;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::error::OsError;
|
||||
|
||||
macro_rules! trace_scope {
|
||||
($s:literal) => {
|
||||
let _crate =
|
||||
@@ -26,3 +29,12 @@ impl Drop for TraceGuard {
|
||||
trace!(target = self.module_path, "Completed `{}`", self.called_from_fn);
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> {
|
||||
if err == CGError::Success {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(os_error!(format!("CGError {err:?}")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,36 +2,34 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
|
||||
use objc2::rc::{Retained, WeakId};
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::{AnyObject, Sel};
|
||||
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
|
||||
use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker};
|
||||
use objc2_app_kit::{
|
||||
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
|
||||
NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification,
|
||||
NSTrackingRectTag, NSView, NSWindow,
|
||||
};
|
||||
use objc2_foundation::{
|
||||
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
|
||||
NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject, NSObjectProtocol,
|
||||
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
|
||||
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
|
||||
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
|
||||
};
|
||||
|
||||
use super::app_state::ApplicationDelegate;
|
||||
use super::app_state::AppState;
|
||||
use super::cursor::{default_cursor, invisible_cursor};
|
||||
use super::event::{
|
||||
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
|
||||
scancode_to_physicalkey,
|
||||
};
|
||||
use super::window::WinitWindow;
|
||||
use super::DEVICE_ID;
|
||||
use super::window::window_id;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
WindowEvent,
|
||||
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
|
||||
PointerKind, PointerSource, TouchPhase, WindowEvent,
|
||||
};
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
|
||||
use crate::platform::macos::OptionAsAlt;
|
||||
use crate::window::WindowId as RootWindowId;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CursorState {
|
||||
@@ -83,7 +81,7 @@ fn key_to_modifier(key: &Key) -> Option<ModifiersState> {
|
||||
match key {
|
||||
Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT),
|
||||
Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL),
|
||||
Key::Named(NamedKey::Super) => Some(ModifiersState::SUPER),
|
||||
Key::Named(NamedKey::Meta) => Some(ModifiersState::META),
|
||||
Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT),
|
||||
_ => None,
|
||||
}
|
||||
@@ -94,7 +92,7 @@ fn get_right_modifier_code(key: &Key) -> KeyCode {
|
||||
Key::Named(NamedKey::Alt) => KeyCode::AltRight,
|
||||
Key::Named(NamedKey::Control) => KeyCode::ControlRight,
|
||||
Key::Named(NamedKey::Shift) => KeyCode::ShiftRight,
|
||||
Key::Named(NamedKey::Super) => KeyCode::SuperRight,
|
||||
Key::Named(NamedKey::Meta) => KeyCode::MetaRight,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -104,7 +102,7 @@ fn get_left_modifier_code(key: &Key) -> KeyCode {
|
||||
Key::Named(NamedKey::Alt) => KeyCode::AltLeft,
|
||||
Key::Named(NamedKey::Control) => KeyCode::ControlLeft,
|
||||
Key::Named(NamedKey::Shift) => KeyCode::ShiftLeft,
|
||||
Key::Named(NamedKey::Super) => KeyCode::SuperLeft,
|
||||
Key::Named(NamedKey::Meta) => KeyCode::MetaLeft,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -112,7 +110,7 @@ fn get_left_modifier_code(key: &Key) -> KeyCode {
|
||||
#[derive(Debug)]
|
||||
pub struct ViewState {
|
||||
/// Strong reference to the global application state.
|
||||
app_delegate: Retained<ApplicationDelegate>,
|
||||
app_state: Rc<AppState>,
|
||||
|
||||
cursor_state: RefCell<CursorState>,
|
||||
ime_position: Cell<NSPoint>,
|
||||
@@ -135,35 +133,25 @@ pub struct ViewState {
|
||||
marked_text: RefCell<Retained<NSMutableAttributedString>>,
|
||||
accepts_first_mouse: bool,
|
||||
|
||||
// Weak reference because the window keeps a strong reference to the view
|
||||
_ns_window: WeakId<WinitWindow>,
|
||||
|
||||
/// The state of the `Option` as `Alt`.
|
||||
option_as_alt: Cell<OptionAsAlt>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(NSView, NSResponder, NSObject))]
|
||||
#[ivars = ViewState]
|
||||
#[name = "WinitView"]
|
||||
pub(super) struct WinitView;
|
||||
|
||||
unsafe impl ClassType for WinitView {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSView;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitView";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitView {
|
||||
type Ivars = ViewState;
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(isFlipped)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitView {
|
||||
#[unsafe(method(isFlipped))]
|
||||
fn is_flipped(&self) -> bool {
|
||||
// `winit` uses the upper-left corner as the origin.
|
||||
true
|
||||
}
|
||||
|
||||
#[method(viewDidMoveToWindow)]
|
||||
#[unsafe(method(viewDidMoveToWindow))]
|
||||
fn view_did_move_to_window(&self) {
|
||||
trace_scope!("viewDidMoveToWindow");
|
||||
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
|
||||
@@ -178,9 +166,10 @@ declare_class!(
|
||||
self.ivars().tracking_rect.set(Some(tracking_rect));
|
||||
}
|
||||
|
||||
#[method(frameDidChange:)]
|
||||
fn frame_did_change(&self, _event: &NSEvent) {
|
||||
trace_scope!("frameDidChange:");
|
||||
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
|
||||
#[unsafe(method(viewFrameDidChangeNotification:))]
|
||||
fn frame_did_change(&self, _notification: Option<&AnyObject>) {
|
||||
trace_scope!("NSViewFrameDidChangeNotification");
|
||||
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
|
||||
self.removeTrackingRect(tracking_rect);
|
||||
}
|
||||
@@ -193,41 +182,45 @@ declare_class!(
|
||||
self.ivars().tracking_rect.set(Some(tracking_rect));
|
||||
|
||||
// Emit resize event here rather than from windowDidResize because:
|
||||
// 1. When a new window is created as a tab, the frame size may change without a window resize occurring.
|
||||
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height).
|
||||
// 1. When a new window is created as a tab, the frame size may change without a window
|
||||
// resize occurring.
|
||||
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
|
||||
// size (includes tab height).
|
||||
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
|
||||
let size = logical_size.to_physical::<u32>(self.scale_factor());
|
||||
self.queue_event(WindowEvent::Resized(size));
|
||||
self.queue_event(WindowEvent::SurfaceResized(size));
|
||||
}
|
||||
|
||||
#[method(drawRect:)]
|
||||
#[unsafe(method(drawRect:))]
|
||||
fn draw_rect(&self, _rect: NSRect) {
|
||||
trace_scope!("drawRect:");
|
||||
|
||||
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
|
||||
if let Some(window) = self.ivars()._ns_window.load() {
|
||||
self.ivars().app_delegate.handle_redraw(window.id());
|
||||
}
|
||||
self.ivars().app_state.handle_redraw(window_id(&self.window()));
|
||||
|
||||
// This is a direct subclass of NSView, no need to call superclass' drawRect:
|
||||
}
|
||||
|
||||
#[method(acceptsFirstResponder)]
|
||||
#[unsafe(method(acceptsFirstResponder))]
|
||||
fn accepts_first_responder(&self) -> bool {
|
||||
trace_scope!("acceptsFirstResponder");
|
||||
true
|
||||
}
|
||||
|
||||
// This is necessary to prevent a beefy terminal error on MacBook Pros:
|
||||
// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem
|
||||
// TODO: Add an API extension for using `NSTouchBar`
|
||||
#[method_id(touchBar)]
|
||||
// IMKInputSession [0x7fc573576ff0
|
||||
// presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self
|
||||
// textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error
|
||||
// Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this
|
||||
// process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from
|
||||
// this process.}, com.apple.inputmethod.EmojiFunctionRowItem TODO: Add an API
|
||||
// extension for using `NSTouchBar`
|
||||
#[unsafe(method_id(touchBar))]
|
||||
fn touch_bar(&self) -> Option<Retained<NSObject>> {
|
||||
trace_scope!("touchBar");
|
||||
None
|
||||
}
|
||||
|
||||
#[method(resetCursorRects)]
|
||||
#[unsafe(method(resetCursorRects))]
|
||||
fn reset_cursor_rects(&self) {
|
||||
trace_scope!("resetCursorRects");
|
||||
let bounds = self.bounds();
|
||||
@@ -242,13 +235,13 @@ declare_class!(
|
||||
}
|
||||
|
||||
unsafe impl NSTextInputClient for WinitView {
|
||||
#[method(hasMarkedText)]
|
||||
#[unsafe(method(hasMarkedText))]
|
||||
fn has_marked_text(&self) -> bool {
|
||||
trace_scope!("hasMarkedText");
|
||||
self.ivars().marked_text.borrow().length() > 0
|
||||
}
|
||||
|
||||
#[method(markedRange)]
|
||||
#[unsafe(method(markedRange))]
|
||||
fn marked_range(&self) -> NSRange {
|
||||
trace_scope!("markedRange");
|
||||
let length = self.ivars().marked_text.borrow().length();
|
||||
@@ -260,14 +253,14 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
#[method(selectedRange)]
|
||||
#[unsafe(method(selectedRange))]
|
||||
fn selected_range(&self) -> NSRange {
|
||||
trace_scope!("selectedRange");
|
||||
// Documented to return `{NSNotFound, 0}` if there is no selection.
|
||||
NSRange::new(NSNotFound as NSUInteger, 0)
|
||||
}
|
||||
|
||||
#[method(setMarkedText:selectedRange:replacementRange:)]
|
||||
#[unsafe(method(setMarkedText:selectedRange:replacementRange:))]
|
||||
fn set_marked_text(
|
||||
&self,
|
||||
string: &NSObject,
|
||||
@@ -277,23 +270,15 @@ declare_class!(
|
||||
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
|
||||
trace_scope!("setMarkedText:selectedRange:replacementRange:");
|
||||
|
||||
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSAttributedString = string.cast();
|
||||
let string = unsafe { &*string };
|
||||
(
|
||||
NSMutableAttributedString::from_attributed_nsstring(string),
|
||||
string.string(),
|
||||
)
|
||||
let (marked_text, string) = if let Some(string) =
|
||||
string.downcast_ref::<NSAttributedString>()
|
||||
{
|
||||
(NSMutableAttributedString::from_attributed_nsstring(string), string.string())
|
||||
} else if let Some(string) = string.downcast_ref::<NSString>() {
|
||||
(NSMutableAttributedString::from_nsstring(string), string.copy())
|
||||
} else {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSString = string.cast();
|
||||
let string = unsafe { &*string };
|
||||
(
|
||||
NSMutableAttributedString::from_nsstring(string),
|
||||
string.copy(),
|
||||
)
|
||||
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
panic!("unexpected text {string:?}")
|
||||
};
|
||||
|
||||
// Update marked text.
|
||||
@@ -329,7 +314,7 @@ declare_class!(
|
||||
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
|
||||
}
|
||||
|
||||
#[method(unmarkText)]
|
||||
#[unsafe(method(unmarkText))]
|
||||
fn unmark_text(&self) {
|
||||
trace_scope!("unmarkText");
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
@@ -346,13 +331,13 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
#[method_id(validAttributesForMarkedText)]
|
||||
#[unsafe(method_id(validAttributesForMarkedText))]
|
||||
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
|
||||
trace_scope!("validAttributesForMarkedText");
|
||||
NSArray::new()
|
||||
}
|
||||
|
||||
#[method_id(attributedSubstringForProposedRange:actualRange:)]
|
||||
#[unsafe(method_id(attributedSubstringForProposedRange:actualRange:))]
|
||||
fn attributed_substring_for_proposed_range(
|
||||
&self,
|
||||
_range: NSRange,
|
||||
@@ -362,45 +347,39 @@ declare_class!(
|
||||
None
|
||||
}
|
||||
|
||||
#[method(characterIndexForPoint:)]
|
||||
#[unsafe(method(characterIndexForPoint:))]
|
||||
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
|
||||
trace_scope!("characterIndexForPoint:");
|
||||
0
|
||||
}
|
||||
|
||||
#[method(firstRectForCharacterRange:actualRange:)]
|
||||
#[unsafe(method(firstRectForCharacterRange:actualRange:))]
|
||||
fn first_rect_for_character_range(
|
||||
&self,
|
||||
_range: NSRange,
|
||||
_actual_range: *mut NSRange,
|
||||
) -> NSRect {
|
||||
trace_scope!("firstRectForCharacterRange:actualRange:");
|
||||
let rect = NSRect::new(
|
||||
self.ivars().ime_position.get(),
|
||||
self.ivars().ime_size.get()
|
||||
);
|
||||
let rect = NSRect::new(self.ivars().ime_position.get(), self.ivars().ime_size.get());
|
||||
// Return value is expected to be in screen coordinates, so we need a conversion here
|
||||
self.window()
|
||||
.convertRectToScreen(self.convertRect_toView(rect, None))
|
||||
self.window().convertRectToScreen(self.convertRect_toView(rect, None))
|
||||
}
|
||||
|
||||
#[method(insertText:replacementRange:)]
|
||||
#[unsafe(method(insertText:replacementRange:))]
|
||||
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
|
||||
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
|
||||
trace_scope!("insertText:replacementRange:");
|
||||
|
||||
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
let string = if string.is_kind_of::<NSAttributedString>() {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSAttributedString = string.cast();
|
||||
unsafe { &*string }.string().to_string()
|
||||
let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() {
|
||||
string.string().to_string()
|
||||
} else if let Some(string) = string.downcast_ref::<NSString>() {
|
||||
string.to_string()
|
||||
} else {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSString = string.cast();
|
||||
unsafe { &*string }.to_string()
|
||||
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
panic!("unexpected text {string:?}")
|
||||
};
|
||||
|
||||
let is_control = string.chars().next().map_or(false, |c| c.is_control());
|
||||
let is_control = string.chars().next().is_some_and(|c| c.is_control());
|
||||
|
||||
// Commit only if we have marked text.
|
||||
if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control {
|
||||
@@ -410,14 +389,16 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
#[method(doCommandBySelector:)]
|
||||
fn do_command_by_selector(&self, _command: Sel) {
|
||||
// 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(method(doCommandBySelector:))]
|
||||
fn do_command_by_selector(&self, command: Sel) {
|
||||
trace_scope!("doCommandBySelector:");
|
||||
// We shouldn't forward any character from just committed text, since we'll end up sending
|
||||
// it twice with some IMEs like Korean one. We'll also always send `Enter` in that case,
|
||||
// which is not desired given it was used to confirm IME input.
|
||||
|
||||
// We shouldn't forward any character from just committed text, since we'll end up
|
||||
// sending it twice with some IMEs like Korean one. We'll also always send
|
||||
// `Enter` in that case, which is not desired given it was used to confirm
|
||||
// IME input.
|
||||
if self.ivars().ime_state.get() == ImeState::Committed {
|
||||
return;
|
||||
}
|
||||
@@ -429,11 +410,28 @@ declare_class!(
|
||||
// Leave preedit so that we also report the key-up for this key.
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
}
|
||||
|
||||
// Send command action to user if they requested it.
|
||||
let window_id = window_id(&self.window());
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
if let Some(handler) = app.macos_handler() {
|
||||
handler.standard_key_binding(
|
||||
event_loop,
|
||||
window_id,
|
||||
command.name().to_str().unwrap(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// The documentation for `-[NSTextInputClient doCommandBySelector:]` clearly states that
|
||||
// we should not be forwarding this event up the responder chain, so no calling `super`
|
||||
// here either.
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(keyDown:)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitView {
|
||||
#[unsafe(method(keyDown:))]
|
||||
fn key_down(&self, event: &NSEvent) {
|
||||
trace_scope!("keyDown:");
|
||||
{
|
||||
@@ -476,23 +474,23 @@ declare_class!(
|
||||
// Allow normal input after the commit.
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
true
|
||||
}
|
||||
},
|
||||
ImeState::Preedit => true,
|
||||
// `key_down` could result in preedit clear, so compare old and current state.
|
||||
_ => old_ime_state != self.ivars().ime_state.get(),
|
||||
};
|
||||
|
||||
if !had_ime_input || self.ivars().forward_key_to_app.get() {
|
||||
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
|
||||
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() });
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
device_id: None,
|
||||
event: key_event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[method(keyUp:)]
|
||||
#[unsafe(method(keyUp:))]
|
||||
fn key_up(&self, event: &NSEvent) {
|
||||
trace_scope!("keyUp:");
|
||||
|
||||
@@ -500,26 +498,23 @@ declare_class!(
|
||||
self.update_modifiers(&event, false);
|
||||
|
||||
// We want to send keyboard input when we are currently in the ground state.
|
||||
if matches!(
|
||||
self.ivars().ime_state.get(),
|
||||
ImeState::Ground | ImeState::Disabled
|
||||
) {
|
||||
if matches!(self.ivars().ime_state.get(), ImeState::Ground | ImeState::Disabled) {
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event: create_key_event(&event, false, false, None),
|
||||
device_id: None,
|
||||
event: create_key_event(&event, false, false),
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[method(flagsChanged:)]
|
||||
#[unsafe(method(flagsChanged:))]
|
||||
fn flags_changed(&self, event: &NSEvent) {
|
||||
trace_scope!("flagsChanged:");
|
||||
|
||||
self.update_modifiers(event, true);
|
||||
}
|
||||
|
||||
#[method(insertTab:)]
|
||||
#[unsafe(method(insertTab:))]
|
||||
fn insert_tab(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("insertTab:");
|
||||
let window = self.window();
|
||||
@@ -530,7 +525,7 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
#[method(insertBackTab:)]
|
||||
#[unsafe(method(insertBackTab:))]
|
||||
fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("insertBackTab:");
|
||||
let window = self.window();
|
||||
@@ -543,7 +538,7 @@ declare_class!(
|
||||
|
||||
// Allows us to receive Cmd-. (the shortcut for closing a dialog)
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
|
||||
#[method(cancelOperation:)]
|
||||
#[unsafe(method(cancelOperation:))]
|
||||
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
trace_scope!("cancelOperation:");
|
||||
@@ -553,10 +548,10 @@ declare_class!(
|
||||
.expect("could not find current event");
|
||||
|
||||
self.update_modifiers(&event, false);
|
||||
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
|
||||
let event = create_key_event(&event, true, unsafe { event.isARepeat() });
|
||||
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
device_id: None,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
@@ -573,42 +568,42 @@ declare_class!(
|
||||
//
|
||||
// See https://github.com/rust-windowing/winit/pull/1490 for history.
|
||||
|
||||
#[method(mouseDown:)]
|
||||
#[unsafe(method(mouseDown:))]
|
||||
fn mouse_down(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[method(mouseUp:)]
|
||||
#[unsafe(method(mouseUp:))]
|
||||
fn mouse_up(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseUp:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Released);
|
||||
}
|
||||
|
||||
#[method(rightMouseDown:)]
|
||||
#[unsafe(method(rightMouseDown:))]
|
||||
fn right_mouse_down(&self, event: &NSEvent) {
|
||||
trace_scope!("rightMouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[method(rightMouseUp:)]
|
||||
#[unsafe(method(rightMouseUp:))]
|
||||
fn right_mouse_up(&self, event: &NSEvent) {
|
||||
trace_scope!("rightMouseUp:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Released);
|
||||
}
|
||||
|
||||
#[method(otherMouseDown:)]
|
||||
#[unsafe(method(otherMouseDown:))]
|
||||
fn other_mouse_down(&self, event: &NSEvent) {
|
||||
trace_scope!("otherMouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[method(otherMouseUp:)]
|
||||
#[unsafe(method(otherMouseUp:))]
|
||||
fn other_mouse_up(&self, event: &NSEvent) {
|
||||
trace_scope!("otherMouseUp:");
|
||||
self.mouse_motion(event);
|
||||
@@ -617,44 +612,55 @@ declare_class!(
|
||||
|
||||
// No tracing on these because that would be overly verbose
|
||||
|
||||
#[method(mouseMoved:)]
|
||||
#[unsafe(method(mouseMoved:))]
|
||||
fn mouse_moved(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(mouseDragged:)]
|
||||
#[unsafe(method(mouseDragged:))]
|
||||
fn mouse_dragged(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(rightMouseDragged:)]
|
||||
#[unsafe(method(rightMouseDragged:))]
|
||||
fn right_mouse_dragged(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(otherMouseDragged:)]
|
||||
#[unsafe(method(otherMouseDragged:))]
|
||||
fn other_mouse_dragged(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(mouseEntered:)]
|
||||
fn mouse_entered(&self, _event: &NSEvent) {
|
||||
#[unsafe(method(mouseEntered:))]
|
||||
fn mouse_entered(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseEntered:");
|
||||
self.queue_event(WindowEvent::CursorEntered {
|
||||
device_id: DEVICE_ID,
|
||||
|
||||
let position = self.mouse_view_point(event).to_physical(self.scale_factor());
|
||||
|
||||
self.queue_event(WindowEvent::PointerEntered {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
position,
|
||||
kind: PointerKind::Mouse,
|
||||
});
|
||||
}
|
||||
|
||||
#[method(mouseExited:)]
|
||||
fn mouse_exited(&self, _event: &NSEvent) {
|
||||
#[unsafe(method(mouseExited:))]
|
||||
fn mouse_exited(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseExited:");
|
||||
|
||||
self.queue_event(WindowEvent::CursorLeft {
|
||||
device_id: DEVICE_ID,
|
||||
let position = self.mouse_view_point(event).to_physical(self.scale_factor());
|
||||
|
||||
self.queue_event(WindowEvent::PointerLeft {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
position: Some(position),
|
||||
kind: PointerKind::Mouse,
|
||||
});
|
||||
}
|
||||
|
||||
#[method(scrollWheel:)]
|
||||
#[unsafe(method(scrollWheel:))]
|
||||
fn scroll_wheel(&self, event: &NSEvent) {
|
||||
trace_scope!("scrollWheel:");
|
||||
|
||||
@@ -671,9 +677,9 @@ declare_class!(
|
||||
};
|
||||
|
||||
// The "momentum phase," if any, has higher priority than touch phase (the two should
|
||||
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum
|
||||
// phase is recorded (or rather, the started/ended cases of the momentum phase) then we
|
||||
// report the touch phase.
|
||||
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no
|
||||
// momentum phase is recorded (or rather, the started/ended cases of the
|
||||
// momentum phase) then we report the touch phase.
|
||||
#[allow(non_upper_case_globals)]
|
||||
let phase = match unsafe { event.momentumPhase() } {
|
||||
NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
|
||||
@@ -687,17 +693,13 @@ declare_class!(
|
||||
|
||||
self.update_modifiers(event, false);
|
||||
|
||||
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop|
|
||||
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseWheel { delta })
|
||||
);
|
||||
self.queue_event(WindowEvent::MouseWheel {
|
||||
device_id: DEVICE_ID,
|
||||
delta,
|
||||
phase,
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta })
|
||||
});
|
||||
self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase });
|
||||
}
|
||||
|
||||
#[method(magnifyWithEvent:)]
|
||||
#[unsafe(method(magnifyWithEvent:))]
|
||||
fn magnify_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("magnifyWithEvent:");
|
||||
|
||||
@@ -713,24 +715,22 @@ declare_class!(
|
||||
};
|
||||
|
||||
self.queue_event(WindowEvent::PinchGesture {
|
||||
device_id: DEVICE_ID,
|
||||
device_id: None,
|
||||
delta: unsafe { event.magnification() },
|
||||
phase,
|
||||
});
|
||||
}
|
||||
|
||||
#[method(smartMagnifyWithEvent:)]
|
||||
#[unsafe(method(smartMagnifyWithEvent:))]
|
||||
fn smart_magnify_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("smartMagnifyWithEvent:");
|
||||
|
||||
self.mouse_motion(event);
|
||||
|
||||
self.queue_event(WindowEvent::DoubleTapGesture {
|
||||
device_id: DEVICE_ID,
|
||||
});
|
||||
self.queue_event(WindowEvent::DoubleTapGesture { device_id: None });
|
||||
}
|
||||
|
||||
#[method(rotateWithEvent:)]
|
||||
#[unsafe(method(rotateWithEvent:))]
|
||||
fn rotate_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("rotateWithEvent:");
|
||||
|
||||
@@ -746,18 +746,18 @@ declare_class!(
|
||||
};
|
||||
|
||||
self.queue_event(WindowEvent::RotationGesture {
|
||||
device_id: DEVICE_ID,
|
||||
device_id: None,
|
||||
delta: unsafe { event.rotation() },
|
||||
phase,
|
||||
});
|
||||
}
|
||||
|
||||
#[method(pressureChangeWithEvent:)]
|
||||
#[unsafe(method(pressureChangeWithEvent:))]
|
||||
fn pressure_change_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("pressureChangeWithEvent:");
|
||||
|
||||
self.queue_event(WindowEvent::TouchpadPressure {
|
||||
device_id: DEVICE_ID,
|
||||
device_id: None,
|
||||
pressure: unsafe { event.pressure() },
|
||||
stage: unsafe { event.stage() } as i64,
|
||||
});
|
||||
@@ -766,13 +766,13 @@ declare_class!(
|
||||
// Allows us to receive Ctrl-Tab and Ctrl-Esc.
|
||||
// Note that this *doesn't* help with any missing Cmd inputs.
|
||||
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
|
||||
#[method(_wantsKeyDownForEvent:)]
|
||||
#[unsafe(method(_wantsKeyDownForEvent:))]
|
||||
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
|
||||
trace_scope!("_wantsKeyDownForEvent:");
|
||||
true
|
||||
}
|
||||
|
||||
#[method(acceptsFirstMouse:)]
|
||||
#[unsafe(method(acceptsFirstMouse:))]
|
||||
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
|
||||
trace_scope!("acceptsFirstMouse:");
|
||||
self.ivars().accepts_first_mouse
|
||||
@@ -782,14 +782,13 @@ declare_class!(
|
||||
|
||||
impl WinitView {
|
||||
pub(super) fn new(
|
||||
app_delegate: &ApplicationDelegate,
|
||||
window: &WinitWindow,
|
||||
app_state: &Rc<AppState>,
|
||||
accepts_first_mouse: bool,
|
||||
option_as_alt: OptionAsAlt,
|
||||
mtm: MainThreadMarker,
|
||||
) -> Retained<Self> {
|
||||
let mtm = MainThreadMarker::from(window);
|
||||
let this = mtm.alloc().set_ivars(ViewState {
|
||||
app_delegate: app_delegate.retain(),
|
||||
app_state: Rc::clone(app_state),
|
||||
cursor_state: Default::default(),
|
||||
ime_position: Default::default(),
|
||||
ime_size: Default::default(),
|
||||
@@ -802,39 +801,22 @@ impl WinitView {
|
||||
forward_key_to_app: Default::default(),
|
||||
marked_text: Default::default(),
|
||||
accepts_first_mouse,
|
||||
_ns_window: WeakId::new(&window.retain()),
|
||||
option_as_alt: Cell::new(option_as_alt),
|
||||
});
|
||||
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
|
||||
this.setPostsFrameChangedNotifications(true);
|
||||
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
|
||||
unsafe {
|
||||
notification_center.addObserver_selector_name_object(
|
||||
&this,
|
||||
sel!(frameDidChange:),
|
||||
Some(NSViewFrameDidChangeNotification),
|
||||
Some(&this),
|
||||
)
|
||||
}
|
||||
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
|
||||
|
||||
*this.ivars().input_source.borrow_mut() = this.current_input_source();
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn window(&self) -> Retained<WinitWindow> {
|
||||
// TODO: Simply use `window` property on `NSView`.
|
||||
// That only returns a window _after_ the view has been attached though!
|
||||
// (which is incompatible with `frameDidChange:`)
|
||||
//
|
||||
// unsafe { msg_send_id![self, window] }
|
||||
self.ivars()._ns_window.load().expect("view to have a window")
|
||||
fn window(&self) -> Retained<NSWindow> {
|
||||
(**self).window().expect("view must be installed in a window")
|
||||
}
|
||||
|
||||
fn queue_event(&self, event: WindowEvent) {
|
||||
let window_id = RootWindowId(self.window().id());
|
||||
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
|
||||
let window_id = window_id(&self.window());
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.window_event(event_loop, window_id, event);
|
||||
});
|
||||
}
|
||||
@@ -939,22 +921,34 @@ impl WinitView {
|
||||
let scancode = unsafe { ns_event.keyCode() };
|
||||
let physical_key = scancode_to_physicalkey(scancode as u32);
|
||||
|
||||
// We'll correct the `is_press` later.
|
||||
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
|
||||
|
||||
let key = code_to_key(physical_key, scancode);
|
||||
let logical_key = code_to_key(physical_key, scancode);
|
||||
// Ignore processing of unknown modifiers because we can't determine whether
|
||||
// it was pressed or release reliably.
|
||||
let Some(event_modifier) = key_to_modifier(&key) else {
|
||||
//
|
||||
// Furthermore, sometimes normal keys are reported inside flagsChanged:, such as
|
||||
// when holding Caps Lock while pressing another key, see:
|
||||
// https://github.com/alacritty/alacritty/issues/8268
|
||||
let Some(event_modifier) = key_to_modifier(&logical_key) else {
|
||||
break 'send_event;
|
||||
};
|
||||
event.physical_key = physical_key;
|
||||
event.logical_key = key.clone();
|
||||
event.location = code_to_location(physical_key);
|
||||
|
||||
let mut event = KeyEvent {
|
||||
location: code_to_location(physical_key),
|
||||
logical_key: logical_key.clone(),
|
||||
physical_key,
|
||||
repeat: false,
|
||||
// We'll correct this later.
|
||||
state: Pressed,
|
||||
text: None,
|
||||
text_with_all_modifiers: None,
|
||||
key_without_modifiers: logical_key.clone(),
|
||||
};
|
||||
|
||||
let location_mask = ModLocationMask::from_location(event.location);
|
||||
|
||||
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
|
||||
let phys_mod = phys_mod_state.entry(key).or_insert(ModLocationMask::empty());
|
||||
let phys_mod =
|
||||
phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty());
|
||||
|
||||
let is_active = current_modifiers.state().contains(event_modifier);
|
||||
let mut events = VecDeque::with_capacity(2);
|
||||
@@ -971,7 +965,7 @@ impl WinitView {
|
||||
event.location = KeyLocation::Left;
|
||||
event.physical_key = get_left_modifier_code(&event.logical_key).into();
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
device_id: None,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
@@ -980,7 +974,7 @@ impl WinitView {
|
||||
event.location = KeyLocation::Right;
|
||||
event.physical_key = get_right_modifier_code(&event.logical_key).into();
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
device_id: None,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
@@ -1011,7 +1005,7 @@ impl WinitView {
|
||||
}
|
||||
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
device_id: None,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
@@ -1033,20 +1027,22 @@ impl WinitView {
|
||||
}
|
||||
|
||||
fn mouse_click(&self, event: &NSEvent, button_state: ElementState) {
|
||||
let position = self.mouse_view_point(event).to_physical(self.scale_factor());
|
||||
let button = mouse_button(event);
|
||||
|
||||
self.update_modifiers(event, false);
|
||||
|
||||
self.queue_event(WindowEvent::MouseInput {
|
||||
device_id: DEVICE_ID,
|
||||
self.queue_event(WindowEvent::PointerButton {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
state: button_state,
|
||||
button,
|
||||
position,
|
||||
button: button.into(),
|
||||
});
|
||||
}
|
||||
|
||||
fn mouse_motion(&self, event: &NSEvent) {
|
||||
let window_point = unsafe { event.locationInWindow() };
|
||||
let view_point = self.convertPoint_fromView(window_point, None);
|
||||
let view_point = self.mouse_view_point(event);
|
||||
let frame = self.frame();
|
||||
|
||||
if view_point.x.is_sign_negative()
|
||||
@@ -1061,15 +1057,22 @@ impl WinitView {
|
||||
}
|
||||
}
|
||||
|
||||
let view_point = LogicalPosition::new(view_point.x, view_point.y);
|
||||
|
||||
self.update_modifiers(event, false);
|
||||
|
||||
self.queue_event(WindowEvent::CursorMoved {
|
||||
device_id: DEVICE_ID,
|
||||
self.queue_event(WindowEvent::PointerMoved {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
position: view_point.to_physical(self.scale_factor()),
|
||||
source: PointerSource::Mouse,
|
||||
});
|
||||
}
|
||||
|
||||
fn mouse_view_point(&self, event: &NSEvent) -> LogicalPosition<f64> {
|
||||
let window_point = unsafe { event.locationInWindow() };
|
||||
let view_point = self.convertPoint_fromView(window_point, None);
|
||||
|
||||
LogicalPosition::new(view_point.x, view_point.y)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the mouse button from the NSEvent.
|
||||
@@ -1100,7 +1103,7 @@ fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEven
|
||||
OptionAsAlt::Both if ev_mods.alt_key() => true,
|
||||
_ => false,
|
||||
} && !ev_mods.control_key()
|
||||
&& !ev_mods.super_key();
|
||||
&& !ev_mods.meta_key();
|
||||
|
||||
if ignore_alt_characters {
|
||||
let ns_chars = unsafe {
|
||||
|
||||
@@ -1,47 +1,44 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use dispatch2::MainThreadBound;
|
||||
use dpi::{Position, Size};
|
||||
use objc2::rc::{autoreleasepool, Retained};
|
||||
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{NSResponder, NSWindow};
|
||||
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
|
||||
use objc2::{define_class, MainThreadMarker, Message};
|
||||
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
|
||||
use objc2_foundation::NSObject;
|
||||
|
||||
use super::event_loop::ActiveEventLoop;
|
||||
use super::window_delegate::WindowDelegate;
|
||||
use crate::error::OsError as RootOsError;
|
||||
use crate::window::WindowAttributes;
|
||||
use crate::error::RequestError;
|
||||
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
|
||||
use crate::window::{
|
||||
Cursor, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes,
|
||||
WindowButtons, WindowId, WindowLevel,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Window {
|
||||
window: MainThreadBound<Retained<WinitWindow>>,
|
||||
window: MainThreadBound<Retained<NSWindow>>,
|
||||
/// The window only keeps a weak reference to this, so we must keep it around here.
|
||||
delegate: MainThreadBound<Retained<WindowDelegate>>,
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
self.window.get_on_main(|window| autoreleasepool(|_| window.close()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub(crate) fn new(
|
||||
window_target: &ActiveEventLoop,
|
||||
attributes: WindowAttributes,
|
||||
) -> Result<Self, RootOsError> {
|
||||
) -> Result<Self, RequestError> {
|
||||
let mtm = window_target.mtm;
|
||||
let delegate = autoreleasepool(|_| {
|
||||
WindowDelegate::new(window_target.app_delegate(), attributes, mtm)
|
||||
})?;
|
||||
let delegate =
|
||||
autoreleasepool(|_| WindowDelegate::new(&window_target.app_state, attributes, mtm))?;
|
||||
Ok(Window {
|
||||
window: MainThreadBound::new(delegate.window().retain(), mtm),
|
||||
delegate: MainThreadBound::new(delegate, mtm),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&WindowDelegate) + Send + 'static) {
|
||||
// For now, don't actually do queuing, since it may be less predictable
|
||||
self.maybe_wait_on_main(f)
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_wait_on_main<R: Send>(
|
||||
&self,
|
||||
f: impl FnOnce(&WindowDelegate) -> R + Send,
|
||||
@@ -49,7 +46,6 @@ impl Window {
|
||||
self.delegate.get_on_main(|delegate| f(delegate))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub(crate) fn raw_window_handle_rwh_06(
|
||||
&self,
|
||||
@@ -61,7 +57,6 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub(crate) fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
@@ -70,48 +65,295 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(pub usize);
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
// Restore the video mode.
|
||||
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_, _))) {
|
||||
self.set_fullscreen(None);
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub const fn dummy() -> Self {
|
||||
Self(0)
|
||||
self.window.get_on_main(|window| autoreleasepool(|_| window.close()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WindowId> for u64 {
|
||||
fn from(window_id: WindowId) -> Self {
|
||||
window_id.0 as u64
|
||||
impl rwh_06::HasDisplayHandle for Window {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = self.raw_display_handle_rwh_06()?;
|
||||
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for WindowId {
|
||||
fn from(raw_id: u64) -> Self {
|
||||
Self(raw_id as usize)
|
||||
impl rwh_06::HasWindowHandle for Window {
|
||||
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = self.raw_window_handle_rwh_06()?;
|
||||
unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) }
|
||||
}
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
impl CoreWindow for Window {
|
||||
fn id(&self) -> crate::window::WindowId {
|
||||
self.maybe_wait_on_main(|delegate| delegate.id())
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f64 {
|
||||
self.maybe_wait_on_main(|delegate| delegate.scale_factor())
|
||||
}
|
||||
|
||||
fn request_redraw(&self) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.request_redraw());
|
||||
}
|
||||
|
||||
fn pre_present_notify(&self) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.pre_present_notify());
|
||||
}
|
||||
|
||||
fn reset_dead_keys(&self) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys());
|
||||
}
|
||||
|
||||
fn surface_position(&self) -> dpi::PhysicalPosition<i32> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.surface_position())
|
||||
}
|
||||
|
||||
fn outer_position(&self) -> Result<dpi::PhysicalPosition<i32>, RequestError> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.outer_position())
|
||||
}
|
||||
|
||||
fn set_outer_position(&self, position: Position) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_outer_position(position));
|
||||
}
|
||||
|
||||
fn surface_size(&self) -> dpi::PhysicalSize<u32> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.surface_size())
|
||||
}
|
||||
|
||||
fn request_surface_size(&self, size: Size) -> Option<dpi::PhysicalSize<u32>> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.request_surface_size(size))
|
||||
}
|
||||
|
||||
fn outer_size(&self) -> dpi::PhysicalSize<u32> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.outer_size())
|
||||
}
|
||||
|
||||
fn safe_area(&self) -> dpi::PhysicalInsets<u32> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.safe_area())
|
||||
}
|
||||
|
||||
fn set_min_surface_size(&self, min_size: Option<Size>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size))
|
||||
}
|
||||
|
||||
fn set_max_surface_size(&self, max_size: Option<Size>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_max_surface_size(max_size));
|
||||
}
|
||||
|
||||
fn surface_resize_increments(&self) -> Option<dpi::PhysicalSize<u32>> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.surface_resize_increments())
|
||||
}
|
||||
|
||||
fn set_surface_resize_increments(&self, increments: Option<Size>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_surface_resize_increments(increments));
|
||||
}
|
||||
|
||||
fn set_title(&self, title: &str) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_title(title));
|
||||
}
|
||||
|
||||
fn set_transparent(&self, transparent: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_transparent(transparent));
|
||||
}
|
||||
|
||||
fn set_blur(&self, blur: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_blur(blur));
|
||||
}
|
||||
|
||||
fn set_visible(&self, visible: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_visible(visible));
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> Option<bool> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.is_visible())
|
||||
}
|
||||
|
||||
fn set_resizable(&self, resizable: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_resizable(resizable))
|
||||
}
|
||||
|
||||
fn is_resizable(&self) -> bool {
|
||||
self.maybe_wait_on_main(|delegate| delegate.is_resizable())
|
||||
}
|
||||
|
||||
fn set_enabled_buttons(&self, buttons: WindowButtons) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_enabled_buttons(buttons))
|
||||
}
|
||||
|
||||
fn enabled_buttons(&self) -> WindowButtons {
|
||||
self.maybe_wait_on_main(|delegate| delegate.enabled_buttons())
|
||||
}
|
||||
|
||||
fn set_minimized(&self, minimized: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_minimized(minimized));
|
||||
}
|
||||
|
||||
fn is_minimized(&self) -> Option<bool> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.is_minimized())
|
||||
}
|
||||
|
||||
fn set_maximized(&self, maximized: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_maximized(maximized));
|
||||
}
|
||||
|
||||
fn is_maximized(&self) -> bool {
|
||||
self.maybe_wait_on_main(|delegate| delegate.is_maximized())
|
||||
}
|
||||
|
||||
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen))
|
||||
}
|
||||
|
||||
fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.fullscreen())
|
||||
}
|
||||
|
||||
fn set_decorations(&self, decorations: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_decorations(decorations));
|
||||
}
|
||||
|
||||
fn is_decorated(&self) -> bool {
|
||||
self.maybe_wait_on_main(|delegate| delegate.is_decorated())
|
||||
}
|
||||
|
||||
fn set_window_level(&self, level: WindowLevel) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_window_level(level));
|
||||
}
|
||||
|
||||
fn set_window_icon(&self, window_icon: Option<Icon>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon));
|
||||
}
|
||||
|
||||
fn set_ime_cursor_area(&self, position: Position, size: Size) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_ime_cursor_area(position, size));
|
||||
}
|
||||
|
||||
fn set_ime_allowed(&self, allowed: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_ime_allowed(allowed));
|
||||
}
|
||||
|
||||
fn set_ime_purpose(&self, purpose: ImePurpose) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_ime_purpose(purpose));
|
||||
}
|
||||
|
||||
fn focus_window(&self) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.focus_window());
|
||||
}
|
||||
|
||||
fn has_focus(&self) -> bool {
|
||||
self.maybe_wait_on_main(|delegate| delegate.has_focus())
|
||||
}
|
||||
|
||||
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.request_user_attention(request_type));
|
||||
}
|
||||
|
||||
fn set_theme(&self, theme: Option<Theme>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_theme(theme));
|
||||
}
|
||||
|
||||
fn theme(&self) -> Option<Theme> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.theme())
|
||||
}
|
||||
|
||||
fn set_content_protected(&self, protected: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_content_protected(protected));
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
self.maybe_wait_on_main(|delegate| delegate.title())
|
||||
}
|
||||
|
||||
fn set_cursor(&self, cursor: Cursor) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_cursor(cursor));
|
||||
}
|
||||
|
||||
fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position))
|
||||
}
|
||||
|
||||
fn set_cursor_grab(&self, mode: crate::window::CursorGrabMode) -> Result<(), RequestError> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode))
|
||||
}
|
||||
|
||||
fn set_cursor_visible(&self, visible: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_cursor_visible(visible))
|
||||
}
|
||||
|
||||
fn drag_window(&self) -> Result<(), RequestError> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.drag_window())
|
||||
}
|
||||
|
||||
fn drag_resize_window(
|
||||
&self,
|
||||
direction: crate::window::ResizeDirection,
|
||||
) -> Result<(), RequestError> {
|
||||
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?)
|
||||
}
|
||||
|
||||
fn show_window_menu(&self, position: Position) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.show_window_menu(position))
|
||||
}
|
||||
|
||||
fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_cursor_hittest(hittest));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
|
||||
self.maybe_wait_on_main(|delegate| {
|
||||
delegate.current_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
|
||||
})
|
||||
}
|
||||
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
|
||||
self.maybe_wait_on_main(|delegate| {
|
||||
Box::new(
|
||||
delegate
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
|
||||
self.maybe_wait_on_main(|delegate| {
|
||||
delegate.primary_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
|
||||
})
|
||||
}
|
||||
|
||||
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
|
||||
self
|
||||
}
|
||||
|
||||
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
define_class!(
|
||||
#[unsafe(super(NSWindow, NSResponder, NSObject))]
|
||||
#[name = "WinitWindow"]
|
||||
#[derive(Debug)]
|
||||
pub struct WinitWindow;
|
||||
|
||||
unsafe impl ClassType for WinitWindow {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSWindow;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitWindow";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitWindow {}
|
||||
|
||||
unsafe impl WinitWindow {
|
||||
#[method(canBecomeMainWindow)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitWindow {
|
||||
#[unsafe(method(canBecomeMainWindow))]
|
||||
fn can_become_main_window(&self) -> bool {
|
||||
trace_scope!("canBecomeMainWindow");
|
||||
true
|
||||
}
|
||||
|
||||
#[method(canBecomeKeyWindow)]
|
||||
#[unsafe(method(canBecomeKeyWindow))]
|
||||
fn can_become_key_window(&self) -> bool {
|
||||
trace_scope!("canBecomeKeyWindow");
|
||||
true
|
||||
@@ -119,8 +361,24 @@ declare_class!(
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitWindow {
|
||||
pub(super) fn id(&self) -> WindowId {
|
||||
WindowId(self as *const Self as usize)
|
||||
define_class!(
|
||||
#[unsafe(super(NSPanel, NSWindow, NSResponder, NSObject))]
|
||||
#[name = "WinitPanel"]
|
||||
#[derive(Debug)]
|
||||
pub struct WinitPanel;
|
||||
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitPanel {
|
||||
// although NSPanel can become key window
|
||||
// it doesn't if window doesn't have NSWindowStyleMask::Titled
|
||||
#[unsafe(method(canBecomeKeyWindow))]
|
||||
fn can_become_key_window(&self) -> bool {
|
||||
trace_scope!("canBecomeKeyWindow");
|
||||
true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
pub(super) fn window_id(window: &NSWindow) -> WindowId {
|
||||
WindowId::from_raw(window as *const _ as usize)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,15 +2,16 @@ use std::cell::RefCell;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::platform_impl::ActiveEventLoop;
|
||||
|
||||
/// A helper type for storing a reference to `ApplicationHandler`, allowing interior mutable access
|
||||
/// to it within the execution of a closure.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct EventHandler {
|
||||
/// This can be in the following states:
|
||||
/// - Not registered by the event loop (None).
|
||||
/// - Not registered by the event loop, or terminated (None).
|
||||
/// - Present (Some(handler)).
|
||||
/// - Currently executing the handler / in use (RefCell borrowed).
|
||||
inner: RefCell<Option<&'static mut dyn ApplicationHandler>>,
|
||||
inner: RefCell<Option<Box<dyn ApplicationHandler + 'static>>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for EventHandler {
|
||||
@@ -36,7 +37,7 @@ impl EventHandler {
|
||||
/// from within the closure.
|
||||
pub(crate) fn set<'handler, R>(
|
||||
&self,
|
||||
app: &'handler mut dyn ApplicationHandler,
|
||||
app: Box<dyn ApplicationHandler + 'handler>,
|
||||
closure: impl FnOnce() -> R,
|
||||
) -> R {
|
||||
// SAFETY: We extend the lifetime of the handler here so that we can
|
||||
@@ -47,8 +48,8 @@ impl EventHandler {
|
||||
// extended beyond `'handler`.
|
||||
let handler = unsafe {
|
||||
mem::transmute::<
|
||||
&'handler mut dyn ApplicationHandler,
|
||||
&'static mut dyn ApplicationHandler,
|
||||
Box<dyn ApplicationHandler + 'handler>,
|
||||
Box<dyn ApplicationHandler + 'static>,
|
||||
>(app)
|
||||
};
|
||||
|
||||
@@ -70,10 +71,13 @@ impl EventHandler {
|
||||
fn drop(&mut self) {
|
||||
match self.0.inner.try_borrow_mut().as_deref_mut() {
|
||||
Ok(data @ Some(_)) => {
|
||||
*data = None;
|
||||
let handler = data.take();
|
||||
// Explicitly `Drop` the application handler.
|
||||
drop(handler);
|
||||
},
|
||||
Ok(None) => {
|
||||
tracing::error!("tried to clear handler, but no handler was set");
|
||||
// Allowed, happens if the handler was cleared manually
|
||||
// elsewhere (such as in `applicationWillTerminate:`).
|
||||
},
|
||||
Err(_) => {
|
||||
// Note: This is not expected to ever happen, this
|
||||
@@ -100,6 +104,7 @@ impl EventHandler {
|
||||
// soundness.
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) fn in_use(&self) -> bool {
|
||||
self.inner.try_borrow().is_err()
|
||||
}
|
||||
@@ -108,23 +113,19 @@ impl EventHandler {
|
||||
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
|
||||
}
|
||||
|
||||
pub(crate) fn handle(
|
||||
&self,
|
||||
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop),
|
||||
event_loop: &ActiveEventLoop,
|
||||
) {
|
||||
pub(crate) fn handle(&self, callback: impl FnOnce(&mut (dyn ApplicationHandler + '_))) {
|
||||
match self.inner.try_borrow_mut().as_deref_mut() {
|
||||
Ok(Some(user_app)) => {
|
||||
Ok(Some(ref mut user_app)) => {
|
||||
// It is important that we keep the reference borrowed here,
|
||||
// so that `in_use` can properly detect that the handler is
|
||||
// still in use.
|
||||
//
|
||||
// If the handler unwinds, the `RefMut` will ensure that the
|
||||
// handler is no longer borrowed.
|
||||
callback(*user_app, event_loop);
|
||||
callback(&mut **user_app);
|
||||
},
|
||||
Ok(None) => {
|
||||
// `NSApplication`, our app delegate and this handler are all
|
||||
// `NSApplication`, our app state and this handler are all
|
||||
// global state and so it's not impossible that we could get
|
||||
// an event after the application has exited the `EventLoop`.
|
||||
tracing::error!("tried to run event handler, but no handler was set");
|
||||
@@ -135,4 +136,21 @@ impl EventHandler {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn terminate(&self) {
|
||||
match self.inner.try_borrow_mut().as_deref_mut() {
|
||||
Ok(data @ Some(_)) => {
|
||||
let handler = data.take();
|
||||
// Explicitly `Drop` the application handler.
|
||||
drop(handler);
|
||||
},
|
||||
Ok(None) => {
|
||||
// When terminating, we expect the application handler to still be registered.
|
||||
tracing::error!("tried to clear handler, but no handler was set");
|
||||
},
|
||||
Err(_) => {
|
||||
panic!("tried to clear handler while an event is currently being handled");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
124
src/platform_impl/apple/event_loop_proxy.rs
Normal file
124
src/platform_impl/apple/event_loop_proxy.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::Arc;
|
||||
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{
|
||||
kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoop, CFRunLoopSource, CFRunLoopSourceContext,
|
||||
};
|
||||
|
||||
use crate::event_loop::EventLoopProxyProvider;
|
||||
|
||||
/// A waker that signals a `CFRunLoopSource` on the main thread.
|
||||
///
|
||||
/// We use this to integrate with the system as cleanly as possible (instead of e.g. keeping an
|
||||
/// atomic around that we check on each iteration of the event loop).
|
||||
///
|
||||
/// See <https://developer.apple.com/documentation/corefoundation/cfrunloopsource?language=objc>.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct EventLoopProxy {
|
||||
source: CFRetained<CFRunLoopSource>,
|
||||
/// Cached value of `CFRunLoopGetMain`.
|
||||
main_loop: CFRetained<CFRunLoop>,
|
||||
}
|
||||
|
||||
// FIXME(madsmtm): Mark `CFRunLoopSource` + `CFRunLoop` as `Send` + `Sync`.
|
||||
unsafe impl Send for EventLoopProxy {}
|
||||
unsafe impl Sync for EventLoopProxy {}
|
||||
|
||||
impl EventLoopProxy {
|
||||
/// Create a new proxy, registering it to be performed on the main thread.
|
||||
///
|
||||
/// The provided closure should call `proxy_wake_up` on the application.
|
||||
pub(crate) fn new<F: Fn() + 'static>(mtm: MainThreadMarker, signaller: F) -> Self {
|
||||
// We use an `Arc` here to make sure that the reference-counting of the signal container is
|
||||
// atomic (`Retained`/`CFRetained` would be valid alternatives too).
|
||||
let signaller = Arc::new(signaller);
|
||||
|
||||
unsafe extern "C-unwind" fn retain<F>(info: *const c_void) -> *const c_void {
|
||||
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
|
||||
unsafe { Arc::increment_strong_count(info.cast::<F>()) };
|
||||
info
|
||||
}
|
||||
unsafe extern "C-unwind" fn release<F>(info: *const c_void) {
|
||||
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
|
||||
unsafe { Arc::decrement_strong_count(info.cast::<F>()) };
|
||||
}
|
||||
|
||||
// Pointer equality / hashing.
|
||||
extern "C-unwind" fn equal(info1: *const c_void, info2: *const c_void) -> u8 {
|
||||
(info1 == info2) as u8
|
||||
}
|
||||
extern "C-unwind" fn hash(info: *const c_void) -> usize {
|
||||
info as usize
|
||||
}
|
||||
|
||||
// Call the provided closure.
|
||||
unsafe extern "C-unwind" fn perform<F: Fn()>(info: *mut c_void) {
|
||||
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
|
||||
let signaller = unsafe { &*info.cast::<F>() };
|
||||
(signaller)();
|
||||
}
|
||||
|
||||
// Fire last.
|
||||
let order = CFIndex::MAX - 1;
|
||||
|
||||
// This is marked `mut` to match the signature of `CFRunLoopSourceCreate`, but the
|
||||
// information is copied, and not actually mutated.
|
||||
let mut context = CFRunLoopSourceContext {
|
||||
version: 0,
|
||||
// This is retained on creation.
|
||||
info: Arc::as_ptr(&signaller) as *mut c_void,
|
||||
retain: Some(retain::<F>),
|
||||
release: Some(release::<F>),
|
||||
copyDescription: None,
|
||||
equal: Some(equal),
|
||||
hash: Some(hash),
|
||||
schedule: None,
|
||||
cancel: None,
|
||||
perform: Some(perform::<F>),
|
||||
};
|
||||
|
||||
// SAFETY: The normal callbacks are thread-safe (`retain`/`release` use atomics, and
|
||||
// `equal`/`hash` only access a pointer).
|
||||
//
|
||||
// Note that the `perform` callback isn't thread-safe (we don't have `F: Send + Sync`), but
|
||||
// that's okay, since we are on the main thread, and the source is only added to the main
|
||||
// run loop (below), and hence only performed there.
|
||||
//
|
||||
// Keeping the closure alive beyond this scope is fine, because `F: 'static`.
|
||||
let source = unsafe {
|
||||
let _ = mtm;
|
||||
CFRunLoopSource::new(None, order, &mut context).unwrap()
|
||||
};
|
||||
|
||||
// Register the source to be performed on the main thread.
|
||||
let main_loop = CFRunLoop::main().unwrap();
|
||||
unsafe { main_loop.add_source(Some(&source), kCFRunLoopCommonModes) };
|
||||
|
||||
Self { source, main_loop }
|
||||
}
|
||||
|
||||
// FIXME(madsmtm): Use this on macOS too.
|
||||
// More difficult there, since the user can re-start the event loop.
|
||||
#[cfg_attr(target_os = "macos", allow(dead_code))]
|
||||
pub(crate) fn invalidate(&self) {
|
||||
// NOTE: We do NOT fire this on `Drop`, since we want the proxy to be cloneable, such that
|
||||
// we only need to register a single source even if there's multiple proxies in use.
|
||||
self.source.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxyProvider for EventLoopProxy {
|
||||
fn wake_up(&self) {
|
||||
// Signal the source, which ends up later invoking `perform` on the main thread.
|
||||
//
|
||||
// Multiple signals in quick succession are automatically coalesced into a single signal.
|
||||
self.source.signal();
|
||||
|
||||
// Let the main thread know there's a new event.
|
||||
//
|
||||
// This is required since we may be (probably are) running on a different thread, and the
|
||||
// main loop may be sleeping (and `CFRunLoopSourceSignal` won't wake it).
|
||||
self.main_loop.wake_up();
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,15 @@
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod appkit;
|
||||
mod event_handler;
|
||||
mod event_loop_proxy;
|
||||
mod notification_center;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
mod uikit;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use self::appkit::*;
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub use self::uikit::*;
|
||||
|
||||
30
src/platform_impl/apple/notification_center.rs
Normal file
30
src/platform_impl/apple/notification_center.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use block2::RcBlock;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2_foundation::{
|
||||
NSNotification, NSNotificationCenter, NSNotificationName, NSObjectProtocol,
|
||||
};
|
||||
|
||||
/// Observe the given notification.
|
||||
///
|
||||
/// This is used in Winit as an alternative to declaring an application delegate, as we want to
|
||||
/// give the user full control over those.
|
||||
pub fn create_observer(
|
||||
center: &NSNotificationCenter,
|
||||
name: &NSNotificationName,
|
||||
handler: impl Fn(&NSNotification) + 'static,
|
||||
) -> Retained<ProtocolObject<dyn NSObjectProtocol>> {
|
||||
let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
|
||||
handler(unsafe { notification.as_ref() });
|
||||
});
|
||||
unsafe {
|
||||
center.addObserverForName_object_queue_usingBlock(
|
||||
Some(name),
|
||||
None, // No sender filter
|
||||
None, // No queue, run on posting thread (i.e. main thread)
|
||||
&block,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
|
||||
use objc2_foundation::{MainThreadMarker, NSObject};
|
||||
use objc2_ui_kit::UIApplication;
|
||||
|
||||
use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper};
|
||||
use crate::event::Event;
|
||||
|
||||
declare_class!(
|
||||
pub struct AppDelegate;
|
||||
|
||||
unsafe impl ClassType for AppDelegate {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
const NAME: &'static str = "WinitApplicationDelegate";
|
||||
}
|
||||
|
||||
impl DeclaredClass for AppDelegate {}
|
||||
|
||||
// UIApplicationDelegate protocol
|
||||
unsafe impl AppDelegate {
|
||||
#[method(application:didFinishLaunchingWithOptions:)]
|
||||
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
|
||||
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
|
||||
true
|
||||
}
|
||||
|
||||
#[method(applicationDidBecomeActive:)]
|
||||
fn did_become_active(&self, _application: &UIApplication) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
|
||||
}
|
||||
|
||||
#[method(applicationWillResignActive:)]
|
||||
fn will_resign_active(&self, _application: &UIApplication) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
|
||||
}
|
||||
|
||||
#[method(applicationWillEnterForeground:)]
|
||||
fn will_enter_foreground(&self, application: &UIApplication) {
|
||||
send_occluded_event_for_all_windows(application, false);
|
||||
}
|
||||
|
||||
#[method(applicationDidEnterBackground:)]
|
||||
fn did_enter_background(&self, application: &UIApplication) {
|
||||
send_occluded_event_for_all_windows(application, true);
|
||||
}
|
||||
|
||||
#[method(applicationWillTerminate:)]
|
||||
fn will_terminate(&self, application: &UIApplication) {
|
||||
app_state::terminated(application);
|
||||
}
|
||||
|
||||
#[method(applicationDidReceiveMemoryWarning:)]
|
||||
fn did_receive_memory_warning(&self, _application: &UIApplication) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning))
|
||||
}
|
||||
}
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,34 +1,34 @@
|
||||
use std::any::Any;
|
||||
use std::ffi::{c_char, c_int, c_void};
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use core_foundation::base::{CFIndex, CFRelease};
|
||||
use core_foundation::runloop::{
|
||||
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
|
||||
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
|
||||
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
|
||||
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{msg_send_id, ClassType};
|
||||
use objc2_foundation::{MainThreadMarker, NSString};
|
||||
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIScreen};
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2::{msg_send, ClassType, MainThreadMarker};
|
||||
use objc2_core_foundation::{
|
||||
kCFRunLoopDefaultMode, CFIndex, CFRunLoop, CFRunLoopActivity, CFRunLoopObserver,
|
||||
};
|
||||
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
|
||||
use objc2_ui_kit::{
|
||||
UIApplication, UIApplicationDidBecomeActiveNotification,
|
||||
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
|
||||
UIApplicationDidReceiveMemoryWarningNotification, UIApplicationWillEnterForegroundNotification,
|
||||
UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen,
|
||||
};
|
||||
use rwh_06::HasDisplayHandle;
|
||||
|
||||
use super::app_delegate::AppDelegate;
|
||||
use super::app_state::{AppState, EventLoopHandler};
|
||||
use super::super::notification_center::create_observer;
|
||||
use super::app_state::{send_occluded_event_for_all_windows, AppState};
|
||||
use super::{app_state, monitor, MonitorHandle};
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError};
|
||||
use crate::event::Event;
|
||||
use crate::error::{EventLoopError, NotSupportedError, RequestError};
|
||||
use crate::event_loop::{
|
||||
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
|
||||
EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as RootOwnedDisplayHandle,
|
||||
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
||||
};
|
||||
use crate::monitor::MonitorHandle as RootMonitorHandle;
|
||||
use crate::monitor::MonitorHandle as CoreMonitorHandle;
|
||||
use crate::platform_impl::Window;
|
||||
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window as RootWindow};
|
||||
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window as CoreWindow};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ActiveEventLoop {
|
||||
@@ -36,34 +36,36 @@ pub(crate) struct ActiveEventLoop {
|
||||
}
|
||||
|
||||
impl RootActiveEventLoop for ActiveEventLoop {
|
||||
fn create_proxy(&self) -> crate::event_loop::EventLoopProxy {
|
||||
let event_loop_proxy = EventLoopProxy::new(AppState::get_mut(self.mtm).proxy_wake_up());
|
||||
RootEventLoopProxy { event_loop_proxy }
|
||||
fn create_proxy(&self) -> CoreEventLoopProxy {
|
||||
CoreEventLoopProxy::new(AppState::get_mut(self.mtm).event_loop_proxy().clone())
|
||||
}
|
||||
|
||||
fn create_window(
|
||||
&self,
|
||||
window_attributes: crate::window::WindowAttributes,
|
||||
) -> Result<RootWindow, OsError> {
|
||||
let window = Window::new(self, window_attributes)?;
|
||||
Ok(RootWindow { window })
|
||||
) -> Result<Box<dyn CoreWindow>, RequestError> {
|
||||
Ok(Box::new(Window::new(self, window_attributes)?))
|
||||
}
|
||||
|
||||
fn create_custom_cursor(
|
||||
&self,
|
||||
_source: CustomCursorSource,
|
||||
) -> Result<CustomCursor, ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
) -> Result<CustomCursor, RequestError> {
|
||||
Err(NotSupportedError::new("create_custom_cursor is not supported").into())
|
||||
}
|
||||
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
|
||||
Box::new(monitor::uiscreens(self.mtm).into_iter().map(|inner| RootMonitorHandle { inner }))
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
|
||||
Box::new(
|
||||
monitor::uiscreens(self.mtm)
|
||||
.into_iter()
|
||||
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
|
||||
)
|
||||
}
|
||||
|
||||
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
|
||||
#[allow(deprecated)]
|
||||
let monitor = MonitorHandle::new(UIScreen::mainScreen(self.mtm));
|
||||
Some(RootMonitorHandle { inner: monitor })
|
||||
Some(CoreMonitorHandle(Arc::new(monitor)))
|
||||
}
|
||||
|
||||
fn listen_device_events(&self, _allowed: DeviceEvents) {}
|
||||
@@ -90,21 +92,15 @@ impl RootActiveEventLoop for ActiveEventLoop {
|
||||
false
|
||||
}
|
||||
|
||||
fn owned_display_handle(&self) -> RootOwnedDisplayHandle {
|
||||
RootOwnedDisplayHandle { platform: OwnedDisplayHandle }
|
||||
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
|
||||
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new());
|
||||
@@ -115,45 +111,29 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub(crate) struct OwnedDisplayHandle;
|
||||
|
||||
impl OwnedDisplayHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
||||
Ok(rwh_06::UiKitDisplayHandle::new().into())
|
||||
}
|
||||
}
|
||||
|
||||
fn map_user_event<'a, A: ApplicationHandler + 'a>(
|
||||
mut app: A,
|
||||
proxy_wake_up: Arc<AtomicBool>,
|
||||
) -> impl FnMut(Event, &dyn RootActiveEventLoop) + 'a {
|
||||
move |event, window_target| match event {
|
||||
Event::NewEvents(cause) => app.new_events(window_target, cause),
|
||||
Event::WindowEvent { window_id, event } => {
|
||||
app.window_event(window_target, window_id, event)
|
||||
},
|
||||
Event::DeviceEvent { device_id, event } => {
|
||||
app.device_event(window_target, device_id, event)
|
||||
},
|
||||
Event::UserWakeUp => {
|
||||
if proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
|
||||
app.proxy_wake_up(window_target);
|
||||
}
|
||||
},
|
||||
Event::Suspended => app.suspended(window_target),
|
||||
Event::Resumed => app.resumed(window_target),
|
||||
Event::CreateSurfaces => app.can_create_surfaces(window_target),
|
||||
Event::AboutToWait => app.about_to_wait(window_target),
|
||||
Event::LoopExiting => app.exiting(window_target),
|
||||
Event::MemoryWarning => app.memory_warning(window_target),
|
||||
impl HasDisplayHandle for OwnedDisplayHandle {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new());
|
||||
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoop {
|
||||
mtm: MainThreadMarker,
|
||||
window_target: ActiveEventLoop,
|
||||
|
||||
// Since iOS 9.0, we no longer need to remove the observers before they are deallocated; the
|
||||
// system instead cleans it up next time it would have posted a notification to it.
|
||||
//
|
||||
// Though we do still need to keep the observers around to prevent them from being deallocated.
|
||||
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_did_become_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_resign_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_enter_foreground_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_did_enter_background_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -178,12 +158,92 @@ impl EventLoop {
|
||||
// this line sets up the main run loop before `UIApplicationMain`
|
||||
setup_control_flow_observers();
|
||||
|
||||
Ok(EventLoop { mtm, window_target: ActiveEventLoop { mtm } })
|
||||
let center = unsafe { NSNotificationCenter::defaultCenter() };
|
||||
|
||||
let _did_finish_launching_observer = create_observer(
|
||||
¢er,
|
||||
// `application:didFinishLaunchingWithOptions:`
|
||||
unsafe { UIApplicationDidFinishLaunchingNotification },
|
||||
move |_| {
|
||||
app_state::did_finish_launching(mtm);
|
||||
},
|
||||
);
|
||||
let _did_become_active_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationDidBecomeActive:`
|
||||
unsafe { UIApplicationDidBecomeActiveNotification },
|
||||
move |_| app_state::handle_resumed(mtm),
|
||||
);
|
||||
let _will_resign_active_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationWillResignActive:`
|
||||
unsafe { UIApplicationWillResignActiveNotification },
|
||||
move |_| app_state::handle_suspended(mtm),
|
||||
);
|
||||
let _will_enter_foreground_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationWillEnterForeground:`
|
||||
unsafe { UIApplicationWillEnterForegroundNotification },
|
||||
move |notification| {
|
||||
let app = unsafe { notification.object() }.expect(
|
||||
"UIApplicationWillEnterForegroundNotification to have application object",
|
||||
);
|
||||
// The `object` in `UIApplicationWillEnterForegroundNotification` is documented to
|
||||
// be `UIApplication`.
|
||||
let app = app.downcast::<UIApplication>().unwrap();
|
||||
send_occluded_event_for_all_windows(&app, false);
|
||||
},
|
||||
);
|
||||
let _did_enter_background_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationDidEnterBackground:`
|
||||
unsafe { UIApplicationDidEnterBackgroundNotification },
|
||||
move |notification| {
|
||||
let app = unsafe { notification.object() }.expect(
|
||||
"UIApplicationDidEnterBackgroundNotification to have application object",
|
||||
);
|
||||
// The `object` in `UIApplicationDidEnterBackgroundNotification` is documented to be
|
||||
// `UIApplication`.
|
||||
let app = app.downcast::<UIApplication>().unwrap();
|
||||
send_occluded_event_for_all_windows(&app, true);
|
||||
},
|
||||
);
|
||||
let _will_terminate_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationWillTerminate:`
|
||||
unsafe { UIApplicationWillTerminateNotification },
|
||||
move |notification| {
|
||||
let app = unsafe { notification.object() }
|
||||
.expect("UIApplicationWillTerminateNotification to have application object");
|
||||
// The `object` in `UIApplicationWillTerminateNotification` is (somewhat) documented
|
||||
// to be `UIApplication`.
|
||||
let app = app.downcast::<UIApplication>().unwrap();
|
||||
app_state::terminated(&app);
|
||||
},
|
||||
);
|
||||
let _did_receive_memory_warning_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationDidReceiveMemoryWarning:`
|
||||
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
|
||||
move |_| app_state::handle_memory_warning(mtm),
|
||||
);
|
||||
|
||||
Ok(EventLoop {
|
||||
mtm,
|
||||
window_target: ActiveEventLoop { mtm },
|
||||
_did_finish_launching_observer,
|
||||
_did_become_active_observer,
|
||||
_will_resign_active_observer,
|
||||
_will_enter_foreground_observer,
|
||||
_did_enter_background_observer,
|
||||
_will_terminate_observer,
|
||||
_did_receive_memory_warning_observer,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
|
||||
let application: Option<Retained<UIApplication>> =
|
||||
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
|
||||
unsafe { msg_send![UIApplication::class(), sharedApplication] };
|
||||
assert!(
|
||||
application.is_none(),
|
||||
"\
|
||||
@@ -191,37 +251,9 @@ impl EventLoop {
|
||||
`EventLoop::run_app` calls `UIApplicationMain` on iOS",
|
||||
);
|
||||
|
||||
let handler = map_user_event(app, AppState::get_mut(self.mtm).proxy_wake_up());
|
||||
|
||||
let handler = unsafe {
|
||||
std::mem::transmute::<
|
||||
Box<dyn FnMut(Event, &dyn RootActiveEventLoop)>,
|
||||
Box<dyn FnMut(Event, &dyn RootActiveEventLoop)>,
|
||||
>(Box::new(handler))
|
||||
};
|
||||
|
||||
let handler = EventLoopHandler { handler, event_loop: self.window_target };
|
||||
|
||||
app_state::will_launch(self.mtm, handler);
|
||||
|
||||
// Ensure application delegate is initialized
|
||||
let _ = AppDelegate::class();
|
||||
|
||||
extern "C" {
|
||||
// These functions are in crt_externs.h.
|
||||
fn _NSGetArgc() -> *mut c_int;
|
||||
fn _NSGetArgv() -> *mut *mut *mut c_char;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
UIApplicationMain(
|
||||
*_NSGetArgc(),
|
||||
NonNull::new(*_NSGetArgv()).unwrap(),
|
||||
None,
|
||||
Some(&NSString::from_str(AppDelegate::NAME)),
|
||||
)
|
||||
};
|
||||
unreachable!()
|
||||
// We intentionally override neither the application nor the delegate,
|
||||
// to allow the user to do so themselves!
|
||||
app_state::launch(self.mtm, app, || UIApplication::main(None, None, self.mtm))
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
|
||||
@@ -229,82 +261,19 @@ impl EventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopProxy {
|
||||
proxy_wake_up: Arc<AtomicBool>,
|
||||
source: CFRunLoopSourceRef,
|
||||
}
|
||||
|
||||
unsafe impl Send for EventLoopProxy {}
|
||||
unsafe impl Sync for EventLoopProxy {}
|
||||
|
||||
impl Clone for EventLoopProxy {
|
||||
fn clone(&self) -> EventLoopProxy {
|
||||
EventLoopProxy::new(self.proxy_wake_up.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventLoopProxy {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRunLoopSourceInvalidate(self.source);
|
||||
CFRelease(self.source as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
fn new(proxy_wake_up: Arc<AtomicBool>) -> EventLoopProxy {
|
||||
unsafe {
|
||||
// just wake up the eventloop
|
||||
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
|
||||
|
||||
// adding a Source to the main CFRunLoop lets us wake it up and
|
||||
// process user events through the normal OS EventLoop mechanisms.
|
||||
let rl = CFRunLoopGetMain();
|
||||
let mut context = CFRunLoopSourceContext {
|
||||
version: 0,
|
||||
info: ptr::null_mut(),
|
||||
retain: None,
|
||||
release: None,
|
||||
copyDescription: None,
|
||||
equal: None,
|
||||
hash: None,
|
||||
schedule: None,
|
||||
cancel: None,
|
||||
perform: event_loop_proxy_handler,
|
||||
};
|
||||
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(rl);
|
||||
|
||||
EventLoopProxy { proxy_wake_up, source }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wake_up(&self) {
|
||||
self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
|
||||
unsafe {
|
||||
// let the main thread know there's a new event
|
||||
CFRunLoopSourceSignal(self.source);
|
||||
let rl = CFRunLoopGetMain();
|
||||
CFRunLoopWakeUp(rl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
extern "C-unwind" fn control_flow_begin_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
|
||||
CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -320,65 +289,68 @@ fn setup_control_flow_observers() {
|
||||
// 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,
|
||||
extern "C-unwind" fn control_flow_main_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
|
||||
kCFRunLoopExit => {}, // may happen when running on macOS
|
||||
CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm),
|
||||
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||
extern "C" fn control_flow_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
extern "C-unwind" fn control_flow_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
|
||||
kCFRunLoopExit => {}, // may happen when running on macOS
|
||||
CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm),
|
||||
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let main_loop = CFRunLoopGetMain();
|
||||
let main_loop = CFRunLoop::main().unwrap();
|
||||
|
||||
let begin_observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopAfterWaiting,
|
||||
1, // repeat = true
|
||||
let begin_observer = CFRunLoopObserver::new(
|
||||
None,
|
||||
CFRunLoopActivity::AfterWaiting.0,
|
||||
true,
|
||||
CFIndex::MIN,
|
||||
control_flow_begin_handler,
|
||||
Some(control_flow_begin_handler),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
|
||||
)
|
||||
.unwrap();
|
||||
main_loop.add_observer(Some(&begin_observer), kCFRunLoopDefaultMode);
|
||||
|
||||
let main_end_observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
1, // repeat = true
|
||||
let main_end_observer = CFRunLoopObserver::new(
|
||||
None,
|
||||
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
|
||||
true,
|
||||
0, // see comment on `control_flow_main_end_handler`
|
||||
control_flow_main_end_handler,
|
||||
Some(control_flow_main_end_handler),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
|
||||
)
|
||||
.unwrap();
|
||||
main_loop.add_observer(Some(&main_end_observer), kCFRunLoopDefaultMode);
|
||||
|
||||
let end_observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
1, // repeat = true
|
||||
let end_observer = CFRunLoopObserver::new(
|
||||
None,
|
||||
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
|
||||
true,
|
||||
CFIndex::MAX,
|
||||
control_flow_end_handler,
|
||||
Some(control_flow_end_handler),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
|
||||
)
|
||||
.unwrap();
|
||||
main_loop.add_observer(Some(&end_observer), kCFRunLoopDefaultMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#![allow(clippy::let_unit_value)]
|
||||
|
||||
mod app_delegate;
|
||||
mod app_state;
|
||||
mod event_loop;
|
||||
mod monitor;
|
||||
@@ -11,44 +10,10 @@ mod window;
|
||||
use std::fmt;
|
||||
|
||||
pub(crate) use self::event_loop::{
|
||||
ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle,
|
||||
PlatformSpecificEventLoopAttributes,
|
||||
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
|
||||
};
|
||||
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
|
||||
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window, WindowId};
|
||||
pub(crate) use crate::cursor::{
|
||||
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
|
||||
};
|
||||
use crate::event::DeviceId as RootDeviceId;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
/// There is no way to detect which device that performed a certain event in
|
||||
/// UIKit (i.e. you can't differentiate between different external keyboards,
|
||||
/// or whether it was the main touchscreen, assistive technologies, or some
|
||||
/// other pointer device that caused a touch event).
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub const fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct FingerId(usize);
|
||||
|
||||
impl FingerId {
|
||||
pub const fn dummy() -> Self {
|
||||
FingerId(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct KeyEventExtra {}
|
||||
pub(crate) use self::monitor::MonitorHandle;
|
||||
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OsError {}
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::collections::VecDeque;
|
||||
use std::num::NonZeroU32;
|
||||
use std::{fmt, hash, ptr};
|
||||
|
||||
use objc2::mutability::IsRetainable;
|
||||
use dispatch2::{run_on_main, MainThreadBound};
|
||||
use objc2::rc::Retained;
|
||||
use objc2::Message;
|
||||
use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger};
|
||||
use objc2::{available, MainThreadMarker, Message};
|
||||
use objc2_foundation::NSInteger;
|
||||
use objc2_ui_kit::{UIScreen, UIScreenMode};
|
||||
|
||||
use super::app_state;
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::monitor::VideoModeHandle as RootVideoModeHandle;
|
||||
use crate::dpi::PhysicalPosition;
|
||||
use crate::monitor::{MonitorHandleProvider, VideoMode};
|
||||
|
||||
// Workaround for `MainThreadBound` implementing almost no traits
|
||||
#[derive(Debug)]
|
||||
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Retained<T>>);
|
||||
|
||||
impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> {
|
||||
impl<T: Message> Clone for MainThreadBoundDelegateImpls<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(run_on_main(|mtm| MainThreadBound::new(Retained::clone(self.0.get(mtm)), mtm)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
impl<T: Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
// SAFETY: Marker only used to get the pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
@@ -32,7 +31,7 @@ impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
impl<T: Message> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// SAFETY: Marker only used to get the pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
@@ -40,14 +39,12 @@ impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
|
||||
impl<T: Message> Eq for MainThreadBoundDelegateImpls<T> {}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub struct VideoModeHandle {
|
||||
pub(crate) size: (u32, u32),
|
||||
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
pub(crate) mode: VideoMode,
|
||||
screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
@@ -58,30 +55,18 @@ impl VideoModeHandle {
|
||||
) -> VideoModeHandle {
|
||||
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
|
||||
let size = screen_mode.size();
|
||||
VideoModeHandle {
|
||||
size: (size.width as u32, size.height as u32),
|
||||
let mode = VideoMode {
|
||||
size: (size.width as u32, size.height as u32).into(),
|
||||
bit_depth: None,
|
||||
refresh_rate_millihertz,
|
||||
};
|
||||
|
||||
VideoModeHandle {
|
||||
mode,
|
||||
screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)),
|
||||
monitor: MonitorHandle::new(uiscreen),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size.into()
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.monitor.clone()
|
||||
}
|
||||
|
||||
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Retained<UIScreenMode> {
|
||||
self.screen_mode.0.get(mtm)
|
||||
}
|
||||
@@ -91,6 +76,60 @@ pub struct MonitorHandle {
|
||||
ui_screen: MainThreadBound<Retained<UIScreen>>,
|
||||
}
|
||||
|
||||
impl MonitorHandleProvider for MonitorHandle {
|
||||
fn id(&self) -> u128 {
|
||||
self.native_id() as _
|
||||
}
|
||||
|
||||
fn native_id(&self) -> u64 {
|
||||
// SAFETY: Only getting the pointer.
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
Retained::as_ptr(self.ui_screen.get(mtm)) as u64
|
||||
}
|
||||
|
||||
fn name(&self) -> Option<std::borrow::Cow<'_, str>> {
|
||||
run_on_main(|mtm| {
|
||||
#[allow(deprecated)]
|
||||
let main = UIScreen::mainScreen(mtm);
|
||||
if *self.ui_screen(mtm) == main {
|
||||
Some("Primary".into())
|
||||
} else if Some(self.ui_screen(mtm)) == main.mirroredScreen().as_ref() {
|
||||
Some("Mirrored".into())
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
UIScreen::screens(mtm)
|
||||
.iter()
|
||||
.position(|rhs| rhs == *self.ui_screen(mtm))
|
||||
.map(|idx| idx.to_string().into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn position(&self) -> Option<PhysicalPosition<i32>> {
|
||||
let bounds = self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeBounds());
|
||||
Some((bounds.origin.x as f64, bounds.origin.y as f64).into())
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f64 {
|
||||
self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
|
||||
}
|
||||
|
||||
fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
Some(run_on_main(|mtm| {
|
||||
VideoModeHandle::new(
|
||||
self.ui_screen(mtm).clone(),
|
||||
self.ui_screen(mtm).currentMode().unwrap(),
|
||||
mtm,
|
||||
)
|
||||
.mode
|
||||
}))
|
||||
}
|
||||
|
||||
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
|
||||
Box::new(self.video_modes())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for MonitorHandle {
|
||||
fn clone(&self) -> Self {
|
||||
run_on_main(|mtm| Self {
|
||||
@@ -101,13 +140,20 @@ impl Clone for MonitorHandle {
|
||||
|
||||
impl hash::Hash for MonitorHandle {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
(self as *const Self).hash(state);
|
||||
// SAFETY: Only getting the pointer.
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
Retained::as_ptr(self.ui_screen.get(mtm)).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
ptr::eq(self, other)
|
||||
// SAFETY: Only getting the pointer.
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
ptr::eq(
|
||||
Retained::as_ptr(self.ui_screen.get(mtm)),
|
||||
Retained::as_ptr(other.ui_screen.get(mtm)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,8 +167,10 @@ impl PartialOrd for MonitorHandle {
|
||||
|
||||
impl Ord for MonitorHandle {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
// SAFETY: Only getting the pointer.
|
||||
// TODO: Make a better ordering
|
||||
(self as *const Self).cmp(&(other as *const Self))
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,79 +191,42 @@ impl MonitorHandle {
|
||||
Self { ui_screen: MainThreadBound::new(ui_screen, mtm) }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<String> {
|
||||
run_on_main(|mtm| {
|
||||
#[allow(deprecated)]
|
||||
let main = UIScreen::mainScreen(mtm);
|
||||
if *self.ui_screen(mtm) == main {
|
||||
Some("Primary".to_string())
|
||||
} else if Some(self.ui_screen(mtm)) == main.mirroredScreen().as_ref() {
|
||||
Some("Mirrored".to_string())
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
UIScreen::screens(mtm)
|
||||
.iter()
|
||||
.position(|rhs| rhs == &**self.ui_screen(mtm))
|
||||
.map(|idx| idx.to_string())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
|
||||
let bounds = self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeBounds());
|
||||
Some((bounds.origin.x as f64, bounds.origin.y as f64).into())
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
|
||||
}
|
||||
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
Some(run_on_main(|mtm| {
|
||||
VideoModeHandle::new(
|
||||
self.ui_screen(mtm).clone(),
|
||||
self.ui_screen(mtm).currentMode().unwrap(),
|
||||
mtm,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
pub fn video_modes_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
run_on_main(|mtm| {
|
||||
let ui_screen = self.ui_screen(mtm);
|
||||
// Use Ord impl of RootVideoModeHandle
|
||||
|
||||
let modes: BTreeSet<_> = ui_screen
|
||||
ui_screen
|
||||
.availableModes()
|
||||
.into_iter()
|
||||
.map(|mode| RootVideoModeHandle {
|
||||
video_mode: VideoModeHandle::new(ui_screen.clone(), mode, mtm),
|
||||
})
|
||||
.collect();
|
||||
|
||||
modes.into_iter().map(|mode| mode.video_mode)
|
||||
.map(|mode| VideoModeHandle::new(ui_screen.clone(), mode, mtm))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.video_modes_handles().map(|handle| handle.mode)
|
||||
}
|
||||
|
||||
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Retained<UIScreen> {
|
||||
self.ui_screen.get(mtm)
|
||||
}
|
||||
|
||||
pub fn preferred_video_mode(&self) -> VideoModeHandle {
|
||||
pub fn preferred_video_mode(&self) -> VideoMode {
|
||||
run_on_main(|mtm| {
|
||||
VideoModeHandle::new(
|
||||
self.ui_screen(mtm).clone(),
|
||||
self.ui_screen(mtm).preferredMode().unwrap(),
|
||||
mtm,
|
||||
)
|
||||
.mode
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
|
||||
let refresh_rate_millihertz: NSInteger = {
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.maximum_frames_per_second {
|
||||
if available!(ios = 10.3, tvos = 10.2) {
|
||||
uiscreen.maximumFramesPerSecond()
|
||||
} else {
|
||||
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
|
||||
@@ -228,7 +239,9 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
|
||||
//
|
||||
// FIXME: earlier OSs could calculate the refresh rate using
|
||||
// `-[CADisplayLink duration]`.
|
||||
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
|
||||
tracing::warn!(
|
||||
"`maximumFramesPerSecond` requires iOS 10.3+ or tvOS 10.2+. Defaulting to 60 fps"
|
||||
);
|
||||
60
|
||||
}
|
||||
};
|
||||
@@ -240,3 +253,27 @@ pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
|
||||
#[allow(deprecated)]
|
||||
UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use objc2_foundation::NSSet;
|
||||
|
||||
use super::*;
|
||||
|
||||
// Test that UIScreen pointer comparisons are correct.
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn screen_comparisons() {
|
||||
// Test code, doesn't matter that it's not thread safe
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
|
||||
assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm)));
|
||||
|
||||
let main = UIScreen::mainScreen(mtm);
|
||||
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(&*screen, &*main)));
|
||||
|
||||
assert!(unsafe {
|
||||
NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,26 @@ use std::cell::{Cell, RefCell};
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::{NSObjectProtocol, ProtocolObject};
|
||||
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
|
||||
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet};
|
||||
use objc2::{available, define_class, msg_send, sel, DefinedClass, MainThreadMarker};
|
||||
use objc2_core_foundation::{CGFloat, CGPoint, CGRect};
|
||||
use objc2_foundation::{NSObject, NSSet, NSString};
|
||||
use objc2_ui_kit::{
|
||||
UICoordinateSpace, UIEvent, UIForceTouchCapability, UIGestureRecognizer,
|
||||
UIGestureRecognizerDelegate, UIGestureRecognizerState, UIPanGestureRecognizer,
|
||||
UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer,
|
||||
UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView,
|
||||
UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate,
|
||||
UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer,
|
||||
UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITextInputTraits, UITouch,
|
||||
UITouchPhase, UITouchType, UITraitEnvironment, UIView,
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
use super::app_state::{self, EventWrapper};
|
||||
use super::window::WinitUIWindow;
|
||||
use super::{FingerId, DEVICE_ID};
|
||||
use crate::dpi::PhysicalPosition;
|
||||
use crate::event::{Event, FingerId as RootFingerId, Force, Touch, TouchPhase, WindowEvent};
|
||||
use crate::window::{WindowAttributes, WindowId as RootWindowId};
|
||||
use crate::event::{
|
||||
ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource, TouchPhase,
|
||||
WindowEvent,
|
||||
};
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
|
||||
use crate::window::WindowAttributes;
|
||||
|
||||
pub struct WinitViewState {
|
||||
pinch_gesture_recognizer: RefCell<Option<Retained<UIPinchGestureRecognizer>>>,
|
||||
@@ -29,72 +34,51 @@ pub struct WinitViewState {
|
||||
rotation_last_delta: Cell<CGFloat>,
|
||||
pinch_last_delta: Cell<CGFloat>,
|
||||
pan_last_delta: Cell<CGPoint>,
|
||||
|
||||
primary_finger: Cell<Option<FingerId>>,
|
||||
fingers: Cell<u8>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(UIView, UIResponder, NSObject))]
|
||||
#[name = "WinitUIView"]
|
||||
#[ivars = WinitViewState]
|
||||
pub(crate) struct WinitView;
|
||||
|
||||
unsafe impl ClassType for WinitView {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIView;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitUIView";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitView {
|
||||
type Ivars = WinitViewState;
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(drawRect:)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitView {
|
||||
#[unsafe(method(drawRect:))]
|
||||
fn draw_rect(&self, rect: CGRect) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let window = self.window().unwrap();
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
}),
|
||||
);
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
});
|
||||
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
|
||||
}
|
||||
|
||||
#[method(layoutSubviews)]
|
||||
#[unsafe(method(layoutSubviews))]
|
||||
fn layout_subviews(&self) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
|
||||
|
||||
let window = self.window().unwrap();
|
||||
let window_bounds = window.bounds();
|
||||
let screen = window.screen();
|
||||
let screen_space = screen.coordinateSpace();
|
||||
let screen_frame = self.convertRect_toCoordinateSpace(window_bounds, &screen_space);
|
||||
let scale_factor = screen.scale();
|
||||
let frame = self.frame();
|
||||
let scale_factor = self.contentScaleFactor() as f64;
|
||||
let size = crate::dpi::LogicalSize {
|
||||
width: screen_frame.size.width as f64,
|
||||
height: screen_frame.size.height as f64,
|
||||
width: frame.size.width as f64,
|
||||
height: frame.size.height as f64,
|
||||
}
|
||||
.to_physical(scale_factor as f64);
|
||||
.to_physical(scale_factor);
|
||||
|
||||
// If the app is started in landscape, the view frame and window bounds can be mismatched.
|
||||
// The view frame will be in portrait and the window bounds in landscape. So apply the
|
||||
// window bounds to the view frame to make it consistent.
|
||||
let view_frame = self.frame();
|
||||
if view_frame != window_bounds {
|
||||
self.setFrame(window_bounds);
|
||||
}
|
||||
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Resized(size),
|
||||
}),
|
||||
);
|
||||
let window = self.window().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::SurfaceResized(size),
|
||||
});
|
||||
}
|
||||
|
||||
#[method(setContentScaleFactor:)]
|
||||
#[unsafe(method(setContentScaleFactor:))]
|
||||
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let _: () =
|
||||
@@ -119,54 +103,54 @@ declare_class!(
|
||||
"invalid scale_factor set on UIView",
|
||||
);
|
||||
let scale_factor = scale_factor as f64;
|
||||
let bounds = self.bounds();
|
||||
let screen = window.screen();
|
||||
let screen_space = screen.coordinateSpace();
|
||||
let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space);
|
||||
let frame = self.frame();
|
||||
let size = crate::dpi::LogicalSize {
|
||||
width: screen_frame.size.width as f64,
|
||||
height: screen_frame.size.height as f64,
|
||||
width: frame.size.width as f64,
|
||||
height: frame.size.height as f64,
|
||||
};
|
||||
let window_id = RootWindowId(window.id());
|
||||
let window_id = window.id();
|
||||
app_state::handle_nonuser_events(
|
||||
mtm,
|
||||
std::iter::once(EventWrapper::ScaleFactorChanged(
|
||||
app_state::ScaleFactorChanged {
|
||||
window,
|
||||
scale_factor,
|
||||
suggested_size: size.to_physical(scale_factor),
|
||||
},
|
||||
))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
||||
},
|
||||
))),
|
||||
std::iter::once(EventWrapper::ScaleFactorChanged(app_state::ScaleFactorChanged {
|
||||
window,
|
||||
scale_factor,
|
||||
suggested_size: size.to_physical(scale_factor),
|
||||
}))
|
||||
.chain(std::iter::once(EventWrapper::Window {
|
||||
window_id,
|
||||
event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
#[method(touchesBegan:withEvent:)]
|
||||
#[unsafe(method(safeAreaInsetsDidChange))]
|
||||
fn safe_area_changed(&self) {
|
||||
debug!("safeAreaInsetsDidChange was called, requesting redraw");
|
||||
// When the safe area changes we want to make sure to emit a redraw event
|
||||
self.setNeedsDisplay();
|
||||
}
|
||||
|
||||
#[unsafe(method(touchesBegan:withEvent:))]
|
||||
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[method(touchesMoved:withEvent:)]
|
||||
#[unsafe(method(touchesMoved:withEvent:))]
|
||||
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[method(touchesEnded:withEvent:)]
|
||||
#[unsafe(method(touchesEnded:withEvent:))]
|
||||
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[method(touchesCancelled:withEvent:)]
|
||||
#[unsafe(method(touchesCancelled:withEvent:))]
|
||||
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[method(pinchGesture:)]
|
||||
#[unsafe(method(pinchGesture:))]
|
||||
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
|
||||
let window = self.window().unwrap();
|
||||
|
||||
@@ -174,54 +158,48 @@ declare_class!(
|
||||
UIGestureRecognizerState::Began => {
|
||||
self.ivars().pinch_last_delta.set(recognizer.scale());
|
||||
(TouchPhase::Started, 0.0)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Changed => {
|
||||
let last_scale: f64 = self.ivars().pinch_last_delta.replace(recognizer.scale());
|
||||
(TouchPhase::Moved, recognizer.scale() - last_scale)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Ended => {
|
||||
let last_scale: f64 = self.ivars().pinch_last_delta.replace(0.0);
|
||||
(TouchPhase::Moved, recognizer.scale() - last_scale)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
|
||||
self.ivars().rotation_last_delta.set(0.0);
|
||||
// Pass -delta so that action is reversed
|
||||
(TouchPhase::Cancelled, -recognizer.scale())
|
||||
}
|
||||
state => panic!("unexpected recognizer state: {:?}", state),
|
||||
},
|
||||
state => panic!("unexpected recognizer state: {state:?}"),
|
||||
};
|
||||
|
||||
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::PinchGesture {
|
||||
device_id: DEVICE_ID,
|
||||
delta: delta as f64,
|
||||
phase,
|
||||
},
|
||||
});
|
||||
let gesture_event = EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::PinchGesture { device_id: None, delta: delta as f64, phase },
|
||||
};
|
||||
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, gesture_event);
|
||||
}
|
||||
|
||||
#[method(doubleTapGesture:)]
|
||||
#[unsafe(method(doubleTapGesture:))]
|
||||
fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) {
|
||||
let window = self.window().unwrap();
|
||||
|
||||
if recognizer.state() == UIGestureRecognizerState::Ended {
|
||||
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::DoubleTapGesture {
|
||||
device_id: DEVICE_ID,
|
||||
},
|
||||
});
|
||||
let gesture_event = EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::DoubleTapGesture { device_id: None },
|
||||
};
|
||||
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, gesture_event);
|
||||
}
|
||||
}
|
||||
|
||||
#[method(rotationGesture:)]
|
||||
#[unsafe(method(rotationGesture:))]
|
||||
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
|
||||
let window = self.window().unwrap();
|
||||
|
||||
@@ -230,41 +208,42 @@ declare_class!(
|
||||
self.ivars().rotation_last_delta.set(0.0);
|
||||
|
||||
(TouchPhase::Started, 0.0)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Changed => {
|
||||
let last_rotation = self.ivars().rotation_last_delta.replace(recognizer.rotation());
|
||||
let last_rotation =
|
||||
self.ivars().rotation_last_delta.replace(recognizer.rotation());
|
||||
|
||||
(TouchPhase::Moved, recognizer.rotation() - last_rotation)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Ended => {
|
||||
let last_rotation = self.ivars().rotation_last_delta.replace(0.0);
|
||||
|
||||
(TouchPhase::Ended, recognizer.rotation() - last_rotation)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
|
||||
self.ivars().rotation_last_delta.set(0.0);
|
||||
|
||||
// Pass -delta so that action is reversed
|
||||
(TouchPhase::Cancelled, -recognizer.rotation())
|
||||
}
|
||||
state => panic!("unexpected recognizer state: {:?}", state),
|
||||
},
|
||||
state => panic!("unexpected recognizer state: {state:?}"),
|
||||
};
|
||||
|
||||
// Make delta negative to match macos, convert to degrees
|
||||
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
let gesture_event = EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::RotationGesture {
|
||||
device_id: DEVICE_ID,
|
||||
device_id: None,
|
||||
delta: -delta.to_degrees() as _,
|
||||
phase,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, gesture_event);
|
||||
}
|
||||
|
||||
#[method(panGesture:)]
|
||||
#[unsafe(method(panGesture:))]
|
||||
fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) {
|
||||
let window = self.window().unwrap();
|
||||
|
||||
@@ -275,7 +254,7 @@ declare_class!(
|
||||
self.ivars().pan_last_delta.set(translation);
|
||||
|
||||
(TouchPhase::Started, 0.0, 0.0)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Changed => {
|
||||
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(translation);
|
||||
|
||||
@@ -283,47 +262,76 @@ declare_class!(
|
||||
let dy = translation.y - last_pan.y;
|
||||
|
||||
(TouchPhase::Moved, dx, dy)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Ended => {
|
||||
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0});
|
||||
let last_pan: CGPoint =
|
||||
self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 });
|
||||
|
||||
let dx = translation.x - last_pan.x;
|
||||
let dy = translation.y - last_pan.y;
|
||||
|
||||
(TouchPhase::Ended, dx, dy)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
|
||||
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0});
|
||||
let last_pan: CGPoint =
|
||||
self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 });
|
||||
|
||||
// Pass -delta so that action is reversed
|
||||
(TouchPhase::Cancelled, -last_pan.x, -last_pan.y)
|
||||
}
|
||||
state => panic!("unexpected recognizer state: {:?}", state),
|
||||
},
|
||||
state => panic!("unexpected recognizer state: {state:?}"),
|
||||
};
|
||||
|
||||
|
||||
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
let gesture_event = EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::PanGesture {
|
||||
device_id: DEVICE_ID,
|
||||
device_id: None,
|
||||
delta: PhysicalPosition::new(dx as _, dy as _),
|
||||
phase,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, gesture_event);
|
||||
}
|
||||
|
||||
#[unsafe(method(canBecomeFirstResponder))]
|
||||
fn can_become_first_responder(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSObjectProtocol for WinitView {}
|
||||
|
||||
unsafe impl UIGestureRecognizerDelegate for WinitView {
|
||||
#[method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]
|
||||
fn should_recognize_simultaneously(&self, _gesture_recognizer: &UIGestureRecognizer, _other_gesture_recognizer: &UIGestureRecognizer) -> bool {
|
||||
#[unsafe(method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:))]
|
||||
fn should_recognize_simultaneously(
|
||||
&self,
|
||||
_gesture_recognizer: &UIGestureRecognizer,
|
||||
_other_gesture_recognizer: &UIGestureRecognizer,
|
||||
) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl UITextInputTraits for WinitView {}
|
||||
|
||||
unsafe impl UIKeyInput for WinitView {
|
||||
#[unsafe(method(hasText))]
|
||||
fn has_text(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[unsafe(method(insertText:))]
|
||||
fn insert_text(&self, text: &NSString) {
|
||||
self.handle_insert_text(text)
|
||||
}
|
||||
|
||||
#[unsafe(method(deleteBackward))]
|
||||
fn delete_backward(&self) {
|
||||
self.handle_delete_backward()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitView {
|
||||
@@ -341,8 +349,11 @@ impl WinitView {
|
||||
rotation_last_delta: Cell::new(0.0),
|
||||
pinch_last_delta: Cell::new(0.0),
|
||||
pan_last_delta: Cell::new(CGPoint { x: 0.0, y: 0.0 }),
|
||||
|
||||
primary_finger: Cell::new(None),
|
||||
fingers: Cell::new(0),
|
||||
});
|
||||
let this: Retained<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
|
||||
let this: Retained<Self> = unsafe { msg_send![super(this), initWithFrame: frame] };
|
||||
|
||||
this.setMultipleTouchEnabled(true);
|
||||
|
||||
@@ -354,8 +365,8 @@ impl WinitView {
|
||||
}
|
||||
|
||||
fn window(&self) -> Option<Retained<WinitUIWindow>> {
|
||||
// SAFETY: `WinitView`s are always installed in a `WinitUIWindow`
|
||||
(**self).window().map(|window| unsafe { Retained::cast(window) })
|
||||
// `WinitView`s should always be installed in a `WinitUIWindow`
|
||||
(**self).window().map(|window| window.downcast().unwrap())
|
||||
}
|
||||
|
||||
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
|
||||
@@ -450,29 +461,21 @@ impl WinitView {
|
||||
fn handle_touches(&self, touches: &NSSet<UITouch>) {
|
||||
let window = self.window().unwrap();
|
||||
let mut touch_events = Vec::new();
|
||||
let os_supports_force = app_state::os_capabilities().force_touch;
|
||||
for touch in touches {
|
||||
let logical_location = touch.locationInView(None);
|
||||
let touch_type = touch.r#type();
|
||||
let force = if os_supports_force {
|
||||
let force = if let UITouchType::Pencil = touch_type {
|
||||
None
|
||||
} else if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) {
|
||||
let trait_collection = self.traitCollection();
|
||||
let touch_capability = trait_collection.forceTouchCapability();
|
||||
// Both the OS _and_ the device need to be checked for force touch support.
|
||||
if touch_capability == UIForceTouchCapability::Available
|
||||
|| touch_type == UITouchType::Pencil
|
||||
{
|
||||
if touch_capability == UIForceTouchCapability::Available {
|
||||
let force = touch.force();
|
||||
let max_possible_force = touch.maximumPossibleForce();
|
||||
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
|
||||
let angle = touch.altitudeAngle();
|
||||
Some(angle as _)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(Force::Calibrated {
|
||||
force: force as _,
|
||||
max_possible_force: max_possible_force as _,
|
||||
altitude_angle,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -480,36 +483,203 @@ impl WinitView {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let touch_id = touch as *const UITouch as usize;
|
||||
let touch_id = Retained::as_ptr(&touch) as usize;
|
||||
let phase = touch.phase();
|
||||
let phase = match phase {
|
||||
UITouchPhase::Began => TouchPhase::Started,
|
||||
UITouchPhase::Moved => TouchPhase::Moved,
|
||||
// 2 is UITouchPhase::Stationary and is not expected here
|
||||
UITouchPhase::Ended => TouchPhase::Ended,
|
||||
UITouchPhase::Cancelled => TouchPhase::Cancelled,
|
||||
_ => panic!("unexpected touch phase: {phase:?}"),
|
||||
};
|
||||
|
||||
let physical_location = {
|
||||
let position = {
|
||||
let scale_factor = self.contentScaleFactor();
|
||||
PhysicalPosition::from_logical::<(f64, f64), f64>(
|
||||
(logical_location.x as _, logical_location.y as _),
|
||||
scale_factor as f64,
|
||||
)
|
||||
};
|
||||
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
device_id: DEVICE_ID,
|
||||
finger_id: RootFingerId(FingerId(touch_id)),
|
||||
location: physical_location,
|
||||
force,
|
||||
phase,
|
||||
}),
|
||||
}));
|
||||
let window_id = window.id();
|
||||
let finger_id = FingerId::from_raw(touch_id);
|
||||
|
||||
let ivars = self.ivars();
|
||||
|
||||
match phase {
|
||||
UITouchPhase::Began => {
|
||||
let primary = if let UITouchType::Pencil = touch_type {
|
||||
true
|
||||
} else {
|
||||
ivars.fingers.set(ivars.fingers.get() + 1);
|
||||
// Keep the primary finger around until we clear all the fingers to
|
||||
// recognize it when user briefly removes it.
|
||||
match ivars.primary_finger.get() {
|
||||
Some(primary_id) => primary_id == finger_id,
|
||||
None => {
|
||||
debug_assert_eq!(
|
||||
ivars.fingers.get(),
|
||||
1,
|
||||
"number of fingers were not counted correctly"
|
||||
);
|
||||
ivars.primary_finger.set(Some(finger_id));
|
||||
true
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
touch_events.push(EventWrapper::Window {
|
||||
window_id,
|
||||
event: WindowEvent::PointerEntered {
|
||||
device_id: None,
|
||||
primary,
|
||||
position,
|
||||
kind: if let UITouchType::Pencil = touch_type {
|
||||
PointerKind::Unknown
|
||||
} else {
|
||||
PointerKind::Touch(finger_id)
|
||||
},
|
||||
},
|
||||
});
|
||||
touch_events.push(EventWrapper::Window {
|
||||
window_id,
|
||||
event: WindowEvent::PointerButton {
|
||||
device_id: None,
|
||||
primary,
|
||||
state: ElementState::Pressed,
|
||||
position,
|
||||
button: if let UITouchType::Pencil = touch_type {
|
||||
ButtonSource::Unknown(0)
|
||||
} else {
|
||||
ButtonSource::Touch { finger_id, force }
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
UITouchPhase::Moved => {
|
||||
let (primary, source) = if let UITouchType::Pencil = touch_type {
|
||||
(true, PointerSource::Unknown)
|
||||
} else {
|
||||
(ivars.primary_finger.get().unwrap() == finger_id, PointerSource::Touch {
|
||||
finger_id,
|
||||
force,
|
||||
})
|
||||
};
|
||||
|
||||
touch_events.push(EventWrapper::Window {
|
||||
window_id,
|
||||
event: WindowEvent::PointerMoved {
|
||||
device_id: None,
|
||||
primary,
|
||||
position,
|
||||
source,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 2 is UITouchPhase::Stationary and is not expected here
|
||||
UITouchPhase::Ended | UITouchPhase::Cancelled => {
|
||||
let primary = if let UITouchType::Pencil = touch_type {
|
||||
true
|
||||
} else {
|
||||
ivars.fingers.set(ivars.fingers.get() - 1);
|
||||
let primary = ivars.primary_finger.get().unwrap() == finger_id;
|
||||
if ivars.fingers.get() == 0 {
|
||||
ivars.primary_finger.set(None);
|
||||
}
|
||||
primary
|
||||
};
|
||||
|
||||
if let UITouchPhase::Ended = phase {
|
||||
touch_events.push(EventWrapper::Window {
|
||||
window_id,
|
||||
event: WindowEvent::PointerButton {
|
||||
device_id: None,
|
||||
primary,
|
||||
state: ElementState::Released,
|
||||
position,
|
||||
button: if let UITouchType::Pencil = touch_type {
|
||||
ButtonSource::Unknown(0)
|
||||
} else {
|
||||
ButtonSource::Touch { finger_id, force }
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
touch_events.push(EventWrapper::Window {
|
||||
window_id,
|
||||
event: WindowEvent::PointerLeft {
|
||||
device_id: None,
|
||||
primary,
|
||||
position: Some(position),
|
||||
kind: if let UITouchType::Pencil = touch_type {
|
||||
PointerKind::Unknown
|
||||
} else {
|
||||
PointerKind::Touch(finger_id)
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
_ => panic!("unexpected touch phase: {phase:?}"),
|
||||
}
|
||||
}
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_events(mtm, touch_events);
|
||||
}
|
||||
|
||||
fn handle_insert_text(&self, text: &NSString) {
|
||||
let window = self.window().unwrap();
|
||||
let window_id = window.id();
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
// send individual events for each character
|
||||
app_state::handle_nonuser_events(
|
||||
mtm,
|
||||
text.to_string().chars().flat_map(|c| {
|
||||
let text = smol_str::SmolStr::from_iter([c]);
|
||||
// Emit both press and release events
|
||||
[ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: None,
|
||||
event: KeyEvent {
|
||||
text: if state == ElementState::Pressed {
|
||||
Some(text.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
state,
|
||||
location: KeyLocation::Standard,
|
||||
repeat: false,
|
||||
logical_key: Key::Character(text.clone()),
|
||||
physical_key: PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
|
||||
text_with_all_modifiers: if state == ElementState::Pressed {
|
||||
Some(text.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
key_without_modifiers: Key::Character(text.clone()),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn handle_delete_backward(&self) {
|
||||
let window = self.window().unwrap();
|
||||
let window_id = window.id();
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_events(
|
||||
mtm,
|
||||
[ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: None,
|
||||
event: KeyEvent {
|
||||
state,
|
||||
logical_key: Key::Named(NamedKey::Backspace),
|
||||
physical_key: PhysicalKey::Code(KeyCode::Backspace),
|
||||
repeat: false,
|
||||
location: KeyLocation::Standard,
|
||||
text: None,
|
||||
text_with_all_modifiers: None,
|
||||
key_without_modifiers: Key::Named(NamedKey::Backspace),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
use objc2_foundation::{MainThreadMarker, NSObject};
|
||||
use objc2::{available, define_class, msg_send, DefinedClass, MainThreadMarker};
|
||||
use objc2_foundation::NSObject;
|
||||
use objc2_ui_kit::{
|
||||
UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle,
|
||||
UIUserInterfaceIdiom, UIView, UIViewController,
|
||||
};
|
||||
|
||||
use super::app_state::{self};
|
||||
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
|
||||
use crate::window::WindowAttributes;
|
||||
|
||||
@@ -20,51 +19,42 @@ pub struct ViewControllerState {
|
||||
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(UIViewController, UIResponder, NSObject))]
|
||||
#[name = "WinitUIViewController"]
|
||||
#[ivars = ViewControllerState]
|
||||
pub(crate) struct WinitViewController;
|
||||
|
||||
unsafe impl ClassType for WinitViewController {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIViewController;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitUIViewController";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitViewController {
|
||||
type Ivars = ViewControllerState;
|
||||
}
|
||||
|
||||
unsafe impl WinitViewController {
|
||||
#[method(shouldAutorotate)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitViewController {
|
||||
#[unsafe(method(shouldAutorotate))]
|
||||
fn should_autorotate(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[method(prefersStatusBarHidden)]
|
||||
#[unsafe(method(prefersStatusBarHidden))]
|
||||
fn prefers_status_bar_hidden(&self) -> bool {
|
||||
self.ivars().prefers_status_bar_hidden.get()
|
||||
}
|
||||
|
||||
#[method(preferredStatusBarStyle)]
|
||||
#[unsafe(method(preferredStatusBarStyle))]
|
||||
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
|
||||
self.ivars().preferred_status_bar_style.get()
|
||||
}
|
||||
|
||||
#[method(prefersHomeIndicatorAutoHidden)]
|
||||
#[unsafe(method(prefersHomeIndicatorAutoHidden))]
|
||||
fn prefers_home_indicator_auto_hidden(&self) -> bool {
|
||||
self.ivars().prefers_home_indicator_auto_hidden.get()
|
||||
}
|
||||
|
||||
#[method(supportedInterfaceOrientations)]
|
||||
#[unsafe(method(supportedInterfaceOrientations))]
|
||||
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
|
||||
self.ivars().supported_orientations.get()
|
||||
}
|
||||
|
||||
#[method(preferredScreenEdgesDeferringSystemGestures)]
|
||||
#[unsafe(method(preferredScreenEdgesDeferringSystemGestures))]
|
||||
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
|
||||
self.ivars()
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.get()
|
||||
self.ivars().preferred_screen_edges_deferring_system_gestures.get()
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -87,11 +77,13 @@ impl WinitViewController {
|
||||
|
||||
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
|
||||
self.ivars().prefers_home_indicator_auto_hidden.set(val);
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.home_indicator_hidden {
|
||||
if available!(ios = 11.0, visionos = 1.0) {
|
||||
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
|
||||
} else {
|
||||
os_capabilities.home_indicator_hidden_err_msg("ignoring")
|
||||
tracing::warn!(
|
||||
"`setNeedsUpdateOfHomeIndicatorAutoHidden` requires iOS 11.0+ or visionOS. \
|
||||
Ignoring"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,11 +93,13 @@ impl WinitViewController {
|
||||
UIRectEdge(val.bits().into())
|
||||
};
|
||||
self.ivars().preferred_screen_edges_deferring_system_gestures.set(val);
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.defer_system_gestures {
|
||||
if available!(ios = 11.0, visionos = 1.0) {
|
||||
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
|
||||
} else {
|
||||
os_capabilities.defer_system_gestures_err_msg("ignoring")
|
||||
tracing::warn!(
|
||||
"`setNeedsUpdateOfScreenEdgesDeferringSystemGestures` requires iOS 11.0+ or \
|
||||
visionOS. Ignoring"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +140,7 @@ impl WinitViewController {
|
||||
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
|
||||
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()),
|
||||
});
|
||||
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
|
||||
|
||||
this.set_prefers_status_bar_hidden(
|
||||
window_attributes.platform_specific.prefers_status_bar_hidden,
|
||||
|
||||
@@ -1,71 +1,63 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
|
||||
use dispatch2::MainThreadBound;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::{AnyObject, NSObject};
|
||||
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
use objc2_foundation::{
|
||||
CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObjectProtocol,
|
||||
};
|
||||
use objc2::{available, class, define_class, msg_send, MainThreadMarker};
|
||||
use objc2_core_foundation::{CGFloat, CGPoint, CGRect, CGSize};
|
||||
use objc2_foundation::{NSObject, NSObjectProtocol};
|
||||
use objc2_ui_kit::{
|
||||
UIApplication, UICoordinateSpace, UIResponder, UIScreen, UIScreenOverscanCompensation,
|
||||
UIViewController, UIWindow,
|
||||
UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
|
||||
UIScreenOverscanCompensation, UIViewController, UIWindow,
|
||||
};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use super::app_state::EventWrapper;
|
||||
use super::view::WinitView;
|
||||
use super::view_controller::WinitViewController;
|
||||
use super::{app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle};
|
||||
use super::{app_state, monitor, ActiveEventLoop, MonitorHandle};
|
||||
use crate::cursor::Cursor;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
||||
use crate::event::{Event, WindowEvent};
|
||||
use crate::dpi::{
|
||||
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
|
||||
Position, Size,
|
||||
};
|
||||
use crate::error::{NotSupportedError, RequestError};
|
||||
use crate::event::WindowEvent;
|
||||
use crate::icon::Icon;
|
||||
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
|
||||
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
|
||||
use crate::window::{
|
||||
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
|
||||
WindowButtons, WindowId as RootWindowId, WindowLevel,
|
||||
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow,
|
||||
WindowAttributes, WindowButtons, WindowId, WindowLevel,
|
||||
};
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(UIWindow, UIResponder, NSObject))]
|
||||
#[name = "WinitUIWindow"]
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct WinitUIWindow;
|
||||
|
||||
unsafe impl ClassType for WinitUIWindow {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIWindow;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitUIWindow";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitUIWindow {}
|
||||
|
||||
unsafe impl WinitUIWindow {
|
||||
#[method(becomeKeyWindow)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitUIWindow {
|
||||
#[unsafe(method(becomeKeyWindow))]
|
||||
fn become_key_window(&self) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(self.id()),
|
||||
event: WindowEvent::Focused(true),
|
||||
}),
|
||||
);
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
|
||||
window_id: self.id(),
|
||||
event: WindowEvent::Focused(true),
|
||||
});
|
||||
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
|
||||
}
|
||||
|
||||
#[method(resignKeyWindow)]
|
||||
#[unsafe(method(resignKeyWindow))]
|
||||
fn resign_key_window(&self) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(self.id()),
|
||||
event: WindowEvent::Focused(false),
|
||||
}),
|
||||
);
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
|
||||
window_id: self.id(),
|
||||
event: WindowEvent::Focused(false),
|
||||
});
|
||||
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
|
||||
}
|
||||
}
|
||||
@@ -78,18 +70,28 @@ impl WinitUIWindow {
|
||||
frame: CGRect,
|
||||
view_controller: &UIViewController,
|
||||
) -> Retained<Self> {
|
||||
let this: Retained<Self> = unsafe { msg_send_id![mtm.alloc(), initWithFrame: frame] };
|
||||
// NOTE: This should only be created after the application has started launching,
|
||||
// (`application:willFinishLaunchingWithOptions:` at the earliest), otherwise you'll run
|
||||
// into very confusing issues with the window not being properly activated.
|
||||
//
|
||||
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
|
||||
let this: Retained<Self> = unsafe { msg_send![mtm.alloc(), initWithFrame: frame] };
|
||||
|
||||
this.setRootViewController(Some(view_controller));
|
||||
|
||||
match window_attributes.fullscreen.clone().map(Into::into) {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
let monitor = video_mode.monitor();
|
||||
match window_attributes.fullscreen.clone() {
|
||||
Some(Fullscreen::Exclusive(monitor, ref video_mode)) => {
|
||||
let monitor = monitor.cast_ref::<MonitorHandle>().unwrap();
|
||||
let screen = monitor.ui_screen(mtm);
|
||||
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
||||
if let Some(video_mode) =
|
||||
monitor.video_modes_handles().find(|mode| &mode.mode == video_mode)
|
||||
{
|
||||
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
||||
}
|
||||
this.setScreen(screen);
|
||||
},
|
||||
Some(Fullscreen::Borderless(Some(ref monitor))) => {
|
||||
let monitor = monitor.cast_ref::<MonitorHandle>().unwrap();
|
||||
let screen = monitor.ui_screen(mtm);
|
||||
this.setScreen(screen);
|
||||
},
|
||||
@@ -100,7 +102,7 @@ impl WinitUIWindow {
|
||||
}
|
||||
|
||||
pub(crate) fn id(&self) -> WindowId {
|
||||
(self as *const Self as usize as u64).into()
|
||||
WindowId::from_raw(self as *const Self as usize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,20 +155,19 @@ impl Inner {
|
||||
|
||||
pub fn pre_present_notify(&self) {}
|
||||
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
pub fn surface_position(&self) -> PhysicalPosition<i32> {
|
||||
let view_position = self.view.frame().origin;
|
||||
let position =
|
||||
LogicalPosition { x: safe_area.origin.x as f64, y: safe_area.origin.y as f64 };
|
||||
let scale_factor = self.scale_factor();
|
||||
Ok(position.to_physical(scale_factor))
|
||||
unsafe { self.window.convertPoint_fromView(view_position, Some(&self.view)) };
|
||||
let position = LogicalPosition::new(position.x, position.y);
|
||||
position.to_physical(self.scale_factor())
|
||||
}
|
||||
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
|
||||
let screen_frame = self.screen_frame();
|
||||
let position =
|
||||
LogicalPosition { x: screen_frame.origin.x as f64, y: screen_frame.origin.y as f64 };
|
||||
let scale_factor = self.scale_factor();
|
||||
Ok(position.to_physical(scale_factor))
|
||||
Ok(position.to_physical(self.scale_factor()))
|
||||
}
|
||||
|
||||
pub fn set_outer_position(&self, physical_position: Position) {
|
||||
@@ -181,45 +182,51 @@ impl Inner {
|
||||
self.window.setBounds(bounds);
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
let scale_factor = self.scale_factor();
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
let size = LogicalSize {
|
||||
width: safe_area.size.width as f64,
|
||||
height: safe_area.size.height as f64,
|
||||
};
|
||||
size.to_physical(scale_factor)
|
||||
pub fn surface_size(&self) -> PhysicalSize<u32> {
|
||||
let frame = self.view.frame();
|
||||
let size = LogicalSize::new(frame.size.width, frame.size.height);
|
||||
size.to_physical(self.scale_factor())
|
||||
}
|
||||
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
let scale_factor = self.scale_factor();
|
||||
let screen_frame = self.screen_frame();
|
||||
let size = LogicalSize {
|
||||
width: screen_frame.size.width as f64,
|
||||
height: screen_frame.size.height as f64,
|
||||
let frame = self.window.frame();
|
||||
let size = LogicalSize::new(frame.size.width, frame.size.height);
|
||||
size.to_physical(self.scale_factor())
|
||||
}
|
||||
|
||||
pub fn request_surface_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
|
||||
Some(self.surface_size())
|
||||
}
|
||||
|
||||
pub fn safe_area(&self) -> PhysicalInsets<u32> {
|
||||
let insets = if available!(ios = 11.0, tvos = 11.0, visionos = 1.0) {
|
||||
self.view.safeAreaInsets()
|
||||
} else {
|
||||
// Assume the status bar frame is the only thing that obscures the view
|
||||
let app = UIApplication::sharedApplication(MainThreadMarker::new().unwrap());
|
||||
#[allow(deprecated)]
|
||||
let status_bar_frame = app.statusBarFrame();
|
||||
UIEdgeInsets { top: status_bar_frame.size.height, left: 0.0, bottom: 0.0, right: 0.0 }
|
||||
};
|
||||
size.to_physical(scale_factor)
|
||||
let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right);
|
||||
insets.to_physical(self.scale_factor())
|
||||
}
|
||||
|
||||
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
|
||||
Some(self.inner_size())
|
||||
pub fn set_min_surface_size(&self, _dimensions: Option<Size>) {
|
||||
warn!("`Window::set_min_surface_size` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_min_inner_size(&self, _dimensions: Option<Size>) {
|
||||
warn!("`Window::set_min_inner_size` is ignored on iOS")
|
||||
pub fn set_max_surface_size(&self, _dimensions: Option<Size>) {
|
||||
warn!("`Window::set_max_surface_size` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_max_inner_size(&self, _dimensions: Option<Size>) {
|
||||
warn!("`Window::set_max_inner_size` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
||||
pub fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resize_increments(&self, _increments: Option<Size>) {
|
||||
warn!("`Window::set_resize_increments` is ignored on iOS")
|
||||
pub fn set_surface_resize_increments(&self, _increments: Option<Size>) {
|
||||
warn!("`Window::set_surface_resize_increments` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_resizable(&self, _resizable: bool) {
|
||||
@@ -250,31 +257,31 @@ impl Inner {
|
||||
debug!("`Window::set_cursor` ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
pub fn set_cursor_position(&self, _position: Position) -> Result<(), NotSupportedError> {
|
||||
Err(NotSupportedError::new("set_cursor_position is not supported"))
|
||||
}
|
||||
|
||||
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), NotSupportedError> {
|
||||
Err(NotSupportedError::new("set_cursor_grab is not supported"))
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&self, _visible: bool) {
|
||||
debug!("`Window::set_cursor_visible` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
pub fn drag_window(&self) -> Result<(), NotSupportedError> {
|
||||
Err(NotSupportedError::new("drag_window is not supported"))
|
||||
}
|
||||
|
||||
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), NotSupportedError> {
|
||||
Err(NotSupportedError::new("drag_resize_window is not supported"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show_window_menu(&self, _position: Position) {}
|
||||
|
||||
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), NotSupportedError> {
|
||||
Err(NotSupportedError::new("set_cursor_hittest is not supported"))
|
||||
}
|
||||
|
||||
pub fn set_minimized(&self, _minimized: bool) {
|
||||
@@ -298,12 +305,19 @@ impl Inner {
|
||||
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let uiscreen = match &monitor {
|
||||
Some(Fullscreen::Exclusive(video_mode)) => {
|
||||
let uiscreen = video_mode.monitor.ui_screen(mtm);
|
||||
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
||||
Some(Fullscreen::Exclusive(monitor, video_mode)) => {
|
||||
let monitor = monitor.cast_ref::<MonitorHandle>().unwrap();
|
||||
let uiscreen = monitor.ui_screen(mtm);
|
||||
if let Some(video_mode) =
|
||||
monitor.video_modes_handles().find(|mode| &mode.mode == video_mode)
|
||||
{
|
||||
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
||||
}
|
||||
uiscreen.clone()
|
||||
},
|
||||
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(),
|
||||
Some(Fullscreen::Borderless(Some(monitor))) => {
|
||||
monitor.cast_ref::<MonitorHandle>().unwrap().ui_screen(mtm).clone()
|
||||
},
|
||||
Some(Fullscreen::Borderless(None)) => {
|
||||
self.current_monitor_inner().ui_screen(mtm).clone()
|
||||
},
|
||||
@@ -341,7 +355,7 @@ impl Inner {
|
||||
&& screen_space_bounds.size.width == screen_bounds.size.width
|
||||
&& screen_space_bounds.size.height == screen_bounds.size.height
|
||||
{
|
||||
Some(Fullscreen::Borderless(Some(monitor)))
|
||||
Some(Fullscreen::Borderless(Some(CoreMonitorHandle(Arc::new(monitor)))))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -365,12 +379,24 @@ impl Inner {
|
||||
warn!("`Window::set_ime_cursor_area` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&self, _allowed: bool) {
|
||||
warn!("`Window::set_ime_allowed` is ignored on iOS")
|
||||
/// Show / hide the keyboard. To show the keyboard, we call `becomeFirstResponder`,
|
||||
/// requesting focus for the [WinitView]. Since [WinitView] implements
|
||||
/// [objc2_ui_kit::UIKeyInput], the keyboard will be shown.
|
||||
/// <https://developer.apple.com/documentation/uikit/uiresponder/1621113-becomefirstresponder>
|
||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
||||
if allowed {
|
||||
unsafe {
|
||||
self.view.becomeFirstResponder();
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
self.view.resignFirstResponder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {
|
||||
warn!("`Window::set_ime_allowed` is ignored on iOS")
|
||||
warn!("`Window::set_ime_purpose` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn focus_window(&self) {
|
||||
@@ -403,7 +429,6 @@ impl Inner {
|
||||
self.window.id()
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
|
||||
let mut window_handle = rwh_06::UiKitWindowHandle::new({
|
||||
let ui_view = Retained::as_ptr(&self.view) as _;
|
||||
@@ -440,6 +465,7 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Window {
|
||||
inner: MainThreadBound<Inner>,
|
||||
}
|
||||
@@ -448,30 +474,33 @@ impl Window {
|
||||
pub(crate) fn new(
|
||||
event_loop: &ActiveEventLoop,
|
||||
window_attributes: WindowAttributes,
|
||||
) -> Result<Window, RootOsError> {
|
||||
) -> Result<Window, RequestError> {
|
||||
let mtm = event_loop.mtm;
|
||||
|
||||
if window_attributes.min_inner_size.is_some() {
|
||||
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
|
||||
if window_attributes.min_surface_size.is_some() {
|
||||
warn!("`WindowAttributes::min_surface_size` is ignored on iOS");
|
||||
}
|
||||
if window_attributes.max_inner_size.is_some() {
|
||||
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
|
||||
if window_attributes.max_surface_size.is_some() {
|
||||
warn!("`WindowAttributes::max_surface_size` is ignored on iOS");
|
||||
}
|
||||
|
||||
// TODO: transparency, visible
|
||||
|
||||
#[allow(deprecated)]
|
||||
let main_screen = UIScreen::mainScreen(mtm);
|
||||
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
|
||||
let fullscreen = window_attributes.fullscreen.clone();
|
||||
let screen = match fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
|
||||
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
|
||||
Some(Fullscreen::Exclusive(ref monitor, _))
|
||||
| Some(Fullscreen::Borderless(Some(ref monitor))) => {
|
||||
let monitor = monitor.cast_ref::<MonitorHandle>().unwrap();
|
||||
monitor.ui_screen(mtm)
|
||||
},
|
||||
Some(Fullscreen::Borderless(None)) | None => &main_screen,
|
||||
};
|
||||
|
||||
let screen_bounds = screen.bounds();
|
||||
|
||||
let frame = match window_attributes.inner_size {
|
||||
let frame = match window_attributes.surface_size {
|
||||
Some(dim) => {
|
||||
let scale_factor = screen.scale();
|
||||
let size = dim.to_logical::<f64>(scale_factor as f64);
|
||||
@@ -490,53 +519,16 @@ impl Window {
|
||||
|
||||
let view_controller = WinitViewController::new(mtm, &window_attributes, &view);
|
||||
let window = WinitUIWindow::new(mtm, &window_attributes, frame, &view_controller);
|
||||
|
||||
app_state::set_key_window(mtm, &window);
|
||||
|
||||
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
|
||||
// event on window creation if the DPI factor != 1.0
|
||||
let scale_factor = view.contentScaleFactor();
|
||||
let scale_factor = scale_factor as f64;
|
||||
if scale_factor != 1.0 {
|
||||
let bounds = view.bounds();
|
||||
let screen = window.screen();
|
||||
let screen_space = screen.coordinateSpace();
|
||||
let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
|
||||
let size = LogicalSize {
|
||||
width: screen_frame.size.width as f64,
|
||||
height: screen_frame.size.height as f64,
|
||||
};
|
||||
let window_id = RootWindowId(window.id());
|
||||
app_state::handle_nonuser_events(
|
||||
mtm,
|
||||
std::iter::once(EventWrapper::ScaleFactorChanged(app_state::ScaleFactorChanged {
|
||||
window: window.clone(),
|
||||
scale_factor,
|
||||
suggested_size: size.to_physical(scale_factor),
|
||||
}))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
||||
},
|
||||
))),
|
||||
);
|
||||
}
|
||||
window.makeKeyAndVisible();
|
||||
|
||||
let inner = Inner { window, view_controller, view, gl_or_metal_backed };
|
||||
Ok(Window { inner: MainThreadBound::new(inner, mtm) })
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) {
|
||||
// For now, don't actually do queuing, since it may be less predictable
|
||||
self.maybe_wait_on_main(f)
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
|
||||
self.inner.get_on_main(|inner| f(inner))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub(crate) fn raw_window_handle_rwh_06(
|
||||
&self,
|
||||
@@ -548,7 +540,6 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub(crate) fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
@@ -557,6 +548,268 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
impl rwh_06::HasDisplayHandle for Window {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = self.raw_display_handle_rwh_06()?;
|
||||
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl rwh_06::HasWindowHandle for Window {
|
||||
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = self.raw_window_handle_rwh_06()?;
|
||||
unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl CoreWindow for Window {
|
||||
fn id(&self) -> crate::window::WindowId {
|
||||
self.maybe_wait_on_main(|delegate| delegate.id())
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f64 {
|
||||
self.maybe_wait_on_main(|delegate| delegate.scale_factor())
|
||||
}
|
||||
|
||||
fn request_redraw(&self) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.request_redraw());
|
||||
}
|
||||
|
||||
fn pre_present_notify(&self) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.pre_present_notify());
|
||||
}
|
||||
|
||||
fn reset_dead_keys(&self) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys());
|
||||
}
|
||||
|
||||
fn surface_position(&self) -> PhysicalPosition<i32> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.surface_position())
|
||||
}
|
||||
|
||||
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.outer_position())
|
||||
}
|
||||
|
||||
fn set_outer_position(&self, position: Position) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_outer_position(position));
|
||||
}
|
||||
|
||||
fn surface_size(&self) -> PhysicalSize<u32> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.surface_size())
|
||||
}
|
||||
|
||||
fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.request_surface_size(size))
|
||||
}
|
||||
|
||||
fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.outer_size())
|
||||
}
|
||||
|
||||
fn safe_area(&self) -> PhysicalInsets<u32> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.safe_area())
|
||||
}
|
||||
|
||||
fn set_min_surface_size(&self, min_size: Option<Size>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size))
|
||||
}
|
||||
|
||||
fn set_max_surface_size(&self, max_size: Option<Size>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_max_surface_size(max_size));
|
||||
}
|
||||
|
||||
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.surface_resize_increments())
|
||||
}
|
||||
|
||||
fn set_surface_resize_increments(&self, increments: Option<Size>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_surface_resize_increments(increments));
|
||||
}
|
||||
|
||||
fn set_title(&self, title: &str) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_title(title));
|
||||
}
|
||||
|
||||
fn set_transparent(&self, transparent: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_transparent(transparent));
|
||||
}
|
||||
|
||||
fn set_blur(&self, blur: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_blur(blur));
|
||||
}
|
||||
|
||||
fn set_visible(&self, visible: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_visible(visible));
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> Option<bool> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.is_visible())
|
||||
}
|
||||
|
||||
fn set_resizable(&self, resizable: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_resizable(resizable))
|
||||
}
|
||||
|
||||
fn is_resizable(&self) -> bool {
|
||||
self.maybe_wait_on_main(|delegate| delegate.is_resizable())
|
||||
}
|
||||
|
||||
fn set_enabled_buttons(&self, buttons: WindowButtons) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_enabled_buttons(buttons))
|
||||
}
|
||||
|
||||
fn enabled_buttons(&self) -> WindowButtons {
|
||||
self.maybe_wait_on_main(|delegate| delegate.enabled_buttons())
|
||||
}
|
||||
|
||||
fn set_minimized(&self, minimized: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_minimized(minimized));
|
||||
}
|
||||
|
||||
fn is_minimized(&self) -> Option<bool> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.is_minimized())
|
||||
}
|
||||
|
||||
fn set_maximized(&self, maximized: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_maximized(maximized));
|
||||
}
|
||||
|
||||
fn is_maximized(&self) -> bool {
|
||||
self.maybe_wait_on_main(|delegate| delegate.is_maximized())
|
||||
}
|
||||
|
||||
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen))
|
||||
}
|
||||
|
||||
fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.fullscreen())
|
||||
}
|
||||
|
||||
fn set_decorations(&self, decorations: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_decorations(decorations));
|
||||
}
|
||||
|
||||
fn is_decorated(&self) -> bool {
|
||||
self.maybe_wait_on_main(|delegate| delegate.is_decorated())
|
||||
}
|
||||
|
||||
fn set_window_level(&self, level: WindowLevel) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_window_level(level));
|
||||
}
|
||||
|
||||
fn set_window_icon(&self, window_icon: Option<Icon>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon));
|
||||
}
|
||||
|
||||
fn set_ime_cursor_area(&self, position: Position, size: Size) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_ime_cursor_area(position, size));
|
||||
}
|
||||
|
||||
fn set_ime_allowed(&self, allowed: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_ime_allowed(allowed));
|
||||
}
|
||||
|
||||
fn set_ime_purpose(&self, purpose: ImePurpose) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_ime_purpose(purpose));
|
||||
}
|
||||
|
||||
fn focus_window(&self) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.focus_window());
|
||||
}
|
||||
|
||||
fn has_focus(&self) -> bool {
|
||||
self.maybe_wait_on_main(|delegate| delegate.has_focus())
|
||||
}
|
||||
|
||||
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.request_user_attention(request_type));
|
||||
}
|
||||
|
||||
fn set_theme(&self, theme: Option<Theme>) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_theme(theme));
|
||||
}
|
||||
|
||||
fn theme(&self) -> Option<Theme> {
|
||||
self.maybe_wait_on_main(|delegate| delegate.theme())
|
||||
}
|
||||
|
||||
fn set_content_protected(&self, protected: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_content_protected(protected));
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
self.maybe_wait_on_main(|delegate| delegate.title())
|
||||
}
|
||||
|
||||
fn set_cursor(&self, cursor: Cursor) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_cursor(cursor));
|
||||
}
|
||||
|
||||
fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
|
||||
Ok(self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position))?)
|
||||
}
|
||||
|
||||
fn set_cursor_grab(&self, mode: crate::window::CursorGrabMode) -> Result<(), RequestError> {
|
||||
Ok(self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode))?)
|
||||
}
|
||||
|
||||
fn set_cursor_visible(&self, visible: bool) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.set_cursor_visible(visible))
|
||||
}
|
||||
|
||||
fn drag_window(&self) -> Result<(), RequestError> {
|
||||
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_window())?)
|
||||
}
|
||||
|
||||
fn drag_resize_window(
|
||||
&self,
|
||||
direction: crate::window::ResizeDirection,
|
||||
) -> Result<(), RequestError> {
|
||||
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?)
|
||||
}
|
||||
|
||||
fn show_window_menu(&self, position: Position) {
|
||||
self.maybe_wait_on_main(|delegate| delegate.show_window_menu(position))
|
||||
}
|
||||
|
||||
fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
|
||||
Ok(self.maybe_wait_on_main(|delegate| delegate.set_cursor_hittest(hittest))?)
|
||||
}
|
||||
|
||||
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
|
||||
self.maybe_wait_on_main(|delegate| {
|
||||
delegate.current_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
|
||||
})
|
||||
}
|
||||
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
|
||||
self.maybe_wait_on_main(|delegate| {
|
||||
Box::new(
|
||||
delegate
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
|
||||
self.maybe_wait_on_main(|delegate| {
|
||||
delegate.primary_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
|
||||
})
|
||||
}
|
||||
|
||||
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
|
||||
self
|
||||
}
|
||||
|
||||
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// WindowExtIOS
|
||||
impl Inner {
|
||||
pub fn set_scale_factor(&self, scale_factor: f64) {
|
||||
@@ -619,7 +872,7 @@ impl Inner {
|
||||
|
||||
impl Inner {
|
||||
fn screen_frame(&self) -> CGRect {
|
||||
self.rect_to_screen_space(self.window.bounds())
|
||||
self.rect_to_screen_space(self.window.frame())
|
||||
}
|
||||
|
||||
fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
@@ -631,75 +884,6 @@ impl Inner {
|
||||
let screen_space = self.window.screen().coordinateSpace();
|
||||
self.window.convertRect_fromCoordinateSpace(rect, &screen_space)
|
||||
}
|
||||
|
||||
fn safe_area_screen_space(&self) -> CGRect {
|
||||
let bounds = self.window.bounds();
|
||||
if app_state::os_capabilities().safe_area {
|
||||
let safe_area = self.window.safeAreaInsets();
|
||||
let safe_bounds = CGRect {
|
||||
origin: CGPoint {
|
||||
x: bounds.origin.x + safe_area.left,
|
||||
y: bounds.origin.y + safe_area.top,
|
||||
},
|
||||
size: CGSize {
|
||||
width: bounds.size.width - safe_area.left - safe_area.right,
|
||||
height: bounds.size.height - safe_area.top - safe_area.bottom,
|
||||
},
|
||||
};
|
||||
self.rect_to_screen_space(safe_bounds)
|
||||
} else {
|
||||
let screen_frame = self.rect_to_screen_space(bounds);
|
||||
let status_bar_frame = {
|
||||
let app = UIApplication::sharedApplication(MainThreadMarker::new().unwrap());
|
||||
#[allow(deprecated)]
|
||||
app.statusBarFrame()
|
||||
};
|
||||
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
|
||||
(screen_frame.origin.y, screen_frame.size.height)
|
||||
} else {
|
||||
let y = status_bar_frame.size.height;
|
||||
let height = screen_frame.size.height
|
||||
- (status_bar_frame.size.height - screen_frame.origin.y);
|
||||
(y, height)
|
||||
};
|
||||
CGRect {
|
||||
origin: CGPoint { x: screen_frame.origin.x, y },
|
||||
size: CGSize { width: screen_frame.size.width, height },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId {
|
||||
window: *mut WinitUIWindow,
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub const fn dummy() -> Self {
|
||||
WindowId { window: std::ptr::null_mut() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WindowId> for u64 {
|
||||
fn from(window_id: WindowId) -> Self {
|
||||
window_id.window as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for WindowId {
|
||||
fn from(raw_id: u64) -> Self {
|
||||
Self { window: raw_id as _ }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for WindowId {}
|
||||
unsafe impl Sync for WindowId {}
|
||||
|
||||
impl From<&AnyObject> for WindowId {
|
||||
fn from(window: &AnyObject) -> WindowId {
|
||||
WindowId { window: window as *const _ as _ }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
|
||||
@@ -35,6 +35,9 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
|
||||
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
|
||||
// I can only hope they agree on what the keycodes mean.
|
||||
//
|
||||
// The mapping here is heavily influenced by Firefox' source:
|
||||
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
|
||||
//
|
||||
// Some of the keycodes are likely superfluous for our purposes, and some are ones which are
|
||||
// difficult to test the correctness of, or discover the purpose of. Because of this, they've
|
||||
// either been commented out here, or not included at all.
|
||||
@@ -163,26 +166,26 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
|
||||
122 => KeyCode::Lang1,
|
||||
123 => KeyCode::Lang2,
|
||||
124 => KeyCode::IntlYen,
|
||||
125 => KeyCode::SuperLeft,
|
||||
126 => KeyCode::SuperRight,
|
||||
125 => KeyCode::MetaLeft,
|
||||
126 => KeyCode::MetaRight,
|
||||
127 => KeyCode::ContextMenu,
|
||||
// 128 => KeyCode::STOP,
|
||||
// 129 => KeyCode::AGAIN,
|
||||
// 130 => KeyCode::PROPS,
|
||||
// 131 => KeyCode::UNDO,
|
||||
// 132 => KeyCode::FRONT,
|
||||
// 133 => KeyCode::COPY,
|
||||
// 134 => KeyCode::OPEN,
|
||||
// 135 => KeyCode::PASTE,
|
||||
// 136 => KeyCode::FIND,
|
||||
// 137 => KeyCode::CUT,
|
||||
// 138 => KeyCode::HELP,
|
||||
128 => KeyCode::BrowserStop,
|
||||
129 => KeyCode::Again,
|
||||
130 => KeyCode::Props,
|
||||
131 => KeyCode::Undo,
|
||||
132 => KeyCode::Select, // FRONT
|
||||
133 => KeyCode::Copy,
|
||||
134 => KeyCode::Open,
|
||||
135 => KeyCode::Paste,
|
||||
136 => KeyCode::Find,
|
||||
137 => KeyCode::Cut,
|
||||
138 => KeyCode::Help,
|
||||
// 139 => KeyCode::MENU,
|
||||
// 140 => KeyCode::CALC,
|
||||
140 => KeyCode::LaunchApp2, // CALC
|
||||
// 141 => KeyCode::SETUP,
|
||||
// 142 => KeyCode::SLEEP,
|
||||
// 143 => KeyCode::WAKEUP,
|
||||
// 144 => KeyCode::FILE,
|
||||
143 => KeyCode::WakeUp,
|
||||
144 => KeyCode::LaunchApp1, // FILE
|
||||
// 145 => KeyCode::SENDFILE,
|
||||
// 146 => KeyCode::DELETEFILE,
|
||||
// 147 => KeyCode::XFER,
|
||||
@@ -193,13 +196,13 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
|
||||
// 152 => KeyCode::COFFEE,
|
||||
// 153 => KeyCode::ROTATE_DISPLAY,
|
||||
// 154 => KeyCode::CYCLEWINDOWS,
|
||||
// 155 => KeyCode::MAIL,
|
||||
// 156 => KeyCode::BOOKMARKS,
|
||||
155 => KeyCode::LaunchMail,
|
||||
156 => KeyCode::BrowserFavorites, // BOOKMARKS
|
||||
// 157 => KeyCode::COMPUTER,
|
||||
// 158 => KeyCode::BACK,
|
||||
// 159 => KeyCode::FORWARD,
|
||||
158 => KeyCode::BrowserBack,
|
||||
159 => KeyCode::BrowserForward,
|
||||
// 160 => KeyCode::CLOSECD,
|
||||
// 161 => KeyCode::EJECTCD,
|
||||
161 => KeyCode::Eject, // EJECTCD
|
||||
// 162 => KeyCode::EJECTCLOSECD,
|
||||
163 => KeyCode::MediaTrackNext,
|
||||
164 => KeyCode::MediaPlayPause,
|
||||
@@ -209,9 +212,9 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
|
||||
// 168 => KeyCode::REWIND,
|
||||
// 169 => KeyCode::PHONE,
|
||||
// 170 => KeyCode::ISO,
|
||||
// 171 => KeyCode::CONFIG,
|
||||
// 172 => KeyCode::HOMEPAGE,
|
||||
// 173 => KeyCode::REFRESH,
|
||||
171 => KeyCode::MediaSelect, // CONFIG
|
||||
172 => KeyCode::BrowserHome,
|
||||
173 => KeyCode::BrowserRefresh,
|
||||
// 174 => KeyCode::EXIT,
|
||||
// 175 => KeyCode::MOVE,
|
||||
// 176 => KeyCode::EDIT,
|
||||
@@ -250,7 +253,7 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
|
||||
// 214 => KeyCode::QUESTION,
|
||||
// 215 => KeyCode::EMAIL,
|
||||
// 216 => KeyCode::CHAT,
|
||||
// 217 => KeyCode::SEARCH,
|
||||
217 => KeyCode::BrowserSearch,
|
||||
// 218 => KeyCode::CONNECT,
|
||||
// 219 => KeyCode::FINANCE,
|
||||
// 220 => KeyCode::SPORT,
|
||||
@@ -416,13 +419,35 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
|
||||
KeyCode::Lang1 => Some(122),
|
||||
KeyCode::Lang2 => Some(123),
|
||||
KeyCode::IntlYen => Some(124),
|
||||
KeyCode::SuperLeft => Some(125),
|
||||
KeyCode::SuperRight => Some(126),
|
||||
KeyCode::MetaLeft => Some(125),
|
||||
KeyCode::MetaRight => Some(126),
|
||||
KeyCode::ContextMenu => Some(127),
|
||||
KeyCode::BrowserStop => Some(128),
|
||||
KeyCode::Again => Some(129),
|
||||
KeyCode::Props => Some(130),
|
||||
KeyCode::Undo => Some(131),
|
||||
KeyCode::Select => Some(132),
|
||||
KeyCode::Copy => Some(133),
|
||||
KeyCode::Open => Some(134),
|
||||
KeyCode::Paste => Some(135),
|
||||
KeyCode::Find => Some(136),
|
||||
KeyCode::Cut => Some(137),
|
||||
KeyCode::Help => Some(138),
|
||||
KeyCode::LaunchApp2 => Some(140),
|
||||
KeyCode::WakeUp => Some(143),
|
||||
KeyCode::LaunchApp1 => Some(144),
|
||||
KeyCode::LaunchMail => Some(155),
|
||||
KeyCode::BrowserFavorites => Some(156),
|
||||
KeyCode::BrowserBack => Some(158),
|
||||
KeyCode::BrowserForward => Some(159),
|
||||
KeyCode::Eject => Some(161),
|
||||
KeyCode::MediaTrackNext => Some(163),
|
||||
KeyCode::MediaPlayPause => Some(164),
|
||||
KeyCode::MediaTrackPrevious => Some(165),
|
||||
KeyCode::MediaStop => Some(166),
|
||||
KeyCode::MediaSelect => Some(171),
|
||||
KeyCode::BrowserHome => Some(172),
|
||||
KeyCode::BrowserRefresh => Some(173),
|
||||
KeyCode::F13 => Some(183),
|
||||
KeyCode::F14 => Some(184),
|
||||
KeyCode::F15 => Some(185),
|
||||
@@ -435,6 +460,7 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
|
||||
KeyCode::F22 => Some(192),
|
||||
KeyCode::F23 => Some(193),
|
||||
KeyCode::F24 => Some(194),
|
||||
KeyCode::BrowserSearch => Some(217),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -596,16 +622,20 @@ pub fn keysym_to_key(keysym: u32) -> Key {
|
||||
keysyms::Control_R => NamedKey::Control,
|
||||
keysyms::Caps_Lock => NamedKey::CapsLock,
|
||||
// keysyms::Shift_Lock => NamedKey::ShiftLock,
|
||||
|
||||
// keysyms::Meta_L => NamedKey::Meta,
|
||||
// keysyms::Meta_R => NamedKey::Meta,
|
||||
keysyms::Alt_L => NamedKey::Alt,
|
||||
keysyms::Alt_R => NamedKey::Alt,
|
||||
keysyms::Super_L => NamedKey::Super,
|
||||
keysyms::Super_R => NamedKey::Super,
|
||||
#[allow(deprecated)]
|
||||
keysyms::Hyper_L => NamedKey::Hyper,
|
||||
#[allow(deprecated)]
|
||||
keysyms::Hyper_R => NamedKey::Hyper,
|
||||
|
||||
// Browsers map X11's Super keys to Meta, so we do that as well.
|
||||
keysyms::Super_L => NamedKey::Meta,
|
||||
keysyms::Super_R => NamedKey::Meta,
|
||||
// The actual Meta keys do not seem to be used by browsers, so we don't do that either.
|
||||
// keysyms::Meta_L => NamedKey::Super,
|
||||
// keysyms::Meta_R => NamedKey::Super,
|
||||
|
||||
// XKB function and modifier keys
|
||||
// keysyms::ISO_Lock => NamedKey::IsoLock,
|
||||
// keysyms::ISO_Level2_Latch => NamedKey::IsoLevel2Latch,
|
||||
@@ -638,7 +668,7 @@ pub fn keysym_to_key(keysym: u32) -> Key {
|
||||
// keysyms::ISO_Release_Margin_Left => NamedKey::IsoReleaseMarginLeft,
|
||||
// keysyms::ISO_Release_Margin_Right => NamedKey::IsoReleaseMarginRight,
|
||||
// keysyms::ISO_Release_Both_Margins => NamedKey::IsoReleaseBothMargins,
|
||||
// keysyms::ISO_Fast_Cursor_Left => NamedKey::IsoFastCursorLeft,
|
||||
// keysyms::ISO_Fast_Cursor_Left => NamedKey::IsoFastPointerLeft,
|
||||
// keysyms::ISO_Fast_Cursor_Right => NamedKey::IsoFastCursorRight,
|
||||
// keysyms::ISO_Fast_Cursor_Up => NamedKey::IsoFastCursorUp,
|
||||
// keysyms::ISO_Fast_Cursor_Down => NamedKey::IsoFastCursorDown,
|
||||
@@ -698,7 +728,7 @@ pub fn keysym_to_key(keysym: u32) -> Key {
|
||||
keysyms::_3270_PrintScreen => NamedKey::PrintScreen,
|
||||
keysyms::_3270_Enter => NamedKey::Enter,
|
||||
|
||||
keysyms::space => NamedKey::Space,
|
||||
keysyms::space => return Key::Character(" ".into()),
|
||||
// exclam..Sinh_kunddaliya
|
||||
|
||||
// XFree86
|
||||
|
||||
@@ -16,7 +16,6 @@ use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle
|
||||
|
||||
use crate::event::{ElementState, KeyEvent};
|
||||
use crate::keyboard::{Key, KeyLocation};
|
||||
use crate::platform_impl::KeyEventExtra;
|
||||
use crate::utils::Lazy;
|
||||
|
||||
mod compose;
|
||||
@@ -183,7 +182,7 @@ pub struct KeyContext<'a> {
|
||||
scratch_buffer: &'a mut Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> KeyContext<'a> {
|
||||
impl KeyContext<'_> {
|
||||
pub fn process_key_event(
|
||||
&mut self,
|
||||
keycode: u32,
|
||||
@@ -198,9 +197,16 @@ impl<'a> KeyContext<'a> {
|
||||
let (key_without_modifiers, _) = event.key_without_modifiers();
|
||||
let text_with_all_modifiers = event.text_with_all_modifiers();
|
||||
|
||||
let platform_specific = KeyEventExtra { text_with_all_modifiers, key_without_modifiers };
|
||||
|
||||
KeyEvent { physical_key, logical_key, text, location, state, repeat, platform_specific }
|
||||
KeyEvent {
|
||||
physical_key,
|
||||
logical_key,
|
||||
text,
|
||||
location,
|
||||
state,
|
||||
repeat,
|
||||
text_with_all_modifiers,
|
||||
key_without_modifiers,
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
|
||||
|
||||
@@ -183,7 +183,7 @@ impl From<ModifiersState> for crate::keyboard::ModifiersState {
|
||||
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift);
|
||||
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl);
|
||||
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt);
|
||||
to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo);
|
||||
to_mods.set(crate::keyboard::ModifiersState::META, mods.logo);
|
||||
to_mods
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,38 +3,20 @@
|
||||
#[cfg(all(not(x11_platform), not(wayland_platform)))]
|
||||
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::env;
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{env, fmt};
|
||||
#[cfg(x11_platform)]
|
||||
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
|
||||
|
||||
use smol_str::SmolStr;
|
||||
|
||||
pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
|
||||
#[cfg(x11_platform)]
|
||||
use self::x11::{X11Error, XConnection, XError, XNotSupported};
|
||||
use crate::application::ApplicationHandler;
|
||||
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{EventLoopError, ExternalError, NotSupportedError};
|
||||
use crate::event_loop::{ActiveEventLoop, AsyncRequestSerial};
|
||||
use crate::icon::Icon;
|
||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||
use crate::keyboard::Key;
|
||||
#[cfg(x11_platform)]
|
||||
use crate::dpi::Size;
|
||||
use crate::error::{EventLoopError, NotSupportedError};
|
||||
use crate::event_loop::ActiveEventLoop;
|
||||
use crate::platform::pump_events::PumpStatus;
|
||||
#[cfg(x11_platform)]
|
||||
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
#[cfg(x11_platform)]
|
||||
use crate::utils::Lazy;
|
||||
use crate::window::{
|
||||
ActivationToken, Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
WindowButtons, WindowLevel,
|
||||
};
|
||||
use crate::platform::x11::WindowType as XWindowType;
|
||||
use crate::window::ActivationToken;
|
||||
|
||||
pub(crate) mod common;
|
||||
#[cfg(wayland_platform)]
|
||||
@@ -108,101 +90,6 @@ impl Default for PlatformSpecificWindowAttributes {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
|
||||
Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OsError {
|
||||
Misc(&'static str),
|
||||
#[cfg(x11_platform)]
|
||||
XError(Arc<X11Error>),
|
||||
#[cfg(wayland_platform)]
|
||||
WaylandError(Arc<wayland::WaylandError>),
|
||||
}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
OsError::Misc(e) => _f.pad(e),
|
||||
#[cfg(x11_platform)]
|
||||
OsError::XError(ref e) => fmt::Display::fmt(e, _f),
|
||||
#[cfg(wayland_platform)]
|
||||
OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Window {
|
||||
#[cfg(x11_platform)]
|
||||
X(x11::Window),
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(wayland::Window),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(u64);
|
||||
|
||||
impl From<WindowId> for u64 {
|
||||
fn from(window_id: WindowId) -> Self {
|
||||
window_id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for WindowId {
|
||||
fn from(raw_id: u64) -> Self {
|
||||
Self(raw_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub const fn dummy() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DeviceId {
|
||||
#[cfg(x11_platform)]
|
||||
X(x11::DeviceId),
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(wayland::DeviceId),
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
pub const fn dummy() -> Self {
|
||||
#[cfg(wayland_platform)]
|
||||
return DeviceId::Wayland(wayland::DeviceId::dummy());
|
||||
#[cfg(all(not(wayland_platform), x11_platform))]
|
||||
return DeviceId::X(x11::DeviceId::dummy());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum FingerId {
|
||||
#[cfg(x11_platform)]
|
||||
X(x11::FingerId),
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(wayland::FingerId),
|
||||
}
|
||||
|
||||
impl FingerId {
|
||||
pub const fn dummy() -> Self {
|
||||
#[cfg(wayland_platform)]
|
||||
return FingerId::Wayland(wayland::FingerId::dummy());
|
||||
#[cfg(all(not(wayland_platform), x11_platform))]
|
||||
return FingerId::X(x11::FingerId::dummy());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum MonitorHandle {
|
||||
#[cfg(x11_platform)]
|
||||
X(x11::MonitorHandle),
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(wayland::MonitorHandle),
|
||||
}
|
||||
|
||||
/// `x11_or_wayland!(match expr; Enum(foo) => foo.something())`
|
||||
/// expands to the equivalent of
|
||||
/// ```ignore
|
||||
@@ -231,443 +118,7 @@ macro_rules! x11_or_wayland {
|
||||
};
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
x11_or_wayland!(match self; MonitorHandle(m) => m.name())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_identifier(&self) -> u32 {
|
||||
x11_or_wayland!(match self; MonitorHandle(m) => m.native_identifier())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
|
||||
x11_or_wayland!(match self; MonitorHandle(m) => m.position())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as _)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
x11_or_wayland!(match self; MonitorHandle(m) => m.current_video_mode())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> {
|
||||
x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum VideoModeHandle {
|
||||
#[cfg(x11_platform)]
|
||||
X(x11::VideoModeHandle),
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(wayland::VideoModeHandle),
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
x11_or_wayland!(match self; VideoModeHandle(m) => m.size())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
x11_or_wayland!(match self; VideoModeHandle(m) => m.bit_depth())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
x11_or_wayland!(match self; VideoModeHandle(m) => m.refresh_rate_millihertz())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
x11_or_wayland!(match self; VideoModeHandle(m) => m.monitor(); as MonitorHandle)
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
|
||||
f(self)
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
|
||||
f(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
x11_or_wayland!(match self; Window(w) => w.id())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, title: &str) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_title(title));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_transparent(&self, transparent: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_transparent(transparent));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_blur(&self, blur: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_blur(blur));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
x11_or_wayland!(match self; Window(w) => w.is_visible())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
x11_or_wayland!(match self; Window(w) => w.outer_position())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
x11_or_wayland!(match self; Window(w) => w.inner_position())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_outer_position(&self, position: Position) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_outer_position(position))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
x11_or_wayland!(match self; Window(w) => w.inner_size())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
x11_or_wayland!(match self; Window(w) => w.outer_size())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
|
||||
x11_or_wayland!(match self; Window(w) => w.request_inner_size(size))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
|
||||
x11_or_wayland!(match self; Window(w) => w.request_activation_token())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_min_inner_size(dimensions))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
||||
x11_or_wayland!(match self; Window(w) => w.resize_increments())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resize_increments(&self, increments: Option<Size>) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_resize_increments(increments))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_resizable(&self) -> bool {
|
||||
x11_or_wayland!(match self; Window(w) => w.is_resizable())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_enabled_buttons(buttons))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn enabled_buttons(&self) -> WindowButtons {
|
||||
x11_or_wayland!(match self; Window(w) => w.enabled_buttons())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, cursor: Cursor) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_cursor(cursor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
x11_or_wayland!(match self; Window(window) => window.drag_window())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
|
||||
x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show_window_menu(&self, position: Position) {
|
||||
x11_or_wayland!(match self; Window(w) => w.show_window_menu(position))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
x11_or_wayland!(match self; Window(w) => w.scale_factor())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_cursor_position(position))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
x11_or_wayland!(match self; Window(w) => w.is_maximized())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_minimized(&self) -> Option<bool> {
|
||||
x11_or_wayland!(match self; Window(w) => w.is_minimized())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
x11_or_wayland!(match self; Window(w) => w.fullscreen())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
x11_or_wayland!(match self; Window(w) => w.is_decorated())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_level(&self, level: WindowLevel) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_window_level(level))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_window_icon(window_icon.map(|icon| icon.inner)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_cursor_area(&self, position: Position, size: Size) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_ime_cursor_area(position, size))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset_dead_keys(&self) {
|
||||
common::xkb::reset_dead_keys()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_purpose(&self, purpose: ImePurpose) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_ime_purpose(purpose))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn focus_window(&self) {
|
||||
x11_or_wayland!(match self; Window(w) => w.focus_window())
|
||||
}
|
||||
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_redraw(&self) {
|
||||
x11_or_wayland!(match self; Window(w) => w.request_redraw())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pre_present_notify(&self) {
|
||||
x11_or_wayland!(match self; Window(w) => w.pre_present_notify())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||
Some(x11_or_wayland!(match self; Window(w) => w.current_monitor()?; as MonitorHandle))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
match self {
|
||||
#[cfg(x11_platform)]
|
||||
Window::X(ref window) => {
|
||||
window.available_monitors().into_iter().map(MonitorHandle::X).collect()
|
||||
},
|
||||
#[cfg(wayland_platform)]
|
||||
Window::Wayland(ref window) => {
|
||||
window.available_monitors().into_iter().map(MonitorHandle::Wayland).collect()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
Some(x11_or_wayland!(match self; Window(w) => w.primary_monitor()?; as MonitorHandle))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_06())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
||||
x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_06())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_theme(&self, theme: Option<Theme>) {
|
||||
x11_or_wayland!(match self; Window(window) => window.set_theme(theme))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn theme(&self) -> Option<Theme> {
|
||||
x11_or_wayland!(match self; Window(window) => window.theme())
|
||||
}
|
||||
|
||||
pub fn set_content_protected(&self, protected: bool) {
|
||||
x11_or_wayland!(match self; Window(window) => window.set_content_protected(protected))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_focus(&self) -> bool {
|
||||
x11_or_wayland!(match self; Window(window) => window.has_focus())
|
||||
}
|
||||
|
||||
pub fn title(&self) -> String {
|
||||
x11_or_wayland!(match self; Window(window) => window.title())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeyEventExtra {
|
||||
pub text_with_all_modifiers: Option<SmolStr>,
|
||||
pub key_without_modifiers: Key,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub(crate) enum PlatformCustomCursor {
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(wayland::CustomCursor),
|
||||
#[cfg(x11_platform)]
|
||||
X(x11::CustomCursor),
|
||||
}
|
||||
|
||||
/// Hooks for X11 errors.
|
||||
#[cfg(x11_platform)]
|
||||
pub(crate) static mut XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
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().unwrap();
|
||||
if let Ok(ref xconn) = *xconn_lock {
|
||||
// Call all the hooks.
|
||||
let mut error_handled = false;
|
||||
for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() {
|
||||
error_handled |= hook(display as *mut _, event as *mut _);
|
||||
}
|
||||
|
||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||
// which do not require initialization.
|
||||
let mut buf: [MaybeUninit<c_char>; 1024] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||
unsafe {
|
||||
(xconn.xlib.XGetErrorText)(
|
||||
display,
|
||||
(*event).error_code as c_int,
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
buf.len() as c_int,
|
||||
)
|
||||
};
|
||||
let description =
|
||||
unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy();
|
||||
|
||||
let error = unsafe {
|
||||
XError {
|
||||
description: description.into_owned(),
|
||||
error_code: (*event).error_code,
|
||||
request_code: (*event).request_code,
|
||||
minor_code: (*event).minor_code,
|
||||
}
|
||||
};
|
||||
|
||||
// Don't log error.
|
||||
if !error_handled {
|
||||
tracing::error!("X11 error: {:#?}", error);
|
||||
// XXX only update the error, if it wasn't handled by any of the hooks.
|
||||
*xconn.latest_error.lock().unwrap() = Some(error);
|
||||
}
|
||||
}
|
||||
// Fun fact: this return value is completely ignored.
|
||||
0
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventLoop {
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(Box<wayland::EventLoop>),
|
||||
@@ -675,14 +126,6 @@ pub enum EventLoop {
|
||||
X(x11::EventLoop),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum EventLoopProxy {
|
||||
#[cfg(x11_platform)]
|
||||
X(x11::EventLoopProxy),
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(wayland::EventLoopProxy),
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
pub(crate) fn new(
|
||||
attributes: &PlatformSpecificEventLoopAttributes,
|
||||
@@ -692,8 +135,8 @@ impl EventLoop {
|
||||
"Initializing the event loop outside of the main thread is a significant \
|
||||
cross-platform compatibility hazard. If you absolutely need to create an \
|
||||
EventLoop on a different thread, you can use the \
|
||||
`EventLoopBuilderExtX11::any_thread` or `EventLoopBuilderExtWayland::any_thread` \
|
||||
functions."
|
||||
`EventLoopBuilderExtX11::with_any_thread` or \
|
||||
`EventLoopBuilderExtWayland::with_any_thread` functions."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -728,16 +171,16 @@ impl EventLoop {
|
||||
} else {
|
||||
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
|
||||
};
|
||||
return Err(EventLoopError::Os(os_error!(OsError::Misc(msg))));
|
||||
return Err(NotSupportedError::new(msg).into());
|
||||
},
|
||||
};
|
||||
|
||||
// Create the display based on the backend.
|
||||
match backend {
|
||||
#[cfg(wayland_platform)]
|
||||
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
|
||||
Backend::Wayland => EventLoop::new_wayland_any_thread(),
|
||||
#[cfg(x11_platform)]
|
||||
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
|
||||
Backend::X => EventLoop::new_x11_any_thread(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -748,12 +191,7 @@ impl EventLoop {
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
fn new_x11_any_thread() -> Result<EventLoop, EventLoopError> {
|
||||
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
|
||||
Ok(xconn) => xconn.clone(),
|
||||
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
|
||||
};
|
||||
|
||||
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
|
||||
x11::EventLoop::new().map(EventLoop::X)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -802,65 +240,6 @@ impl AsRawFd for EventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
pub fn wake_up(&self) {
|
||||
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.wake_up())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum OwnedDisplayHandle {
|
||||
#[cfg(x11_platform)]
|
||||
X(Arc<XConnection>),
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(wayland_client::Connection),
|
||||
}
|
||||
|
||||
impl OwnedDisplayHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
||||
use std::ptr::NonNull;
|
||||
|
||||
match self {
|
||||
#[cfg(x11_platform)]
|
||||
Self::X(xconn) => Ok(rwh_06::XlibDisplayHandle::new(
|
||||
NonNull::new(xconn.display.cast()),
|
||||
xconn.default_screen_index() as _,
|
||||
)
|
||||
.into()),
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
Self::Wayland(conn) => {
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
Ok(rwh_06::WaylandDisplayHandle::new(
|
||||
NonNull::new(conn.display().id().as_ptr().cast()).unwrap(),
|
||||
)
|
||||
.into())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for OwnedDisplayHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
#[cfg(x11_platform)]
|
||||
(Self::X(this), Self::X(other)) => Arc::as_ptr(this).eq(&Arc::as_ptr(other)),
|
||||
#[cfg(wayland_platform)]
|
||||
(Self::Wayland(this), Self::Wayland(other)) => this.eq(other),
|
||||
#[cfg(all(x11_platform, wayland_platform))]
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for OwnedDisplayHandle {}
|
||||
|
||||
/// Returns the minimum `Option<Duration>`, taking into account that `None`
|
||||
/// equates to an infinite timeout, not a zero timeout (so can't just use
|
||||
/// `Option::min`)
|
||||
|
||||
@@ -1,42 +1,58 @@
|
||||
//! The event-loop routines.
|
||||
|
||||
use std::any::Any;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::io::Result as IOResult;
|
||||
use std::mem;
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use sctk::reexports::calloop::Error as CalloopError;
|
||||
use calloop::ping::Ping;
|
||||
use rustix::event::{PollFd, PollFlags};
|
||||
use rustix::pipe::{self, PipeFlags};
|
||||
use sctk::reexports::calloop_wayland_source::WaylandSource;
|
||||
use sctk::reexports::client::{globals, Connection, QueueHandle};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::cursor::OnlyCursorImage;
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::error::{EventLoopError, ExternalError, OsError as RootOsError};
|
||||
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
|
||||
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents};
|
||||
use crate::error::{EventLoopError, NotSupportedError, OsError, RequestError};
|
||||
use crate::event::{DeviceEvent, StartCause, SurfaceSizeWriter, WindowEvent};
|
||||
use crate::event_loop::{
|
||||
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
|
||||
OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
||||
};
|
||||
use crate::monitor::MonitorHandle as CoreMonitorHandle;
|
||||
use crate::platform::pump_events::PumpStatus;
|
||||
use crate::platform_impl::platform::min_timeout;
|
||||
use crate::platform_impl::{OsError, PlatformCustomCursor};
|
||||
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
|
||||
use crate::platform_impl::wayland::types::cursor::WaylandCustomCursor;
|
||||
use crate::window::{CustomCursor as CoreCustomCursor, CustomCursorSource, Theme};
|
||||
|
||||
mod proxy;
|
||||
pub mod sink;
|
||||
|
||||
pub use proxy::EventLoopProxy;
|
||||
use proxy::EventLoopProxy;
|
||||
use sink::EventSink;
|
||||
|
||||
use super::output::MonitorHandle;
|
||||
use super::state::{WindowCompositorUpdate, WinitState};
|
||||
use super::window::state::FrameCallbackState;
|
||||
use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
|
||||
use super::{logical_to_physical_rounded, WindowId};
|
||||
pub use crate::event_loop::EventLoopProxy as CoreEventLoopProxy;
|
||||
|
||||
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Event {
|
||||
WindowEvent { window_id: WindowId, event: WindowEvent },
|
||||
DeviceEvent { event: DeviceEvent },
|
||||
}
|
||||
|
||||
/// The Wayland event loop.
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoop {
|
||||
/// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop
|
||||
loop_running: bool,
|
||||
@@ -50,39 +66,34 @@ pub struct EventLoop {
|
||||
wayland_dispatcher: WaylandDispatcher,
|
||||
|
||||
/// Connection to the wayland server.
|
||||
connection: Connection,
|
||||
handle: Arc<OwnedDisplayHandle>,
|
||||
|
||||
/// Event loop window target.
|
||||
pub(crate) active_event_loop: ActiveEventLoop,
|
||||
active_event_loop: ActiveEventLoop,
|
||||
|
||||
// XXX drop after everything else, just to be safe.
|
||||
/// Calloop's event loop.
|
||||
event_loop: calloop::EventLoop<'static, WinitState>,
|
||||
|
||||
pump_event_notifier: Option<PumpEventNotifier>,
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new() -> Result<EventLoop, EventLoopError> {
|
||||
macro_rules! map_err {
|
||||
($e:expr, $err:expr) => {
|
||||
$e.map_err(|error| os_error!($err(error).into()))
|
||||
};
|
||||
}
|
||||
|
||||
let connection = map_err!(Connection::connect_to_env(), WaylandError::Connection)?;
|
||||
let connection = Connection::connect_to_env().map_err(|err| os_error!(err))?;
|
||||
|
||||
let (globals, mut event_queue) =
|
||||
map_err!(globals::registry_queue_init(&connection), WaylandError::Global)?;
|
||||
globals::registry_queue_init(&connection).map_err(|err| os_error!(err))?;
|
||||
let queue_handle = event_queue.handle();
|
||||
|
||||
let event_loop =
|
||||
map_err!(calloop::EventLoop::<WinitState>::try_new(), WaylandError::Calloop)?;
|
||||
calloop::EventLoop::<WinitState>::try_new().map_err(|err| os_error!(err))?;
|
||||
|
||||
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())
|
||||
.map_err(|error| os_error!(error))?;
|
||||
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?;
|
||||
|
||||
// NOTE: do a roundtrip after binding the globals to prevent potential
|
||||
// races with the server.
|
||||
map_err!(event_queue.roundtrip(&mut winit_state), WaylandError::Dispatch)?;
|
||||
event_queue.roundtrip(&mut winit_state).map_err(|err| os_error!(err))?;
|
||||
|
||||
// Register Wayland source.
|
||||
let wayland_source = WaylandSource::new(connection.clone(), event_queue);
|
||||
@@ -98,48 +109,43 @@ impl EventLoop {
|
||||
result
|
||||
});
|
||||
|
||||
map_err!(
|
||||
event_loop.handle().register_dispatcher(wayland_dispatcher.clone()),
|
||||
WaylandError::Calloop
|
||||
)?;
|
||||
event_loop
|
||||
.handle()
|
||||
.register_dispatcher(wayland_dispatcher.clone())
|
||||
.map_err(|err| os_error!(err))?;
|
||||
|
||||
// Setup the user proxy.
|
||||
let (ping, ping_source) = calloop::ping::make_ping().unwrap();
|
||||
let result = event_loop
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(ping_source, move |_, _, winit_state: &mut WinitState| {
|
||||
winit_state.dispatched_events = true;
|
||||
winit_state.proxy_wake_up = true;
|
||||
})
|
||||
.map_err(|error| error.error);
|
||||
map_err!(result, WaylandError::Calloop)?;
|
||||
.map_err(|err| os_error!(err))?;
|
||||
|
||||
// An event's loop awakener to wake up for window events from winit's windows.
|
||||
let (event_loop_awakener, event_loop_awakener_source) = map_err!(
|
||||
calloop::ping::make_ping()
|
||||
.map_err(|error| CalloopError::OtherError(Box::new(error).into())),
|
||||
WaylandError::Calloop
|
||||
)?;
|
||||
let (event_loop_awakener, event_loop_awakener_source) =
|
||||
calloop::ping::make_ping().map_err(|err| os_error!(err))?;
|
||||
|
||||
let result = event_loop
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(event_loop_awakener_source, move |_, _, winit_state: &mut WinitState| {
|
||||
// Mark that we have something to dispatch.
|
||||
winit_state.dispatched_events = true;
|
||||
})
|
||||
.map_err(|error| error.error);
|
||||
map_err!(result, WaylandError::Calloop)?;
|
||||
.map_err(|err| os_error!(err))?;
|
||||
|
||||
let handle = Arc::new(OwnedDisplayHandle::new(connection));
|
||||
let active_event_loop = ActiveEventLoop {
|
||||
connection: connection.clone(),
|
||||
handle: handle.clone(),
|
||||
wayland_dispatcher: wayland_dispatcher.clone(),
|
||||
event_loop_awakener,
|
||||
event_loop_proxy: EventLoopProxy::new(ping),
|
||||
event_loop_proxy: EventLoopProxy::new(ping).into(),
|
||||
queue_handle,
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
exit: Cell::new(None),
|
||||
state: RefCell::new(winit_state),
|
||||
wayland_callback: Default::default(),
|
||||
};
|
||||
|
||||
let event_loop = Self {
|
||||
@@ -147,10 +153,11 @@ impl EventLoop {
|
||||
compositor_updates: Vec::new(),
|
||||
buffer_sink: EventSink::default(),
|
||||
window_ids: Vec::new(),
|
||||
connection,
|
||||
handle,
|
||||
wayland_dispatcher,
|
||||
event_loop,
|
||||
active_event_loop,
|
||||
pump_event_notifier: None,
|
||||
};
|
||||
|
||||
Ok(event_loop)
|
||||
@@ -205,13 +212,27 @@ impl EventLoop {
|
||||
if !self.exiting() {
|
||||
self.poll_events_with_timeout(timeout, &mut app);
|
||||
}
|
||||
|
||||
if let Some(code) = self.exit_code() {
|
||||
self.loop_running = false;
|
||||
|
||||
app.exiting(&self.active_event_loop);
|
||||
|
||||
PumpStatus::Exit(code)
|
||||
} else {
|
||||
// NOTE: spawn a wake-up thread, thus if we have code reading the wayland connection
|
||||
// in parallel to winit, we ensure that the loop itself is marked as having events.
|
||||
if timeout.is_some() && self.pump_event_notifier.is_none() {
|
||||
self.pump_event_notifier = Some(PumpEventNotifier::spawn(
|
||||
self.active_event_loop.handle.connection.clone(),
|
||||
self.active_event_loop.event_loop_awakener.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(pump_event_notifier) = self.pump_event_notifier.as_ref() {
|
||||
// Notify that we don't have to wait, since we're out of winit.
|
||||
*pump_event_notifier.control.0.lock().unwrap() = PumpEventNotifierAction::Monitor;
|
||||
pump_event_notifier.control.1.notify_one();
|
||||
}
|
||||
|
||||
PumpStatus::Continue
|
||||
}
|
||||
}
|
||||
@@ -241,7 +262,7 @@ impl EventLoop {
|
||||
//
|
||||
// Checking for flush error is essential to perform an exit with error, since
|
||||
// once we have a protocol error, we could get stuck retrying...
|
||||
if self.connection.flush().is_err() {
|
||||
if self.handle.connection.flush().is_err() {
|
||||
self.set_exit_code(1);
|
||||
return;
|
||||
}
|
||||
@@ -275,7 +296,10 @@ impl EventLoop {
|
||||
|
||||
// Reduce spurious wake-ups.
|
||||
let dispatched_events = self.with_state(|state| state.dispatched_events);
|
||||
if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events {
|
||||
if matches!(cause, StartCause::WaitCancelled { .. })
|
||||
&& !dispatched_events
|
||||
&& timeout.is_none()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -298,10 +322,6 @@ impl EventLoop {
|
||||
|
||||
app.new_events(&self.active_event_loop, cause);
|
||||
|
||||
if let Some(callback) = self.active_event_loop.wayland_callback.get() {
|
||||
callback(app);
|
||||
}
|
||||
|
||||
// NB: For consistency all platforms must call `can_create_surfaces` even though Wayland
|
||||
// applications don't themselves have a formal surface destroy/create lifecycle.
|
||||
if cause == StartCause::Init {
|
||||
@@ -323,24 +343,23 @@ impl EventLoop {
|
||||
let windows = state.windows.get_mut();
|
||||
let window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
let scale_factor = window.scale_factor();
|
||||
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
|
||||
let size = logical_to_physical_rounded(window.surface_size(), scale_factor);
|
||||
(size, scale_factor)
|
||||
});
|
||||
|
||||
// Stash the old window size.
|
||||
let old_physical_size = physical_size;
|
||||
|
||||
let new_inner_size = Arc::new(Mutex::new(physical_size));
|
||||
let root_window_id = crate::window::WindowId(window_id);
|
||||
let new_surface_size = Arc::new(Mutex::new(physical_size));
|
||||
let event = WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
|
||||
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)),
|
||||
};
|
||||
|
||||
app.window_event(&self.active_event_loop, root_window_id, event);
|
||||
app.window_event(&self.active_event_loop, window_id, event);
|
||||
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
let physical_size = *new_surface_size.lock().unwrap();
|
||||
drop(new_surface_size);
|
||||
|
||||
// Resize the window when user altered the size.
|
||||
if old_physical_size != physical_size {
|
||||
@@ -350,7 +369,7 @@ impl EventLoop {
|
||||
|
||||
let new_logical_size: LogicalSize<f64> =
|
||||
physical_size.to_logical(scale_factor);
|
||||
window.request_inner_size(new_logical_size.into());
|
||||
window.request_surface_size(new_logical_size.into());
|
||||
});
|
||||
|
||||
// Make it queue resize.
|
||||
@@ -366,7 +385,7 @@ impl EventLoop {
|
||||
let window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
|
||||
let size = logical_to_physical_rounded(window.surface_size(), scale_factor);
|
||||
|
||||
// Mark the window as needed a redraw.
|
||||
state
|
||||
@@ -380,13 +399,11 @@ impl EventLoop {
|
||||
size
|
||||
});
|
||||
|
||||
let window_id = crate::window::WindowId(window_id);
|
||||
let event = WindowEvent::Resized(physical_size);
|
||||
let event = WindowEvent::SurfaceResized(physical_size);
|
||||
app.window_event(&self.active_event_loop, window_id, event);
|
||||
}
|
||||
|
||||
if compositor_update.close_window {
|
||||
let window_id = crate::window::WindowId(window_id);
|
||||
app.window_event(&self.active_event_loop, window_id, WindowEvent::CloseRequested);
|
||||
}
|
||||
}
|
||||
@@ -400,10 +417,9 @@ impl EventLoop {
|
||||
Event::WindowEvent { window_id, event } => {
|
||||
app.window_event(&self.active_event_loop, window_id, event)
|
||||
},
|
||||
Event::DeviceEvent { device_id, event } => {
|
||||
app.device_event(&self.active_event_loop, device_id, event)
|
||||
Event::DeviceEvent { event } => {
|
||||
app.device_event(&self.active_event_loop, None, event)
|
||||
},
|
||||
_ => unreachable!("event which is neither device nor window event."),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,10 +432,9 @@ impl EventLoop {
|
||||
Event::WindowEvent { window_id, event } => {
|
||||
app.window_event(&self.active_event_loop, window_id, event)
|
||||
},
|
||||
Event::DeviceEvent { device_id, event } => {
|
||||
app.device_event(&self.active_event_loop, device_id, event)
|
||||
Event::DeviceEvent { event } => {
|
||||
app.device_event(&self.active_event_loop, None, event)
|
||||
},
|
||||
_ => unreachable!("event which is neither device nor window event."),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,8 +471,7 @@ impl EventLoop {
|
||||
});
|
||||
|
||||
if let Some(event) = event {
|
||||
let window_id = crate::window::WindowId(*window_id);
|
||||
app.window_event(&self.active_event_loop, window_id, event);
|
||||
app.window_event(&self.active_event_loop, *window_id, event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,14 +537,12 @@ impl EventLoop {
|
||||
})
|
||||
}
|
||||
|
||||
fn roundtrip(&mut self) -> Result<usize, RootOsError> {
|
||||
fn roundtrip(&mut self) -> Result<usize, OsError> {
|
||||
let state = &mut self.active_event_loop.state.get_mut();
|
||||
|
||||
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
|
||||
let event_queue = wayland_source.queue();
|
||||
event_queue.roundtrip(state).map_err(|error| {
|
||||
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
|
||||
})
|
||||
event_queue.roundtrip(state).map_err(|err| os_error!(err))
|
||||
}
|
||||
|
||||
fn control_flow(&self) -> ControlFlow {
|
||||
@@ -562,12 +574,13 @@ impl AsRawFd for EventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActiveEventLoop {
|
||||
/// Event loop proxy
|
||||
event_loop_proxy: EventLoopProxy,
|
||||
event_loop_proxy: CoreEventLoopProxy,
|
||||
|
||||
/// The event loop wakeup source.
|
||||
pub event_loop_awakener: calloop::ping::Ping,
|
||||
pub event_loop_awakener: Ping,
|
||||
|
||||
/// The main queue used by the event loop.
|
||||
pub queue_handle: QueueHandle<WinitState>,
|
||||
@@ -585,19 +598,13 @@ pub struct ActiveEventLoop {
|
||||
/// Dispatcher of Wayland events.
|
||||
pub wayland_dispatcher: WaylandDispatcher,
|
||||
|
||||
/// Connection to the wayland server.
|
||||
pub connection: Connection,
|
||||
|
||||
pub wayland_callback: Cell<Option<fn(&mut dyn ApplicationHandler)>>,
|
||||
/// Handle for the underlying event loop.
|
||||
pub handle: Arc<OwnedDisplayHandle>,
|
||||
}
|
||||
|
||||
impl RootActiveEventLoop for ActiveEventLoop {
|
||||
fn create_proxy(&self) -> crate::event_loop::EventLoopProxy {
|
||||
crate::event_loop::EventLoopProxy {
|
||||
event_loop_proxy: crate::platform_impl::EventLoopProxy::Wayland(
|
||||
self.event_loop_proxy.clone(),
|
||||
),
|
||||
}
|
||||
fn create_proxy(&self) -> CoreEventLoopProxy {
|
||||
self.event_loop_proxy.clone()
|
||||
}
|
||||
|
||||
fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||
@@ -622,10 +629,15 @@ impl RootActiveEventLoop for ActiveEventLoop {
|
||||
fn create_custom_cursor(
|
||||
&self,
|
||||
cursor: CustomCursorSource,
|
||||
) -> Result<RootCustomCursor, ExternalError> {
|
||||
Ok(RootCustomCursor {
|
||||
inner: PlatformCustomCursor::Wayland(OnlyCursorImage(Arc::from(cursor.inner.0))),
|
||||
})
|
||||
) -> Result<CoreCustomCursor, RequestError> {
|
||||
let cursor_image = match cursor {
|
||||
CustomCursorSource::Image(cursor_image) => cursor_image,
|
||||
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
|
||||
return Err(NotSupportedError::new("unsupported cursor kind").into())
|
||||
},
|
||||
};
|
||||
|
||||
Ok(CoreCustomCursor(Arc::new(WaylandCustomCursor(cursor_image))))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -636,41 +648,31 @@ impl RootActiveEventLoop for ActiveEventLoop {
|
||||
fn create_window(
|
||||
&self,
|
||||
window_attributes: crate::window::WindowAttributes,
|
||||
) -> Result<crate::window::Window, RootOsError> {
|
||||
) -> Result<Box<dyn crate::window::Window>, RequestError> {
|
||||
let window = crate::platform_impl::wayland::Window::new(self, window_attributes)?;
|
||||
let window = crate::platform_impl::Window::Wayland(window);
|
||||
Ok(crate::window::Window { window })
|
||||
Ok(Box::new(window))
|
||||
}
|
||||
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = crate::monitor::MonitorHandle>> {
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
|
||||
Box::new(
|
||||
self.state
|
||||
.borrow()
|
||||
.output_state
|
||||
.outputs()
|
||||
.map(crate::platform_impl::wayland::output::MonitorHandle::new)
|
||||
.map(crate::platform_impl::MonitorHandle::Wayland)
|
||||
.map(|inner| crate::monitor::MonitorHandle { inner }),
|
||||
.map(MonitorHandle::new)
|
||||
.map(|inner| CoreMonitorHandle(Arc::new(inner))),
|
||||
)
|
||||
}
|
||||
|
||||
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
|
||||
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
|
||||
// There's no primary monitor on Wayland.
|
||||
None
|
||||
}
|
||||
|
||||
fn owned_display_handle(&self) -> crate::event_loop::OwnedDisplayHandle {
|
||||
crate::event_loop::OwnedDisplayHandle {
|
||||
platform: crate::platform_impl::OwnedDisplayHandle::Wayland(self.connection.clone()),
|
||||
}
|
||||
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
|
||||
CoreOwnedDisplayHandle::new(self.handle.clone())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
|
||||
self
|
||||
}
|
||||
@@ -690,8 +692,24 @@ impl ActiveEventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
self.handle.display_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OwnedDisplayHandle {
|
||||
pub(crate) connection: Connection,
|
||||
}
|
||||
|
||||
impl OwnedDisplayHandle {
|
||||
fn new(connection: Connection) -> Self {
|
||||
Self { connection }
|
||||
}
|
||||
}
|
||||
|
||||
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
@@ -703,3 +721,84 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
|
||||
Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw.into()) })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PumpEventNotifier {
|
||||
/// Whether we're in winit or not.
|
||||
control: Arc<(Mutex<PumpEventNotifierAction>, Condvar)>,
|
||||
/// Waker handle for the working thread.
|
||||
worker_waker: Option<OwnedFd>,
|
||||
/// Thread handle.
|
||||
handle: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Drop for PumpEventNotifier {
|
||||
fn drop(&mut self) {
|
||||
// Wake-up the thread.
|
||||
if let Some(worker_waker) = self.worker_waker.as_ref() {
|
||||
let _ = rustix::io::write(worker_waker.as_fd(), &[0u8]);
|
||||
}
|
||||
*self.control.0.lock().unwrap() = PumpEventNotifierAction::Monitor;
|
||||
self.control.1.notify_one();
|
||||
|
||||
if let Some(handle) = self.handle.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PumpEventNotifier {
|
||||
fn spawn(connection: Connection, awakener: Ping) -> Self {
|
||||
// Start from the waiting state.
|
||||
let control = Arc::new((Mutex::new(PumpEventNotifierAction::Pause), Condvar::new()));
|
||||
let control_thread = Arc::clone(&control);
|
||||
|
||||
let (read, write) = match pipe::pipe_with(PipeFlags::CLOEXEC | PipeFlags::NONBLOCK) {
|
||||
Ok((read, write)) => (read, write),
|
||||
Err(_) => return Self { control, handle: None, worker_waker: None },
|
||||
};
|
||||
|
||||
let handle =
|
||||
std::thread::Builder::new().name(String::from("pump_events mon")).spawn(move || {
|
||||
let (lock, cvar) = &*control_thread;
|
||||
'outer: loop {
|
||||
let mut wait = lock.lock().unwrap();
|
||||
while *wait == PumpEventNotifierAction::Pause {
|
||||
wait = cvar.wait(wait).unwrap();
|
||||
}
|
||||
// Wake-up the main loop and put this one back to sleep.
|
||||
*wait = PumpEventNotifierAction::Pause;
|
||||
drop(wait);
|
||||
|
||||
while let Some(read_guard) = connection.prepare_read() {
|
||||
let _ = connection.flush();
|
||||
let poll_fd = PollFd::from_borrowed_fd(connection.as_fd(), PollFlags::IN);
|
||||
let pipe_poll_fd = PollFd::from_borrowed_fd(read.as_fd(), PollFlags::IN);
|
||||
// Read from the `fd` before going back to poll.
|
||||
if Ok(1) == rustix::io::read(read.as_fd(), &mut [0u8; 1]) {
|
||||
break 'outer;
|
||||
}
|
||||
let _ = rustix::event::poll(&mut [poll_fd, pipe_poll_fd], -1);
|
||||
// Non-blocking read the connection.
|
||||
let _ = read_guard.read_without_dispatch();
|
||||
}
|
||||
|
||||
awakener.ping();
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(err) = handle.as_ref().err() {
|
||||
warn!("failed to spawn pump_events wake-up thread: {err}");
|
||||
}
|
||||
|
||||
PumpEventNotifier { control, handle: handle.ok(), worker_waker: Some(write) }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum PumpEventNotifierAction {
|
||||
/// Monitor the wayland queue.
|
||||
Monitor,
|
||||
/// Pause monitoring.
|
||||
Pause,
|
||||
}
|
||||
|
||||
@@ -1,19 +1,31 @@
|
||||
//! An event loop proxy.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use sctk::reexports::calloop::ping::Ping;
|
||||
|
||||
use crate::event_loop::{EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider};
|
||||
|
||||
/// A handle that can be sent across the threads and used to wake up the `EventLoop`.
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoopProxy {
|
||||
ping: Ping,
|
||||
}
|
||||
|
||||
impl EventLoopProxyProvider for EventLoopProxy {
|
||||
fn wake_up(&self) {
|
||||
self.ping.ping();
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
pub fn new(ping: Ping) -> Self {
|
||||
Self { ping }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wake_up(&self) {
|
||||
self.ping.ping();
|
||||
impl From<EventLoopProxy> for CoreEventLoopProxy {
|
||||
fn from(value: EventLoopProxy) -> Self {
|
||||
CoreEventLoopProxy::new(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
use std::vec::Drain;
|
||||
|
||||
use super::{DeviceId, WindowId};
|
||||
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
|
||||
use crate::platform_impl::platform::DeviceId as PlatformDeviceId;
|
||||
use crate::window::WindowId as RootWindowId;
|
||||
use super::Event;
|
||||
use crate::event::{DeviceEvent, WindowEvent};
|
||||
use crate::window::WindowId;
|
||||
|
||||
/// An event loop's sink to deliver events from the Wayland event callbacks
|
||||
/// to the winit's user.
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct EventSink {
|
||||
pub(crate) window_events: Vec<Event>,
|
||||
}
|
||||
@@ -27,17 +26,14 @@ impl EventSink {
|
||||
|
||||
/// Add new device event to a queue.
|
||||
#[inline]
|
||||
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
|
||||
self.window_events.push(Event::DeviceEvent {
|
||||
event,
|
||||
device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)),
|
||||
});
|
||||
pub fn push_device_event(&mut self, event: DeviceEvent) {
|
||||
self.window_events.push(Event::DeviceEvent { event });
|
||||
}
|
||||
|
||||
/// Add new window event to a queue.
|
||||
#[inline]
|
||||
pub fn push_window_event(&mut self, event: WindowEvent, window_id: WindowId) {
|
||||
self.window_events.push(Event::WindowEvent { event, window_id: RootWindowId(window_id) });
|
||||
self.window_events.push(Event::WindowEvent { event, window_id });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
//! Winit's Wayland backend.
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
|
||||
pub use output::{MonitorHandle, VideoModeHandle};
|
||||
use sctk::reexports::client::globals::{BindError, GlobalError};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy};
|
||||
pub use window::Window;
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
pub(super) use crate::cursor::OnlyCursorImage as CustomCursor;
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
pub use crate::platform_impl::platform::{OsError, WindowId};
|
||||
use crate::window::WindowId;
|
||||
|
||||
mod event_loop;
|
||||
mod output;
|
||||
@@ -21,69 +13,13 @@ mod state;
|
||||
mod types;
|
||||
mod window;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WaylandError {
|
||||
/// Error connecting to the socket.
|
||||
Connection(ConnectError),
|
||||
|
||||
/// Error binding the global.
|
||||
Global(GlobalError),
|
||||
|
||||
// Bind error.
|
||||
Bind(BindError),
|
||||
|
||||
/// Error during the dispatching the event queue.
|
||||
Dispatch(DispatchError),
|
||||
|
||||
/// Calloop error.
|
||||
Calloop(calloop::Error),
|
||||
|
||||
/// Wayland
|
||||
Wire(client::backend::WaylandError),
|
||||
}
|
||||
|
||||
impl Display for WaylandError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WaylandError::Connection(error) => error.fmt(f),
|
||||
WaylandError::Global(error) => error.fmt(f),
|
||||
WaylandError::Bind(error) => error.fmt(f),
|
||||
WaylandError::Dispatch(error) => error.fmt(f),
|
||||
WaylandError::Calloop(error) => error.fmt(f),
|
||||
WaylandError::Wire(error) => error.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WaylandError> for OsError {
|
||||
fn from(value: WaylandError) -> Self {
|
||||
Self::WaylandError(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy device id, since Wayland doesn't have device events.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub const fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct FingerId(i32);
|
||||
|
||||
impl FingerId {
|
||||
pub const fn dummy() -> Self {
|
||||
FingerId(0)
|
||||
}
|
||||
}
|
||||
pub use event_loop::{ActiveEventLoop, EventLoop};
|
||||
pub use window::Window;
|
||||
|
||||
/// Get the WindowId out of the surface.
|
||||
#[inline]
|
||||
fn make_wid(surface: &WlSurface) -> WindowId {
|
||||
WindowId(surface.id().as_ptr() as u64)
|
||||
WindowId::from_raw(surface.id().as_ptr() as usize)
|
||||
}
|
||||
|
||||
/// The default routine does floor, but we need round on Wayland.
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::borrow::Cow;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use sctk::output::{Mode, OutputData};
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::platform_impl::platform::VideoModeHandle as PlatformVideoModeHandle;
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition};
|
||||
use crate::monitor::{MonitorHandleProvider as CoreMonitorHandle, VideoMode};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MonitorHandle {
|
||||
@@ -17,21 +18,24 @@ impl MonitorHandle {
|
||||
pub(crate) fn new(proxy: WlOutput) -> Self {
|
||||
Self { proxy }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.with_output_info(|info| info.name.clone())
|
||||
impl CoreMonitorHandle for MonitorHandle {
|
||||
fn id(&self) -> u128 {
|
||||
self.native_id() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_identifier(&self) -> u32 {
|
||||
fn native_id(&self) -> u64 {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.with_output_info(|info| info.id)
|
||||
output_data.with_output_info(|info| info.id as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
|
||||
fn name(&self) -> Option<Cow<'_, str>> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.with_output_info(|info| info.name.clone().map(Cow::Owned))
|
||||
}
|
||||
|
||||
fn position(&self) -> Option<PhysicalPosition<i32>> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
Some(output_data.with_output_info(|info| {
|
||||
info.logical_position.map_or_else(
|
||||
@@ -47,95 +51,41 @@ impl MonitorHandle {
|
||||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> i32 {
|
||||
fn scale_factor(&self) -> f64 {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.scale_factor()
|
||||
output_data.scale_factor() as f64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_video_mode(&self) -> Option<PlatformVideoModeHandle> {
|
||||
fn current_video_mode(&self) -> Option<crate::monitor::VideoMode> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.with_output_info(|info| {
|
||||
let mode = info.modes.iter().find(|mode| mode.current).cloned();
|
||||
|
||||
mode.map(|mode| {
|
||||
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(self.clone(), mode))
|
||||
})
|
||||
mode.map(wayland_mode_to_core_mode)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> {
|
||||
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
let modes = output_data.with_output_info(|info| info.modes.clone());
|
||||
|
||||
let monitor = self.clone();
|
||||
|
||||
modes.into_iter().map(move |mode| {
|
||||
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(monitor.clone(), mode))
|
||||
})
|
||||
Box::new(modes.into_iter().map(wayland_mode_to_core_mode))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.native_identifier() == other.native_identifier()
|
||||
self.native_id() == other.native_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MonitorHandle {}
|
||||
|
||||
impl PartialOrd for MonitorHandle {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for MonitorHandle {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.native_identifier().cmp(&other.native_identifier())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for MonitorHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.native_identifier().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoModeHandle {
|
||||
pub(crate) size: PhysicalSize<u32>,
|
||||
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
fn new(monitor: MonitorHandle, mode: Mode) -> Self {
|
||||
VideoModeHandle {
|
||||
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
|
||||
refresh_rate_millihertz: NonZeroU32::new(mode.refresh_rate as u32),
|
||||
monitor: monitor.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.monitor.clone()
|
||||
/// Convert the wayland's [`Mode`] to winit's [`VideoMode`].
|
||||
fn wayland_mode_to_core_mode(mode: Mode) -> VideoMode {
|
||||
VideoMode {
|
||||
size: (mode.dimensions.0, mode.dimensions.1).into(),
|
||||
bit_depth: None,
|
||||
refresh_rate_millihertz: NonZeroU32::new(mode.refresh_rate as u32),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::keyboard::ModifiersState;
|
||||
use crate::platform_impl::common::xkb::Context;
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
|
||||
use crate::platform_impl::wayland::{self, WindowId};
|
||||
|
||||
impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
fn event(
|
||||
@@ -369,10 +369,9 @@ fn key_input(
|
||||
None => return,
|
||||
};
|
||||
|
||||
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
|
||||
if let Some(mut key_context) = keyboard_state.xkb_context.key_context() {
|
||||
let event = key_context.process_key_event(keycode, state, repeat);
|
||||
let event = WindowEvent::KeyboardInput { device_id, event, is_synthetic: false };
|
||||
let event = WindowEvent::KeyboardInput { device_id: None, event, is_synthetic: false };
|
||||
event_sink.push_window_event(event, window_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ pub struct WinitSeatState {
|
||||
/// The mapping from touched points to the surfaces they're present.
|
||||
touch_map: AHashMap<i32, TouchPoint>,
|
||||
|
||||
/// Id of the first touch event.
|
||||
first_touch_id: Option<i32>,
|
||||
|
||||
/// The text input bound on the seat.
|
||||
text_input: Option<Arc<ZwpTextInputV3>>,
|
||||
|
||||
@@ -93,8 +96,12 @@ impl SeatHandler for WinitState {
|
||||
},
|
||||
SeatCapability::Pointer if seat_state.pointer.is_none() => {
|
||||
let surface = self.compositor_state.create_surface(queue_handle);
|
||||
let viewport = self
|
||||
.viewporter_state
|
||||
.as_ref()
|
||||
.map(|state| state.get_viewport(&surface, queue_handle));
|
||||
let surface_id = surface.id();
|
||||
let pointer_data = WinitPointerData::new(seat.clone());
|
||||
let pointer_data = WinitPointerData::new(seat.clone(), viewport);
|
||||
let themed_pointer = self
|
||||
.seat_state
|
||||
.get_pointer_with_theme_and_data(
|
||||
|
||||
@@ -18,6 +18,7 @@ use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_ma
|
||||
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1};
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::csd_frame::FrameClick;
|
||||
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
|
||||
|
||||
use sctk::compositor::SurfaceData;
|
||||
use sctk::globals::GlobalData;
|
||||
@@ -27,10 +28,13 @@ use sctk::seat::pointer::{
|
||||
use sctk::seat::SeatState;
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition};
|
||||
use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent};
|
||||
use crate::event::{
|
||||
ElementState, MouseButton, MouseScrollDelta, PointerKind, PointerSource, TouchPhase,
|
||||
WindowEvent,
|
||||
};
|
||||
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
|
||||
use crate::platform_impl::wayland::{self, WindowId};
|
||||
|
||||
pub mod relative_pointer;
|
||||
|
||||
@@ -59,8 +63,6 @@ impl PointerHandler for WinitState {
|
||||
},
|
||||
};
|
||||
|
||||
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
|
||||
|
||||
for event in events {
|
||||
let surface = &event.surface;
|
||||
|
||||
@@ -124,18 +126,20 @@ impl PointerHandler for WinitState {
|
||||
},
|
||||
// Regular events on the main surface.
|
||||
PointerEventKind::Enter { .. } => {
|
||||
self.events_sink
|
||||
.push_window_event(WindowEvent::CursorEntered { device_id }, window_id);
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerEntered {
|
||||
primary: true,
|
||||
device_id: None,
|
||||
position,
|
||||
kind: PointerKind::Mouse,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
|
||||
window.pointer_entered(Arc::downgrade(themed_pointer));
|
||||
|
||||
// Set the currently focused surface.
|
||||
pointer.winit_data().inner.lock().unwrap().surface = Some(window_id);
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::CursorMoved { device_id, position },
|
||||
window_id,
|
||||
);
|
||||
},
|
||||
PointerEventKind::Leave { .. } => {
|
||||
window.pointer_left(Arc::downgrade(themed_pointer));
|
||||
@@ -143,12 +147,24 @@ impl PointerHandler for WinitState {
|
||||
// Remove the active surface.
|
||||
pointer.winit_data().inner.lock().unwrap().surface = None;
|
||||
|
||||
self.events_sink
|
||||
.push_window_event(WindowEvent::CursorLeft { device_id }, window_id);
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerLeft {
|
||||
primary: true,
|
||||
device_id: None,
|
||||
position: Some(position),
|
||||
kind: PointerKind::Mouse,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
},
|
||||
PointerEventKind::Motion { .. } => {
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::CursorMoved { device_id, position },
|
||||
WindowEvent::PointerMoved {
|
||||
primary: true,
|
||||
device_id: None,
|
||||
position,
|
||||
source: PointerSource::Mouse,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
},
|
||||
@@ -164,7 +180,13 @@ impl PointerHandler for WinitState {
|
||||
ElementState::Released
|
||||
};
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::MouseInput { device_id, state, button },
|
||||
WindowEvent::PointerButton {
|
||||
primary: true,
|
||||
device_id: None,
|
||||
state,
|
||||
position,
|
||||
button: button.into(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
},
|
||||
@@ -209,7 +231,7 @@ impl PointerHandler for WinitState {
|
||||
};
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::MouseWheel { device_id, delta, phase },
|
||||
WindowEvent::MouseWheel { device_id: None, delta, phase },
|
||||
window_id,
|
||||
)
|
||||
},
|
||||
@@ -225,13 +247,17 @@ pub struct WinitPointerData {
|
||||
|
||||
/// The data required by the sctk.
|
||||
sctk_data: PointerData,
|
||||
|
||||
/// Viewport for fractional cursor.
|
||||
viewport: Option<WpViewport>,
|
||||
}
|
||||
|
||||
impl WinitPointerData {
|
||||
pub fn new(seat: WlSeat) -> Self {
|
||||
pub fn new(seat: WlSeat, viewport: Option<WpViewport>) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(WinitPointerDataInner::default()),
|
||||
sctk_data: PointerData::new(seat),
|
||||
viewport,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,6 +338,18 @@ impl WinitPointerData {
|
||||
locked_pointer.set_cursor_position_hint(surface_x, surface_y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn viewport(&self) -> Option<&WpViewport> {
|
||||
self.viewport.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WinitPointerData {
|
||||
fn drop(&mut self) {
|
||||
if let Some(viewport) = self.viewport.take() {
|
||||
viewport.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerDataExt for WinitPointerData {
|
||||
@@ -393,6 +431,7 @@ impl WinitPointerDataExt for WlPointer {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PointerConstraintsState {
|
||||
pointer_constraints: ZwpPointerConstraintsV1,
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use crate::event::DeviceEvent;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
|
||||
/// Wrapper around the relative pointer.
|
||||
#[derive(Debug)]
|
||||
pub struct RelativePointerState {
|
||||
manager: ZwpRelativePointerManagerV1,
|
||||
}
|
||||
@@ -66,10 +67,9 @@ impl Dispatch<ZwpRelativePointerV1, GlobalData, WinitState> for RelativePointerS
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
state.events_sink.push_device_event(
|
||||
DeviceEvent::MouseMotion { delta: (dx_unaccel, dy_unaccel) },
|
||||
super::DeviceId,
|
||||
);
|
||||
state
|
||||
.events_sink
|
||||
.push_device_event(DeviceEvent::PointerMotion { delta: (dx_unaccel, dy_unaccel) });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::platform_impl::wayland;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
use crate::window::ImePurpose;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TextInputState {
|
||||
text_input_manager: ZwpTextInputManagerV3,
|
||||
}
|
||||
@@ -119,11 +120,15 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Clear preedit at the start of `Done`.
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
window_id,
|
||||
);
|
||||
// Clear preedit, unless all we'll be doing next is sending a new preedit.
|
||||
if text_input_data.pending_commit.is_some()
|
||||
|| text_input_data.pending_preedit.is_none()
|
||||
{
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
// Send `Commit`.
|
||||
if let Some(text) = text_input_data.pending_commit.take() {
|
||||
|
||||
@@ -8,9 +8,9 @@ use sctk::seat::touch::{TouchData, TouchHandler};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{Touch, TouchPhase, WindowEvent};
|
||||
use crate::event::{ButtonSource, ElementState, FingerId, PointerKind, PointerSource, WindowEvent};
|
||||
use crate::platform_impl::wayland;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId, FingerId};
|
||||
|
||||
impl TouchHandler for WinitState {
|
||||
fn down(
|
||||
@@ -40,20 +40,33 @@ impl TouchHandler for WinitState {
|
||||
|
||||
// Update the state of the point.
|
||||
let location = LogicalPosition::<f64>::from(position);
|
||||
// Only update primary finger once we don't have any touch.
|
||||
if seat_state.touch_map.is_empty() {
|
||||
seat_state.first_touch_id = Some(id);
|
||||
}
|
||||
let primary = seat_state.first_touch_id == Some(id);
|
||||
seat_state.touch_map.insert(id, TouchPoint { surface, location });
|
||||
|
||||
let position = location.to_physical(scale_factor);
|
||||
let finger_id = FingerId::from_raw(id as usize);
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::Touch(Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Started,
|
||||
location: location.to_physical(scale_factor),
|
||||
force: None,
|
||||
finger_id: crate::event::FingerId(crate::platform_impl::FingerId::Wayland(
|
||||
FingerId(id),
|
||||
)),
|
||||
}),
|
||||
WindowEvent::PointerEntered {
|
||||
device_id: None,
|
||||
primary,
|
||||
position,
|
||||
kind: PointerKind::Touch(finger_id),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerButton {
|
||||
device_id: None,
|
||||
primary,
|
||||
state: ElementState::Pressed,
|
||||
position,
|
||||
button: ButtonSource::Touch { finger_id, force: None },
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
@@ -81,24 +94,41 @@ impl TouchHandler for WinitState {
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Update the primary touch point.
|
||||
let primary = seat_state.first_touch_id == Some(id);
|
||||
// Reset primary finger once all the other fingers are lifted to not transfer primary
|
||||
// finger to some other finger and still accept it when it's briefly moved between the
|
||||
// windows.
|
||||
if seat_state.touch_map.is_empty() {
|
||||
seat_state.first_touch_id = None;
|
||||
}
|
||||
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
let scale_factor = match self.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().scale_factor(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let position = touch_point.location.to_physical(scale_factor);
|
||||
let finger_id = FingerId::from_raw(id as usize);
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::Touch(Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Ended,
|
||||
location: touch_point.location.to_physical(scale_factor),
|
||||
force: None,
|
||||
finger_id: crate::event::FingerId(crate::platform_impl::FingerId::Wayland(
|
||||
FingerId(id),
|
||||
)),
|
||||
}),
|
||||
WindowEvent::PointerButton {
|
||||
device_id: None,
|
||||
primary,
|
||||
state: ElementState::Released,
|
||||
position,
|
||||
button: ButtonSource::Touch { finger_id, force: None },
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerLeft {
|
||||
device_id: None,
|
||||
primary,
|
||||
position: Some(position),
|
||||
kind: PointerKind::Touch(finger_id),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
@@ -126,6 +156,8 @@ impl TouchHandler for WinitState {
|
||||
None => return,
|
||||
};
|
||||
|
||||
let primary = seat_state.first_touch_id == Some(id);
|
||||
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
let scale_factor = match self.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().scale_factor(),
|
||||
@@ -135,17 +167,15 @@ impl TouchHandler for WinitState {
|
||||
touch_point.location = LogicalPosition::<f64>::from(position);
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::Touch(Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Moved,
|
||||
location: touch_point.location.to_physical(scale_factor),
|
||||
force: None,
|
||||
finger_id: crate::event::FingerId(crate::platform_impl::FingerId::Wayland(
|
||||
FingerId(id),
|
||||
)),
|
||||
}),
|
||||
WindowEvent::PointerMoved {
|
||||
device_id: None,
|
||||
primary,
|
||||
position: touch_point.location.to_physical(scale_factor),
|
||||
source: PointerSource::Touch {
|
||||
finger_id: FingerId::from_raw(id as usize),
|
||||
force: None,
|
||||
},
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
@@ -166,23 +196,21 @@ impl TouchHandler for WinitState {
|
||||
None => return,
|
||||
};
|
||||
|
||||
let location = touch_point.location.to_physical(scale_factor);
|
||||
let primary = seat_state.first_touch_id == Some(id);
|
||||
let position = touch_point.location.to_physical(scale_factor);
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::Touch(Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location,
|
||||
force: None,
|
||||
finger_id: crate::event::FingerId(crate::platform_impl::FingerId::Wayland(
|
||||
FingerId(id),
|
||||
)),
|
||||
}),
|
||||
WindowEvent::PointerLeft {
|
||||
device_id: None,
|
||||
primary,
|
||||
position: Some(position),
|
||||
kind: PointerKind::Touch(FingerId::from_raw(id as usize)),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
seat_state.first_touch_id = None;
|
||||
}
|
||||
|
||||
fn shape(
|
||||
|
||||
@@ -21,6 +21,7 @@ use sctk::shm::slot::SlotPool;
|
||||
use sctk::shm::{Shm, ShmHandler};
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
|
||||
use crate::error::OsError;
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::output::MonitorHandle;
|
||||
use crate::platform_impl::wayland::seat::{
|
||||
@@ -32,10 +33,10 @@ use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScali
|
||||
use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState;
|
||||
use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState;
|
||||
use crate::platform_impl::wayland::window::{WindowRequests, WindowState};
|
||||
use crate::platform_impl::wayland::{WaylandError, WindowId};
|
||||
use crate::platform_impl::OsError;
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
|
||||
/// Winit's Wayland state.
|
||||
#[derive(Debug)]
|
||||
pub struct WinitState {
|
||||
/// The WlRegistry.
|
||||
pub registry_state: RegistryState,
|
||||
@@ -126,7 +127,7 @@ impl WinitState {
|
||||
) -> Result<Self, OsError> {
|
||||
let registry_state = RegistryState::new(globals);
|
||||
let compositor_state =
|
||||
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
|
||||
CompositorState::bind(globals, queue_handle).map_err(|err| os_error!(err))?;
|
||||
let subcompositor_state = match SubcompositorState::bind(
|
||||
compositor_state.wl_compositor().clone(),
|
||||
globals,
|
||||
@@ -156,7 +157,7 @@ impl WinitState {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let shm = Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
|
||||
let shm = Shm::bind(globals, queue_handle).map_err(|err| os_error!(err))?;
|
||||
let custom_cursor_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap()));
|
||||
|
||||
Ok(Self {
|
||||
@@ -168,7 +169,7 @@ impl WinitState {
|
||||
shm,
|
||||
custom_cursor_pool,
|
||||
|
||||
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
|
||||
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(|err| os_error!(err))?,
|
||||
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
|
||||
|
||||
windows: Default::default(),
|
||||
|
||||
@@ -2,7 +2,16 @@ use cursor_icon::CursorIcon;
|
||||
use sctk::reexports::client::protocol::wl_shm::Format;
|
||||
use sctk::shm::slot::{Buffer, SlotPool};
|
||||
|
||||
use crate::cursor::CursorImage;
|
||||
use crate::cursor::{CursorImage, CustomCursorProvider};
|
||||
|
||||
// Wrap in our own type to not impl trait on global type.
|
||||
#[derive(Debug)]
|
||||
pub struct WaylandCustomCursor(pub(crate) CursorImage);
|
||||
impl CustomCursorProvider for WaylandCustomCursor {
|
||||
fn is_animated(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SelectedCursor {
|
||||
@@ -26,7 +35,8 @@ pub struct CustomCursor {
|
||||
}
|
||||
|
||||
impl CustomCursor {
|
||||
pub(crate) fn new(pool: &mut SlotPool, image: &CursorImage) -> Self {
|
||||
pub(crate) fn new(pool: &mut SlotPool, image: &WaylandCustomCursor) -> Self {
|
||||
let image = &image.0;
|
||||
let (buffer, canvas) = pool
|
||||
.create_buffer(
|
||||
image.width as i32,
|
||||
|
||||
@@ -14,9 +14,9 @@ use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::
|
||||
|
||||
use crate::event_loop::AsyncRequestSerial;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
use crate::platform_impl::WindowId;
|
||||
use crate::window::ActivationToken;
|
||||
use crate::window::{ActivationToken, WindowId};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XdgActivationState {
|
||||
xdg_activation: XdgActivationV1,
|
||||
}
|
||||
@@ -79,7 +79,7 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
|
||||
state.events_sink.push_window_event(
|
||||
crate::event::WindowEvent::ActivationTokenDone {
|
||||
serial: *serial,
|
||||
token: ActivationToken::_new(token),
|
||||
token: ActivationToken::from_raw(token),
|
||||
},
|
||||
*window_id,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! The Wayland window.
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -16,17 +18,16 @@ use super::event_loop::sink::EventSink;
|
||||
use super::output::MonitorHandle;
|
||||
use super::state::WinitState;
|
||||
use super::types::xdg_activation::XdgActivationTokenData;
|
||||
use super::{ActiveEventLoop, WaylandError, WindowId};
|
||||
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
||||
use super::ActiveEventLoop;
|
||||
use crate::dpi::{LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{NotSupportedError, RequestError};
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
use crate::event_loop::AsyncRequestSerial;
|
||||
use crate::platform_impl::{
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
|
||||
};
|
||||
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
|
||||
use crate::platform_impl::wayland::output;
|
||||
use crate::window::{
|
||||
Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
WindowAttributes, WindowButtons, WindowLevel,
|
||||
Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel,
|
||||
};
|
||||
|
||||
pub(crate) mod state;
|
||||
@@ -34,6 +35,7 @@ pub(crate) mod state;
|
||||
pub use state::WindowState;
|
||||
|
||||
/// The Wayland window.
|
||||
#[derive(Debug)]
|
||||
pub struct Window {
|
||||
/// Reference to the underlying SCTK window.
|
||||
window: SctkWindow,
|
||||
@@ -77,7 +79,7 @@ impl Window {
|
||||
pub(crate) fn new(
|
||||
event_loop_window_target: &ActiveEventLoop,
|
||||
attributes: WindowAttributes,
|
||||
) -> Result<Self, RootOsError> {
|
||||
) -> Result<Self, RequestError> {
|
||||
let queue_handle = event_loop_window_target.queue_handle.clone();
|
||||
let mut state = event_loop_window_target.state.borrow_mut();
|
||||
|
||||
@@ -87,9 +89,9 @@ impl Window {
|
||||
let compositor = state.compositor_state.clone();
|
||||
let xdg_activation =
|
||||
state.xdg_activation.as_ref().map(|activation_state| activation_state.global().clone());
|
||||
let display = event_loop_window_target.connection.display();
|
||||
let display = event_loop_window_target.handle.connection.display();
|
||||
|
||||
let size: Size = attributes.inner_size.unwrap_or(LogicalSize::new(800., 600.).into());
|
||||
let size: Size = attributes.surface_size.unwrap_or(LogicalSize::new(800., 600.).into());
|
||||
|
||||
// We prefer server side decorations, however to not have decorations we ask for client
|
||||
// side decorations instead.
|
||||
@@ -103,7 +105,7 @@ impl Window {
|
||||
state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle);
|
||||
|
||||
let mut window_state = WindowState::new(
|
||||
event_loop_window_target.connection.clone(),
|
||||
event_loop_window_target.handle.clone(),
|
||||
&event_loop_window_target.queue_handle,
|
||||
&state,
|
||||
size,
|
||||
@@ -129,28 +131,26 @@ impl Window {
|
||||
|
||||
// Set the min and max sizes. We must set the hints upon creating a window, so
|
||||
// we use the default `1.` scaling...
|
||||
let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.));
|
||||
let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.));
|
||||
window_state.set_min_inner_size(min_size);
|
||||
window_state.set_max_inner_size(max_size);
|
||||
let min_size = attributes.min_surface_size.map(|size| size.to_logical(1.));
|
||||
let max_size = attributes.max_surface_size.map(|size| size.to_logical(1.));
|
||||
window_state.set_min_surface_size(min_size);
|
||||
window_state.set_max_surface_size(max_size);
|
||||
|
||||
// Non-resizable implies that the min and max sizes are set to the same value.
|
||||
window_state.set_resizable(attributes.resizable);
|
||||
|
||||
// Set startup mode.
|
||||
match attributes.fullscreen.map(Into::into) {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
match attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(..)) => {
|
||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
|
||||
},
|
||||
#[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
|
||||
Some(Fullscreen::Borderless(monitor)) => {
|
||||
let output = monitor.and_then(|monitor| match monitor {
|
||||
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
|
||||
#[cfg(x11_platform)]
|
||||
PlatformMonitorHandle::X(_) => None,
|
||||
let output = monitor.as_ref().and_then(|monitor| {
|
||||
monitor.cast_ref::<output::MonitorHandle>().map(|handle| &handle.proxy)
|
||||
});
|
||||
|
||||
window.set_fullscreen(output.as_ref())
|
||||
window.set_fullscreen(output)
|
||||
},
|
||||
_ if attributes.maximized => window.set_maximized(),
|
||||
_ => (),
|
||||
@@ -165,7 +165,7 @@ impl Window {
|
||||
if let (Some(xdg_activation), Some(token)) =
|
||||
(xdg_activation.as_ref(), attributes.platform_specific.activation_token)
|
||||
{
|
||||
xdg_activation.activate(token._token, &surface);
|
||||
xdg_activation.activate(token.token, &surface);
|
||||
}
|
||||
|
||||
// XXX Do initial commit.
|
||||
@@ -190,15 +190,11 @@ impl Window {
|
||||
let event_queue = wayland_source.queue();
|
||||
|
||||
// Do a roundtrip.
|
||||
event_queue.roundtrip(&mut state).map_err(|error| {
|
||||
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
|
||||
})?;
|
||||
event_queue.roundtrip(&mut state).map_err(|err| os_error!(err))?;
|
||||
|
||||
// XXX Wait for the initial configure to arrive.
|
||||
while !window_state.lock().unwrap().is_configured() {
|
||||
event_queue.blocking_dispatch(&mut state).map_err(|error| {
|
||||
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
|
||||
})?;
|
||||
event_queue.blocking_dispatch(&mut state).map_err(|err| os_error!(err))?;
|
||||
}
|
||||
|
||||
// Wake-up event loop, so it'll send initial redraw requested.
|
||||
@@ -220,54 +216,70 @@ impl Window {
|
||||
window_events_sink,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
|
||||
NonNull::new(self.window.xdg_toplevel().id().as_ptr().cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
|
||||
let xdg_activation = match self.xdg_activation.as_ref() {
|
||||
Some(xdg_activation) => xdg_activation,
|
||||
None => return Err(NotSupportedError::new("xdg_activation_v1 is not available").into()),
|
||||
};
|
||||
|
||||
let serial = AsyncRequestSerial::get();
|
||||
|
||||
let data = XdgActivationTokenData::Obtain((self.window_id, serial));
|
||||
let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
|
||||
xdg_activation_token.set_surface(self.surface());
|
||||
xdg_activation_token.commit();
|
||||
|
||||
Ok(serial)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
pub fn surface(&self) -> &WlSurface {
|
||||
self.window.wl_surface()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
self.window_requests.closed.store(true, Ordering::Relaxed);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
}
|
||||
|
||||
impl rwh_06::HasWindowHandle for Window {
|
||||
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = rwh_06::WaylandWindowHandle::new({
|
||||
let ptr = self.window.wl_surface().id().as_ptr();
|
||||
std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null")
|
||||
});
|
||||
|
||||
unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw.into())) }
|
||||
}
|
||||
}
|
||||
|
||||
impl rwh_06::HasDisplayHandle for Window {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = rwh_06::WaylandDisplayHandle::new({
|
||||
let ptr = self.display.id().as_ptr();
|
||||
std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null")
|
||||
});
|
||||
|
||||
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw.into())) }
|
||||
}
|
||||
}
|
||||
|
||||
impl CoreWindow for Window {
|
||||
fn id(&self) -> WindowId {
|
||||
self.window_id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, title: impl ToString) {
|
||||
let new_title = title.to_string();
|
||||
self.window_state.lock().unwrap().set_title(new_title);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, _visible: bool) {
|
||||
// Not possible on Wayland.
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_outer_position(&self, _: Position) {
|
||||
// Not possible on Wayland.
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
super::logical_to_physical_rounded(window_state.inner_size(), scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_redraw(&self) {
|
||||
fn request_redraw(&self) {
|
||||
// NOTE: try to not wake up the loop when the event was already scheduled and not yet
|
||||
// processed by the loop, because if at this point the value was `true` it could only
|
||||
// mean that the loop still haven't dispatched the value to the client and will do
|
||||
@@ -283,135 +295,119 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pre_present_notify(&self) {
|
||||
fn title(&self) -> String {
|
||||
self.window_state.lock().unwrap().title().to_owned()
|
||||
}
|
||||
|
||||
fn pre_present_notify(&self) {
|
||||
self.window_state.lock().unwrap().request_frame_callback();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
fn reset_dead_keys(&self) {
|
||||
crate::platform_impl::common::xkb::reset_dead_keys()
|
||||
}
|
||||
|
||||
fn surface_position(&self) -> PhysicalPosition<i32> {
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
|
||||
Err(NotSupportedError::new("window position information is not available on Wayland")
|
||||
.into())
|
||||
}
|
||||
|
||||
fn set_outer_position(&self, _position: Position) {
|
||||
// Not possible.
|
||||
}
|
||||
|
||||
fn surface_size(&self) -> PhysicalSize<u32> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
super::logical_to_physical_rounded(window_state.surface_size(), scale_factor)
|
||||
}
|
||||
|
||||
fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
let new_size = window_state.request_surface_size(size);
|
||||
self.request_redraw();
|
||||
Some(new_size)
|
||||
}
|
||||
|
||||
fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
let new_size = window_state.request_inner_size(size);
|
||||
self.request_redraw();
|
||||
Some(new_size)
|
||||
fn safe_area(&self) -> PhysicalInsets<u32> {
|
||||
PhysicalInsets::new(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
/// Set the minimum inner size for the window.
|
||||
#[inline]
|
||||
pub fn set_min_inner_size(&self, min_size: Option<Size>) {
|
||||
fn set_min_surface_size(&self, min_size: Option<Size>) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let min_size = min_size.map(|size| size.to_logical(scale_factor));
|
||||
self.window_state.lock().unwrap().set_min_inner_size(min_size);
|
||||
self.window_state.lock().unwrap().set_min_surface_size(min_size);
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Set the maximum inner size for the window.
|
||||
/// Set the maximum surface size for the window.
|
||||
#[inline]
|
||||
pub fn set_max_inner_size(&self, max_size: Option<Size>) {
|
||||
fn set_max_surface_size(&self, max_size: Option<Size>) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let max_size = max_size.map(|size| size.to_logical(scale_factor));
|
||||
self.window_state.lock().unwrap().set_max_inner_size(max_size);
|
||||
self.window_state.lock().unwrap().set_max_surface_size(max_size);
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
||||
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resize_increments(&self, _increments: Option<Size>) {
|
||||
warn!("`set_resize_increments` is not implemented for Wayland");
|
||||
fn set_surface_resize_increments(&self, _increments: Option<Size>) {
|
||||
warn!("`set_surface_resize_increments` is not implemented for Wayland");
|
||||
}
|
||||
|
||||
fn set_title(&self, title: &str) {
|
||||
let new_title = title.to_string();
|
||||
self.window_state.lock().unwrap().set_title(new_title);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_transparent(&self, transparent: bool) {
|
||||
fn set_transparent(&self, transparent: bool) {
|
||||
self.window_state.lock().unwrap().set_transparent(transparent);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_focus(&self) -> bool {
|
||||
self.window_state.lock().unwrap().has_focus()
|
||||
fn set_visible(&self, _visible: bool) {
|
||||
// Not possible on Wayland.
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_minimized(&self) -> Option<bool> {
|
||||
// XXX clients don't know whether they are minimized or not.
|
||||
fn is_visible(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show_window_menu(&self, position: Position) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let position = position.to_logical(scale_factor);
|
||||
self.window_state.lock().unwrap().show_window_menu(position);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
|
||||
self.window_state.lock().unwrap().drag_resize_window(direction)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
fn set_resizable(&self, resizable: bool) {
|
||||
if self.window_state.lock().unwrap().set_resizable(resizable) {
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_resizable(&self) -> bool {
|
||||
fn is_resizable(&self) -> bool {
|
||||
self.window_state.lock().unwrap().resizable()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {
|
||||
fn set_enabled_buttons(&self, _buttons: WindowButtons) {
|
||||
// TODO(kchibisov) v5 of the xdg_shell allows that.
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn enabled_buttons(&self) -> WindowButtons {
|
||||
fn enabled_buttons(&self) -> WindowButtons {
|
||||
// TODO(kchibisov) v5 of the xdg_shell allows that.
|
||||
WindowButtons::all()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.window_state.lock().unwrap().scale_factor()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_blur(&self, blur: bool) {
|
||||
self.window_state.lock().unwrap().set_blur(blur);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorate: bool) {
|
||||
self.window_state.lock().unwrap().set_decorate(decorate)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
self.window_state.lock().unwrap().is_decorated()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_level(&self, _level: WindowLevel) {}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_window_icon(&self, _window_icon: Option<PlatformIcon>) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
fn set_minimized(&self, minimized: bool) {
|
||||
// You can't unminimize the window on Wayland.
|
||||
if !minimized {
|
||||
warn!("Unminimizing is ignored on Wayland.");
|
||||
@@ -421,8 +417,20 @@ impl Window {
|
||||
self.window.set_minimized();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
fn is_minimized(&self) -> Option<bool> {
|
||||
// XXX clients don't know whether they are minimized or not.
|
||||
None
|
||||
}
|
||||
|
||||
fn set_maximized(&self, maximized: bool) {
|
||||
if maximized {
|
||||
self.window.set_maximized()
|
||||
} else {
|
||||
self.window.unset_maximized()
|
||||
}
|
||||
}
|
||||
|
||||
fn is_maximized(&self) -> bool {
|
||||
self.window_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
@@ -432,17 +440,24 @@ impl Window {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
if maximized {
|
||||
self.window.set_maximized()
|
||||
} else {
|
||||
self.window.unset_maximized()
|
||||
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
match fullscreen {
|
||||
Some(Fullscreen::Exclusive(..)) => {
|
||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
|
||||
},
|
||||
#[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
|
||||
Some(Fullscreen::Borderless(monitor)) => {
|
||||
let output = monitor.as_ref().and_then(|monitor| {
|
||||
monitor.cast_ref::<output::MonitorHandle>().map(|handle| &handle.proxy)
|
||||
});
|
||||
|
||||
self.window.set_fullscreen(output)
|
||||
},
|
||||
None => self.window.unset_fullscreen(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
let is_fullscreen = self
|
||||
.window_state
|
||||
.lock()
|
||||
@@ -453,7 +468,7 @@ impl Window {
|
||||
.unwrap_or_default();
|
||||
|
||||
if is_fullscreen {
|
||||
let current_monitor = self.current_monitor().map(PlatformMonitorHandle::Wayland);
|
||||
let current_monitor = self.current_monitor();
|
||||
Some(Fullscreen::Borderless(current_monitor))
|
||||
} else {
|
||||
None
|
||||
@@ -461,41 +476,63 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
match fullscreen {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
|
||||
},
|
||||
#[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
|
||||
Some(Fullscreen::Borderless(monitor)) => {
|
||||
let output = monitor.and_then(|monitor| match monitor {
|
||||
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
|
||||
#[cfg(x11_platform)]
|
||||
PlatformMonitorHandle::X(_) => None,
|
||||
});
|
||||
fn scale_factor(&self) -> f64 {
|
||||
self.window_state.lock().unwrap().scale_factor()
|
||||
}
|
||||
|
||||
self.window.set_fullscreen(output.as_ref())
|
||||
},
|
||||
None => self.window.unset_fullscreen(),
|
||||
#[inline]
|
||||
fn set_blur(&self, blur: bool) {
|
||||
self.window_state.lock().unwrap().set_blur(blur);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_decorations(&self, decorate: bool) {
|
||||
self.window_state.lock().unwrap().set_decorate(decorate)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_decorated(&self) -> bool {
|
||||
self.window_state.lock().unwrap().is_decorated()
|
||||
}
|
||||
|
||||
fn set_window_level(&self, _level: WindowLevel) {}
|
||||
|
||||
fn set_window_icon(&self, _window_icon: Option<crate::window::Icon>) {}
|
||||
|
||||
#[inline]
|
||||
fn set_ime_cursor_area(&self, position: Position, size: Size) {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
if window_state.ime_allowed() {
|
||||
let scale_factor = window_state.scale_factor();
|
||||
let position = position.to_logical(scale_factor);
|
||||
let size = size.to_logical(scale_factor);
|
||||
window_state.set_ime_cursor_area(position, size);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, cursor: Cursor) {
|
||||
let window_state = &mut self.window_state.lock().unwrap();
|
||||
fn set_ime_allowed(&self, allowed: bool) {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
|
||||
match cursor {
|
||||
Cursor::Icon(icon) => window_state.set_cursor(icon),
|
||||
Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
|
||||
if window_state.ime_allowed() != allowed && window_state.set_ime_allowed(allowed) {
|
||||
let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled });
|
||||
self.window_events_sink.lock().unwrap().push_window_event(event, self.window_id);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
self.window_state.lock().unwrap().set_cursor_visible(visible);
|
||||
fn set_ime_purpose(&self, purpose: ImePurpose) {
|
||||
self.window_state.lock().unwrap().set_ime_purpose(purpose);
|
||||
}
|
||||
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
fn focus_window(&self) {}
|
||||
|
||||
fn has_focus(&self) -> bool {
|
||||
self.window_state.lock().unwrap().has_focus()
|
||||
}
|
||||
|
||||
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let xdg_activation = match self.xdg_activation.as_ref() {
|
||||
Some(xdg_activation) => xdg_activation,
|
||||
None => {
|
||||
@@ -521,29 +558,26 @@ impl Window {
|
||||
xdg_activation_token.commit();
|
||||
}
|
||||
|
||||
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
|
||||
let xdg_activation = match self.xdg_activation.as_ref() {
|
||||
Some(xdg_activation) => xdg_activation,
|
||||
None => return Err(NotSupportedError::new()),
|
||||
};
|
||||
|
||||
let serial = AsyncRequestSerial::get();
|
||||
|
||||
let data = XdgActivationTokenData::Obtain((self.window_id, serial));
|
||||
let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
|
||||
xdg_activation_token.set_surface(self.surface());
|
||||
xdg_activation_token.commit();
|
||||
|
||||
Ok(serial)
|
||||
fn set_theme(&self, theme: Option<Theme>) {
|
||||
self.window_state.lock().unwrap().set_theme(theme)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
self.window_state.lock().unwrap().set_cursor_grab(mode)
|
||||
fn theme(&self) -> Option<Theme> {
|
||||
self.window_state.lock().unwrap().theme()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
|
||||
fn set_content_protected(&self, _protected: bool) {}
|
||||
|
||||
fn set_cursor(&self, cursor: Cursor) {
|
||||
let window_state = &mut self.window_state.lock().unwrap();
|
||||
|
||||
match cursor {
|
||||
Cursor::Icon(icon) => window_state.set_cursor(icon),
|
||||
Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
|
||||
let scale_factor = self.scale_factor();
|
||||
let position = position.to_logical(scale_factor);
|
||||
self.window_state
|
||||
@@ -554,124 +588,74 @@ impl Window {
|
||||
.map(|_| self.request_redraw())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> {
|
||||
self.window_state.lock().unwrap().set_cursor_grab(mode)
|
||||
}
|
||||
|
||||
fn set_cursor_visible(&self, visible: bool) {
|
||||
self.window_state.lock().unwrap().set_cursor_visible(visible);
|
||||
}
|
||||
|
||||
fn drag_window(&self) -> Result<(), RequestError> {
|
||||
self.window_state.lock().unwrap().drag_window()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
||||
fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> {
|
||||
self.window_state.lock().unwrap().drag_resize_window(direction)
|
||||
}
|
||||
|
||||
fn show_window_menu(&self, position: Position) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let position = position.to_logical(scale_factor);
|
||||
self.window_state.lock().unwrap().show_window_menu(position);
|
||||
}
|
||||
|
||||
fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
|
||||
let surface = self.window.wl_surface();
|
||||
|
||||
if hittest {
|
||||
surface.set_input_region(None);
|
||||
Ok(())
|
||||
} else {
|
||||
let region = Region::new(&*self.compositor).map_err(|_| {
|
||||
ExternalError::Os(os_error!(OsError::Misc("failed to set input region.")))
|
||||
})?;
|
||||
let region = Region::new(&*self.compositor).map_err(|err| os_error!(err))?;
|
||||
region.add(0, 0, 0, 0);
|
||||
surface.set_input_region(Some(region.wl_region()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_cursor_area(&self, position: Position, size: Size) {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
if window_state.ime_allowed() {
|
||||
let scale_factor = window_state.scale_factor();
|
||||
let position = position.to_logical(scale_factor);
|
||||
let size = size.to_logical(scale_factor);
|
||||
window_state.set_ime_cursor_area(position, size);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
|
||||
if window_state.ime_allowed() != allowed && window_state.set_ime_allowed(allowed) {
|
||||
let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled });
|
||||
self.window_events_sink.lock().unwrap().push_window_event(event, self.window_id);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_purpose(&self, purpose: ImePurpose) {
|
||||
self.window_state.lock().unwrap().set_ime_purpose(purpose);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn focus_window(&self) {}
|
||||
|
||||
#[inline]
|
||||
pub fn surface(&self) -> &WlSurface {
|
||||
self.window.wl_surface()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
|
||||
let data = self.window.wl_surface().data::<SurfaceData>()?;
|
||||
data.outputs().next().map(MonitorHandle::new)
|
||||
data.outputs()
|
||||
.next()
|
||||
.map(MonitorHandle::new)
|
||||
.map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
|
||||
self.monitors.lock().unwrap().clone()
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
|
||||
Box::new(
|
||||
self.monitors
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|inner| CoreMonitorHandle(Arc::new(inner))),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
// XXX there's no such concept on Wayland.
|
||||
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
|
||||
// NOTE: There's no such concept on Wayland.
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
Ok(rwh_06::WaylandWindowHandle::new({
|
||||
let ptr = self.window.wl_surface().id().as_ptr();
|
||||
std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null")
|
||||
})
|
||||
.into())
|
||||
/// Get the raw-window-handle v0.6 display handle.
|
||||
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
||||
Ok(rwh_06::WaylandDisplayHandle::new({
|
||||
let ptr = self.display.id().as_ptr();
|
||||
std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null")
|
||||
})
|
||||
.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_theme(&self, theme: Option<Theme>) {
|
||||
self.window_state.lock().unwrap().set_theme(theme)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn theme(&self) -> Option<Theme> {
|
||||
self.window_state.lock().unwrap().theme()
|
||||
}
|
||||
|
||||
pub fn set_content_protected(&self, _protected: bool) {}
|
||||
|
||||
#[inline]
|
||||
pub fn title(&self) -> String {
|
||||
self.window_state.lock().unwrap().title().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
self.window_requests.closed.store(true, Ordering::Relaxed);
|
||||
self.event_loop_awakener.ping();
|
||||
/// Get the raw-window-handle v0.6 window handle.
|
||||
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use sctk::reexports::client::backend::ObjectId;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
use sctk::reexports::client::{Proxy, QueueHandle};
|
||||
use sctk::reexports::csd_frame::{
|
||||
DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowState as XdgWindowState,
|
||||
};
|
||||
@@ -28,31 +28,34 @@ use sctk::subcompositor::SubcompositorState;
|
||||
use tracing::{info, warn};
|
||||
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
|
||||
|
||||
use crate::cursor::CustomCursor as RootCustomCursor;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError};
|
||||
use crate::cursor::CustomCursor as CoreCustomCursor;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Size};
|
||||
use crate::error::{NotSupportedError, RequestError};
|
||||
use crate::platform_impl::wayland::event_loop::OwnedDisplayHandle;
|
||||
use crate::platform_impl::wayland::logical_to_physical_rounded;
|
||||
use crate::platform_impl::wayland::seat::{
|
||||
PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext,
|
||||
};
|
||||
use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState};
|
||||
use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
|
||||
use crate::platform_impl::wayland::types::cursor::{
|
||||
CustomCursor, SelectedCursor, WaylandCustomCursor,
|
||||
};
|
||||
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
|
||||
use crate::platform_impl::{PlatformCustomCursor, WindowId};
|
||||
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
|
||||
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, WindowId};
|
||||
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
pub type WinitFrame = sctk_adwaita::AdwaitaFrame<WinitState>;
|
||||
#[cfg(not(feature = "sctk-adwaita"))]
|
||||
pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame<WinitState>;
|
||||
|
||||
// Minimum window inner size.
|
||||
// Minimum window surface size.
|
||||
const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
|
||||
|
||||
/// The state of the window which is being updated from the [`WinitState`].
|
||||
#[derive(Debug)]
|
||||
pub struct WindowState {
|
||||
/// The connection to Wayland server.
|
||||
pub connection: Connection,
|
||||
pub handle: Arc<OwnedDisplayHandle>,
|
||||
|
||||
/// The `Shm` to set cursor.
|
||||
pub shm: WlShm,
|
||||
@@ -112,7 +115,7 @@ pub struct WindowState {
|
||||
/// The text inputs observed on the window.
|
||||
text_inputs: Vec<ZwpTextInputV3>,
|
||||
|
||||
/// The inner size of the window, as in without client side decorations.
|
||||
/// The surface size of the window, as in without client side decorations.
|
||||
size: LogicalSize<u32>,
|
||||
|
||||
/// Whether the CSD fail to create, so we don't try to create them on each iteration.
|
||||
@@ -122,8 +125,8 @@ pub struct WindowState {
|
||||
decorate: bool,
|
||||
|
||||
/// Min size.
|
||||
min_inner_size: LogicalSize<u32>,
|
||||
max_inner_size: Option<LogicalSize<u32>>,
|
||||
min_surface_size: LogicalSize<u32>,
|
||||
max_surface_size: Option<LogicalSize<u32>>,
|
||||
|
||||
/// The size of the window when no states were applied to it. The primary use for it
|
||||
/// is to fallback to original window size, before it was maximized, if the compositor
|
||||
@@ -161,7 +164,7 @@ pub struct WindowState {
|
||||
impl WindowState {
|
||||
/// Create new window state.
|
||||
pub fn new(
|
||||
connection: Connection,
|
||||
handle: Arc<OwnedDisplayHandle>,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
winit_state: &WinitState,
|
||||
initial_size: Size,
|
||||
@@ -183,7 +186,7 @@ impl WindowState {
|
||||
blur: None,
|
||||
blur_manager: winit_state.kwin_blur_manager.clone(),
|
||||
compositor,
|
||||
connection,
|
||||
handle,
|
||||
csd_fails: false,
|
||||
cursor_grab_mode: GrabState::new(),
|
||||
selected_cursor: Default::default(),
|
||||
@@ -197,8 +200,8 @@ impl WindowState {
|
||||
ime_allowed: false,
|
||||
ime_purpose: ImePurpose::Normal,
|
||||
last_configure: None,
|
||||
max_inner_size: None,
|
||||
min_inner_size: MIN_WINDOW_SIZE,
|
||||
max_surface_size: None,
|
||||
min_surface_size: MIN_WINDOW_SIZE,
|
||||
pointer_constraints,
|
||||
pointers: Default::default(),
|
||||
queue_handle: queue_handle.clone(),
|
||||
@@ -219,9 +222,9 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Apply closure on the given pointer.
|
||||
fn apply_on_pointer<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
|
||||
fn apply_on_pointer<F: FnMut(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
|
||||
&self,
|
||||
callback: F,
|
||||
mut callback: F,
|
||||
) {
|
||||
self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| {
|
||||
let data = pointer.pointer().winit_data();
|
||||
@@ -328,7 +331,7 @@ impl WindowState {
|
||||
|
||||
// Apply configure bounds only when compositor let the user decide what size to pick.
|
||||
if constrain {
|
||||
let bounds = self.inner_size_bounds(&configure);
|
||||
let bounds = self.surface_size_bounds(&configure);
|
||||
new_size.width =
|
||||
bounds.0.map(|bound_w| new_size.width.min(bound_w.get())).unwrap_or(new_size.width);
|
||||
new_size.height = bounds
|
||||
@@ -353,7 +356,7 @@ impl WindowState {
|
||||
// NOTE: Set the configure before doing a resize, since we query it during it.
|
||||
self.last_configure = Some(configure);
|
||||
|
||||
if state_change_requires_resize || new_size != self.inner_size() {
|
||||
if state_change_requires_resize || new_size != self.surface_size() {
|
||||
self.resize(new_size);
|
||||
true
|
||||
} else {
|
||||
@@ -361,8 +364,8 @@ impl WindowState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the bounds for the inner size of the surface.
|
||||
fn inner_size_bounds(
|
||||
/// Compute the bounds for the surface size of the surface.
|
||||
fn surface_size_bounds(
|
||||
&self,
|
||||
configure: &WindowConfigure,
|
||||
) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
|
||||
@@ -388,7 +391,7 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Start interacting drag resize.
|
||||
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
|
||||
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> {
|
||||
let xdg_toplevel = self.window.xdg_toplevel();
|
||||
|
||||
// TODO(kchibisov) handle touch serials.
|
||||
@@ -402,7 +405,7 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Start the window drag.
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
pub fn drag_window(&self) -> Result<(), RequestError> {
|
||||
let xdg_toplevel = self.window.xdg_toplevel();
|
||||
// TODO(kchibisov) handle touch serials.
|
||||
self.apply_on_pointer(|_, data| {
|
||||
@@ -507,8 +510,8 @@ impl WindowState {
|
||||
// Restore min/max sizes of the window.
|
||||
self.reload_min_max_hints();
|
||||
} else {
|
||||
self.set_min_inner_size(Some(self.size));
|
||||
self.set_max_inner_size(Some(self.size));
|
||||
self.set_min_surface_size(Some(self.size));
|
||||
self.set_max_surface_size(Some(self.size));
|
||||
}
|
||||
|
||||
// Reload the state on the frame as well.
|
||||
@@ -533,7 +536,7 @@ impl WindowState {
|
||||
|
||||
/// Get the size of the window.
|
||||
#[inline]
|
||||
pub fn inner_size(&self) -> LogicalSize<u32> {
|
||||
pub fn surface_size(&self) -> LogicalSize<u32> {
|
||||
self.size
|
||||
}
|
||||
|
||||
@@ -628,21 +631,21 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Try to resize the window when the user can do so.
|
||||
pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize<u32> {
|
||||
pub fn request_surface_size(&mut self, surface_size: Size) -> PhysicalSize<u32> {
|
||||
if self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) {
|
||||
self.resize(inner_size.to_logical(self.scale_factor()))
|
||||
self.resize(surface_size.to_logical(self.scale_factor()))
|
||||
}
|
||||
|
||||
logical_to_physical_rounded(self.inner_size(), self.scale_factor())
|
||||
logical_to_physical_rounded(self.surface_size(), self.scale_factor())
|
||||
}
|
||||
|
||||
/// Resize the window to the new inner size.
|
||||
fn resize(&mut self, inner_size: LogicalSize<u32>) {
|
||||
self.size = inner_size;
|
||||
/// Resize the window to the new surface size.
|
||||
fn resize(&mut self, surface_size: LogicalSize<u32>) {
|
||||
self.size = surface_size;
|
||||
|
||||
// Update the stateless size.
|
||||
if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
|
||||
self.stateless_size = inner_size;
|
||||
self.stateless_size = surface_size;
|
||||
}
|
||||
|
||||
// Update the inner frame.
|
||||
@@ -673,7 +676,7 @@ impl WindowState {
|
||||
|
||||
// Update the target viewport, this is used if and only if fractional scaling is in use.
|
||||
if let Some(viewport) = self.viewport.as_ref() {
|
||||
// Set inner size without the borders.
|
||||
// Set surface size without the borders.
|
||||
viewport.set_destination(self.size.width as _, self.size.height as _);
|
||||
}
|
||||
}
|
||||
@@ -693,26 +696,25 @@ impl WindowState {
|
||||
}
|
||||
|
||||
self.apply_on_pointer(|pointer, _| {
|
||||
if pointer.set_cursor(&self.connection, cursor_icon).is_err() {
|
||||
if pointer.set_cursor(&self.handle.connection, cursor_icon).is_err() {
|
||||
warn!("Failed to set cursor to {:?}", cursor_icon);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the custom cursor icon.
|
||||
pub(crate) fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
|
||||
let cursor = match cursor {
|
||||
RootCustomCursor { inner: PlatformCustomCursor::Wayland(cursor) } => cursor.0,
|
||||
#[cfg(x11_platform)]
|
||||
RootCustomCursor { inner: PlatformCustomCursor::X(_) } => {
|
||||
tracing::error!("passed a X11 cursor to Wayland backend");
|
||||
pub(crate) fn set_custom_cursor(&mut self, cursor: CoreCustomCursor) {
|
||||
let cursor = match cursor.cast_ref::<WaylandCustomCursor>() {
|
||||
Some(cursor) => cursor,
|
||||
None => {
|
||||
tracing::error!("unrecognized cursor passed to Wayland backend");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let cursor = {
|
||||
let mut pool = self.custom_cursor_pool.lock().unwrap();
|
||||
CustomCursor::new(&mut pool, &cursor)
|
||||
CustomCursor::new(&mut pool, cursor)
|
||||
};
|
||||
|
||||
if self.cursor_visible {
|
||||
@@ -723,17 +725,26 @@ impl WindowState {
|
||||
}
|
||||
|
||||
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
|
||||
self.apply_on_pointer(|pointer, _| {
|
||||
self.apply_on_pointer(|pointer, data| {
|
||||
let surface = pointer.surface();
|
||||
|
||||
let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
|
||||
let scale = if let Some(viewport) = data.viewport() {
|
||||
let scale = self.scale_factor();
|
||||
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
|
||||
viewport.set_destination(size.width, size.height);
|
||||
scale
|
||||
} else {
|
||||
let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
|
||||
surface.set_buffer_scale(scale);
|
||||
scale as f64
|
||||
};
|
||||
|
||||
surface.set_buffer_scale(scale);
|
||||
surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
|
||||
if surface.version() >= 4 {
|
||||
surface.damage_buffer(0, 0, cursor.w, cursor.h);
|
||||
} else {
|
||||
surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
|
||||
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
|
||||
surface.damage(0, 0, size.width, size.height);
|
||||
}
|
||||
surface.commit();
|
||||
|
||||
@@ -743,17 +754,14 @@ impl WindowState {
|
||||
.and_then(|data| data.pointer_data().latest_enter_serial())
|
||||
.unwrap();
|
||||
|
||||
pointer.pointer().set_cursor(
|
||||
serial,
|
||||
Some(surface),
|
||||
cursor.hotspot_x / scale,
|
||||
cursor.hotspot_y / scale,
|
||||
);
|
||||
let hotspot =
|
||||
PhysicalPosition::new(cursor.hotspot_x, cursor.hotspot_y).to_logical(scale);
|
||||
pointer.pointer().set_cursor(serial, Some(surface), hotspot.x, hotspot.y);
|
||||
});
|
||||
}
|
||||
|
||||
/// Set maximum inner window size.
|
||||
pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
|
||||
pub fn set_min_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
|
||||
// Ensure that the window has the right minimum size.
|
||||
let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
|
||||
size.width = size.width.max(MIN_WINDOW_SIZE.width);
|
||||
@@ -766,12 +774,12 @@ impl WindowState {
|
||||
.map(|frame| frame.add_borders(size.width, size.height).into())
|
||||
.unwrap_or(size);
|
||||
|
||||
self.min_inner_size = size;
|
||||
self.min_surface_size = size;
|
||||
self.window.set_min_size(Some(size.into()));
|
||||
}
|
||||
|
||||
/// Set maximum inner window size.
|
||||
pub fn set_max_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
|
||||
pub fn set_max_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
|
||||
let size = size.map(|size| {
|
||||
self.frame
|
||||
.as_ref()
|
||||
@@ -779,7 +787,7 @@ impl WindowState {
|
||||
.unwrap_or(size)
|
||||
});
|
||||
|
||||
self.max_inner_size = size;
|
||||
self.max_surface_size = size;
|
||||
self.window.set_max_size(size.map(Into::into));
|
||||
}
|
||||
|
||||
@@ -799,7 +807,7 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Set the cursor grabbing state on the top-level.
|
||||
pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), RequestError> {
|
||||
if self.cursor_grab_mode.user_grab_mode == mode {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -812,46 +820,67 @@ impl WindowState {
|
||||
|
||||
/// Reload the hints for minimum and maximum sizes.
|
||||
pub fn reload_min_max_hints(&mut self) {
|
||||
self.set_min_inner_size(Some(self.min_inner_size));
|
||||
self.set_max_inner_size(self.max_inner_size);
|
||||
self.set_min_surface_size(Some(self.min_surface_size));
|
||||
self.set_max_surface_size(self.max_surface_size);
|
||||
}
|
||||
|
||||
/// Set the grabbing state on the surface.
|
||||
fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), RequestError> {
|
||||
let pointer_constraints = match self.pointer_constraints.as_ref() {
|
||||
Some(pointer_constraints) => pointer_constraints,
|
||||
None if mode == CursorGrabMode::None => return Ok(()),
|
||||
None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
|
||||
None => {
|
||||
return Err(
|
||||
NotSupportedError::new("zwp_pointer_constraints is not available").into()
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
// Replace the current mode.
|
||||
let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode);
|
||||
|
||||
match old_mode {
|
||||
CursorGrabMode::None => (),
|
||||
let mut unset_old = false;
|
||||
match self.cursor_grab_mode.current_grab_mode {
|
||||
CursorGrabMode::None => unset_old = true,
|
||||
CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
|
||||
data.unconfine_pointer();
|
||||
unset_old = true;
|
||||
}),
|
||||
CursorGrabMode::Locked => {
|
||||
self.apply_on_pointer(|_, data| data.unlock_pointer());
|
||||
self.apply_on_pointer(|_, data| {
|
||||
data.unlock_pointer();
|
||||
unset_old = true;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// In case we haven't unset the old mode, it means that we don't have a cursor above
|
||||
// the window, thus just wait for it to re-appear.
|
||||
if !unset_old {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut set_mode = false;
|
||||
let surface = self.window.wl_surface();
|
||||
match mode {
|
||||
CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
|
||||
let pointer = pointer.pointer();
|
||||
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
|
||||
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
|
||||
set_mode = true;
|
||||
}),
|
||||
CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
|
||||
let pointer = pointer.pointer();
|
||||
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
|
||||
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
|
||||
set_mode = true;
|
||||
}),
|
||||
CursorGrabMode::None => {
|
||||
// Current lock/confine was already removed.
|
||||
set_mode = true;
|
||||
},
|
||||
}
|
||||
|
||||
// Replace the current grab mode after we've ensure that it got updated.
|
||||
if set_mode {
|
||||
self.cursor_grab_mode.current_grab_mode = mode;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -865,16 +894,17 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Set the position of the cursor.
|
||||
pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), ExternalError> {
|
||||
pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), RequestError> {
|
||||
if self.pointer_constraints.is_none() {
|
||||
return Err(ExternalError::NotSupported(NotSupportedError::new()));
|
||||
return Err(NotSupportedError::new("zwp_pointer_constraints is not available").into());
|
||||
}
|
||||
|
||||
// Position can be set only for locked cursor.
|
||||
if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
|
||||
return Err(ExternalError::Os(os_error!(crate::platform_impl::OsError::Misc(
|
||||
"cursor position can be set only for locked cursor."
|
||||
))));
|
||||
return Err(NotSupportedError::new(
|
||||
"cursor position could only be changed for locked pointer",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
self.apply_on_pointer(|_, data| {
|
||||
@@ -1091,7 +1121,7 @@ impl Drop for WindowState {
|
||||
}
|
||||
|
||||
/// The state of the cursor grabs.
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct GrabState {
|
||||
/// The grab mode requested by the user.
|
||||
user_grab_mode: CursorGrabMode,
|
||||
|
||||
@@ -165,14 +165,14 @@ fn push_display(buffer: &mut Vec<u8>, display: &impl std::fmt::Display) {
|
||||
buffer: &'a mut Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Write for Writer<'a> {
|
||||
impl std::fmt::Write for Writer<'_> {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
self.buffer.extend_from_slice(s.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
write!(Writer { buffer }, "{}", display).unwrap();
|
||||
write!(Writer { buffer }, "{display}").unwrap();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::str::Utf8Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
use dpi::PhysicalPosition;
|
||||
use percent_encoding::percent_decode;
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt};
|
||||
|
||||
@@ -38,6 +39,7 @@ impl From<io::Error> for DndDataParseError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Dnd {
|
||||
xconn: Arc<XConnection>,
|
||||
// Populated by XdndEnter event handler
|
||||
@@ -45,13 +47,25 @@ pub struct Dnd {
|
||||
pub type_list: Option<Vec<xproto::Atom>>,
|
||||
// Populated by XdndPosition event handler
|
||||
pub source_window: Option<xproto::Window>,
|
||||
// Populated by XdndPosition event handler
|
||||
pub position: PhysicalPosition<f64>,
|
||||
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
|
||||
pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>,
|
||||
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
|
||||
pub dragging: bool,
|
||||
}
|
||||
|
||||
impl Dnd {
|
||||
pub fn new(xconn: Arc<XConnection>) -> Result<Self, X11Error> {
|
||||
Ok(Dnd { xconn, version: None, type_list: None, source_window: None, result: None })
|
||||
Ok(Dnd {
|
||||
xconn,
|
||||
version: None,
|
||||
type_list: None,
|
||||
source_window: None,
|
||||
position: PhysicalPosition::default(),
|
||||
result: None,
|
||||
dragging: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
@@ -59,6 +73,7 @@ impl Dnd {
|
||||
self.type_list = None;
|
||||
self.source_window = None;
|
||||
self.result = None;
|
||||
self.dragging = false;
|
||||
}
|
||||
|
||||
pub unsafe fn send_status(
|
||||
|
||||
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