mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
221 Commits
v0.29.2
...
rwh-send-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e66eba38f5 | ||
|
|
e9a25a4c91 | ||
|
|
674657efb6 | ||
|
|
7d5bee767c | ||
|
|
745cfaab2c | ||
|
|
a8f49dc8ef | ||
|
|
e5310ade08 | ||
|
|
37946e0a3a | ||
|
|
86b737f5e7 | ||
|
|
e37585e5bc | ||
|
|
4aeeb24745 | ||
|
|
8cd3aaa8a2 | ||
|
|
2c15de7cf9 | ||
|
|
0a7ea61834 | ||
|
|
4ee11018c2 | ||
|
|
4f669ebbd2 | ||
|
|
7761b2b16c | ||
|
|
ae41e3265f | ||
|
|
8702a09333 | ||
|
|
8b5c84f404 | ||
|
|
a676d0018b | ||
|
|
04ca85a909 | ||
|
|
cc33212479 | ||
|
|
f2c5127f27 | ||
|
|
af93167237 | ||
|
|
7f6b16a6af | ||
|
|
bf5806a9b2 | ||
|
|
2a9c593e01 | ||
|
|
becdd0dbd2 | ||
|
|
3eea505440 | ||
|
|
b863283c38 | ||
|
|
73718c9f2f | ||
|
|
f735f028a1 | ||
|
|
da947992ac | ||
|
|
e9784127df | ||
|
|
0be2bb0a8c | ||
|
|
075996b1fa | ||
|
|
a7241b3db3 | ||
|
|
17296e9878 | ||
|
|
b3c87caa7c | ||
|
|
81a1d9c396 | ||
|
|
5612626944 | ||
|
|
d3ca685b77 | ||
|
|
7bed5eecfd | ||
|
|
14140607d1 | ||
|
|
eab982c402 | ||
|
|
21701a33de | ||
|
|
c89e6df758 | ||
|
|
e9210555c1 | ||
|
|
0994b5ceb8 | ||
|
|
bcce5134e1 | ||
|
|
d333dd8664 | ||
|
|
52af1b4a77 | ||
|
|
3c9f9da19e | ||
|
|
075dfcea19 | ||
|
|
92b7dcccc1 | ||
|
|
5a3be586f4 | ||
|
|
12dbbf8012 | ||
|
|
53ca5af48f | ||
|
|
c235bd154a | ||
|
|
f4e71a1d9c | ||
|
|
62ed51a138 | ||
|
|
b2a2ec91ae | ||
|
|
d37d1a03b2 | ||
|
|
772b21ce09 | ||
|
|
2edcd09704 | ||
|
|
d35c3bea42 | ||
|
|
89a184ed84 | ||
|
|
36d4907da8 | ||
|
|
98b3508aca | ||
|
|
c0db53a516 | ||
|
|
52b7205b75 | ||
|
|
41dbbc27a0 | ||
|
|
c346fb7e61 | ||
|
|
6a041f84ba | ||
|
|
acfeff5327 | ||
|
|
b9e1e96eaa | ||
|
|
880238a24f | ||
|
|
f5b4d6938f | ||
|
|
c65e2247a1 | ||
|
|
801fddbfcf | ||
|
|
3ad64fb811 | ||
|
|
9bf4493a21 | ||
|
|
48f6582eb4 | ||
|
|
ef34692148 | ||
|
|
c48116a8fd | ||
|
|
b938fe9df5 | ||
|
|
b7e3649e8b | ||
|
|
844269d017 | ||
|
|
e41fac825c | ||
|
|
bbeacc46d5 | ||
|
|
61581ebb4f | ||
|
|
93f1000a05 | ||
|
|
1ea41a2ee2 | ||
|
|
42c9b7e40e | ||
|
|
0960635895 | ||
|
|
789a497980 | ||
|
|
fac6110cb6 | ||
|
|
0363be4776 | ||
|
|
f5dd1c008c | ||
|
|
48a1e84906 | ||
|
|
ee0db52ac4 | ||
|
|
c7cf0cfd83 | ||
|
|
8393d98940 | ||
|
|
af247eac0f | ||
|
|
b2b4564a5f | ||
|
|
cb58c49a90 | ||
|
|
ffb46dd61f | ||
|
|
8c8fb39fcd | ||
|
|
2422ea39d0 | ||
|
|
878d832d24 | ||
|
|
e2e01e1fc6 | ||
|
|
c8b685ddbc | ||
|
|
f10ae52385 | ||
|
|
e731041c15 | ||
|
|
9df7fc47a1 | ||
|
|
992aeb0ca0 | ||
|
|
0caba93b51 | ||
|
|
c00c1e9eb7 | ||
|
|
83950acd5a | ||
|
|
b99403b1b9 | ||
|
|
4f0ce7201d | ||
|
|
e648169861 | ||
|
|
8fdd81ecef | ||
|
|
7a2a2341c2 | ||
|
|
d68d9eab38 | ||
|
|
a06ea45c0f | ||
|
|
67b041e231 | ||
|
|
6dfc78fb50 | ||
|
|
477619c0a7 | ||
|
|
5f1a4b65ad | ||
|
|
bb9b629bc3 | ||
|
|
0c8cf94a70 | ||
|
|
1dfca5a395 | ||
|
|
7541220a41 | ||
|
|
7e11912d22 | ||
|
|
86baa1c99a | ||
|
|
d9f04780cc | ||
|
|
67d3fd28f7 | ||
|
|
a3cba838ea | ||
|
|
48abf52aac | ||
|
|
68ef9f707e | ||
|
|
9979441c82 | ||
|
|
309e6aa85a | ||
|
|
8b8556798e | ||
|
|
2d96480a89 | ||
|
|
6caff77abb | ||
|
|
af6c343d0e | ||
|
|
119462795a | ||
|
|
f801c4a00b | ||
|
|
f9758528f6 | ||
|
|
778d70c001 | ||
|
|
dc973883c9 | ||
|
|
2233edb9a0 | ||
|
|
bd2f1e8312 | ||
|
|
e9ebf1e5f4 | ||
|
|
5f7955cb2b | ||
|
|
793c535b01 | ||
|
|
ed26dd58fd | ||
|
|
584aab4cd0 | ||
|
|
8100a6a584 | ||
|
|
cad3277550 | ||
|
|
3c3a863cc9 | ||
|
|
8a7e18aaf0 | ||
|
|
57fad2ce15 | ||
|
|
7a58fe58ce | ||
|
|
38f28d5836 | ||
|
|
189a0080a6 | ||
|
|
b5aa96bea4 | ||
|
|
19e3906369 | ||
|
|
9ac3259a79 | ||
|
|
2b2dd6b65d | ||
|
|
75173118b0 | ||
|
|
e33d2bee6c | ||
|
|
ae7497e18f | ||
|
|
935146d299 | ||
|
|
755c533b08 | ||
|
|
ae9b02e097 | ||
|
|
e6c7cc297d | ||
|
|
b74cee8df1 | ||
|
|
e5eb253698 | ||
|
|
ec11b4877f | ||
|
|
9e46dffcc5 | ||
|
|
7501039d57 | ||
|
|
289ce32d77 | ||
|
|
0d366ffbda | ||
|
|
a6f414d732 | ||
|
|
c47d0846fa | ||
|
|
461efaf99f | ||
|
|
420840278b | ||
|
|
f5e73b0af4 | ||
|
|
f40b5f0dad | ||
|
|
c91402efb9 | ||
|
|
43acf7f42f | ||
|
|
c62e64060b | ||
|
|
f7a84a5b50 | ||
|
|
89aa7cc06e | ||
|
|
b166e1ff13 | ||
|
|
97434d8d80 | ||
|
|
06fb089633 | ||
|
|
4d6dbea74c | ||
|
|
c5941d105f | ||
|
|
b63164645b | ||
|
|
5b5ebc25d8 | ||
|
|
d7ec899d69 | ||
|
|
5379d60e4d | ||
|
|
44e2f95331 | ||
|
|
3b2d1a7643 | ||
|
|
50b17a3907 | ||
|
|
af26f01b95 | ||
|
|
c4d70d75c1 | ||
|
|
db8de03142 | ||
|
|
ff0ce9d065 | ||
|
|
42e492cde8 | ||
|
|
5e0e1e96bc | ||
|
|
bd890e69aa | ||
|
|
bca57ed0b4 | ||
|
|
4652d48105 | ||
|
|
96c0b267e2 | ||
|
|
81fd39485f | ||
|
|
6178acede8 |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain: [stable, nightly, '1.65.0']
|
||||
toolchain: [stable, nightly, '1.70.0']
|
||||
platform:
|
||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
||||
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||
@@ -43,10 +43,10 @@ jobs:
|
||||
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
||||
exclude:
|
||||
# Android is tested on stable-3
|
||||
- toolchain: '1.65.0'
|
||||
- toolchain: '1.70.0'
|
||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
include:
|
||||
- toolchain: '1.69.0'
|
||||
- toolchain: '1.70.0'
|
||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
|
||||
env:
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
- name: Build tests
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
matrix.toolchain != '1.70.0'
|
||||
run: cargo $CMD test --no-run $OPTIONS
|
||||
|
||||
- name: Run tests
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
matrix.toolchain != '1.70.0'
|
||||
run: cargo $CMD test $OPTIONS
|
||||
|
||||
- name: Lint with clippy
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
- name: Build tests with serde enabled
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
matrix.toolchain != '1.70.0'
|
||||
run: cargo $CMD test --no-run $OPTIONS --features serde
|
||||
|
||||
- name: Run tests with serde enabled
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
matrix.toolchain != '1.70.0'
|
||||
run: cargo $CMD test $OPTIONS --features serde
|
||||
|
||||
# See restore step above
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "deps/apk-builder"]
|
||||
path = deps/apk-builder
|
||||
url = https://github.com/rust-windowing/android-rs-glue
|
||||
48
CHANGELOG.md
48
CHANGELOG.md
@@ -11,6 +11,54 @@ Unreleased` header.
|
||||
|
||||
# Unreleased
|
||||
|
||||
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
|
||||
- Add `Window::set_custom_cursor`
|
||||
- Add `CustomCursor`
|
||||
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
|
||||
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
|
||||
- On macOS, add services menu.
|
||||
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
|
||||
- On Web, fix setting cursor icon overriding cursor visibility.
|
||||
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
|
||||
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
|
||||
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
|
||||
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
|
||||
|
||||
# 0.29.5
|
||||
|
||||
- On macOS, remove spurious error logging when handling `Fn`.
|
||||
- On X11, fix an issue where floating point data from the server is
|
||||
misinterpreted during a drag and drop operation.
|
||||
- On X11, fix a bug where focusing the window would panic.
|
||||
- On macOS, fix `refresh_rate_millihertz`.
|
||||
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
|
||||
- On X11, fix `Xft.dpi` detection from Xresources.
|
||||
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
|
||||
- On Wayland, fix resize being sent on focus change.
|
||||
- On Windows, fix `set_ime_cursor_area`.
|
||||
|
||||
# 0.29.4
|
||||
|
||||
- Fix crash when running iOS app on macOS.
|
||||
- On X11, check common alternative cursor names when loading cursor.
|
||||
- On X11, reload the DPI after a property change event.
|
||||
- On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread.
|
||||
- On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account.
|
||||
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
|
||||
- On Wayland, fix `wl_surface` being destroyed before associated objects.
|
||||
- On macOS, fix assertion when pressing `Fn` key.
|
||||
|
||||
# 0.29.3
|
||||
|
||||
- On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible.
|
||||
- On Wayland, fix `RedrawRequsted` being always sent without decorations and `sctk-adwaita` feature.
|
||||
- On Wayland, ignore resize requests when the window is fully tiled.
|
||||
- On Wayland, use `configure_bounds` to constrain `with_inner_size` when compositor wants users to pick size.
|
||||
- On Windows, fix deadlock when accessing the state during `Cursor{Enter,Leave}`.
|
||||
- On Windows, add support for `Window::set_transparent`.
|
||||
- On macOS, fix deadlock when entering a nested event loop from an event handler.
|
||||
- On macOS, add support for `Window::set_blur`.
|
||||
|
||||
# 0.29.2
|
||||
|
||||
- **Breaking:** Bump MSRV from `1.60` to `1.65`.
|
||||
|
||||
@@ -11,7 +11,7 @@ may be worth creating a separate crate that extends Winit's API to add that func
|
||||
When reporting an issue, in order to help the maintainers understand what the problem is, please make
|
||||
your description of the issue as detailed as possible:
|
||||
|
||||
- if it is a bug, please provide clear explanation of what happens, what should happen, and how to
|
||||
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
|
||||
reproduce the issue, ideally by providing a minimal program exhibiting the problem
|
||||
- if it is a feature request, please provide a clear argumentation about why you believe this feature
|
||||
should be supported by winit
|
||||
@@ -20,8 +20,8 @@ your description of the issue as detailed as possible:
|
||||
|
||||
When making a code contribution to winit, before opening your pull request, please make sure that:
|
||||
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.65.
|
||||
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.70.
|
||||
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
|
||||
were not tested, and what should be tested, so that a maintainer or another contributor can test them
|
||||
- you updated any relevant documentation in winit
|
||||
- you left comments in your code explaining any part that is not straightforward, so that the
|
||||
@@ -34,7 +34,7 @@ When making a code contribution to winit, before opening your pull request, plea
|
||||
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
|
||||
should be updated.
|
||||
|
||||
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
|
||||
Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy
|
||||
is that a PR must be approved by at least two maintainers of winit before being merged, including
|
||||
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
|
||||
|
||||
@@ -46,27 +46,26 @@ Once your PR is deemed ready, the merging maintainer will take care of resolving
|
||||
|
||||
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
|
||||
|
||||
If you are interested in being pinged when testing is needed for a certain platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
|
||||
If you are interested in being pinged when testing is needed for a specific platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
|
||||
|
||||
## Release process
|
||||
|
||||
Given that winit is a widely used library we should be able to make a patch
|
||||
Given that winit is a widely used library, we should be able to make a patch
|
||||
releases at any time we want without blocking the development of new features.
|
||||
|
||||
To achieve these goals, a new branch is created for every new release. Releases
|
||||
and later patch releases are committed and tagged in this branch.
|
||||
To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch.
|
||||
|
||||
The exact steps for an exemplary `0.2.0` release might look like this:
|
||||
1. Initially the version on the latest master is `0.1.0`
|
||||
1. Initially, the version on the latest master is `0.1.0`
|
||||
2. A new `v0.2.x` branch is created for the release
|
||||
3. In the branch, the version is bumped to `v0.2.0`
|
||||
4. The new commit in the branch is tagged `v0.2.0`
|
||||
5. The version is pushed to crates.io
|
||||
6. A GitHub release is created for the `v0.2.0` tag
|
||||
7. On master, the version is bumped to `0.2.0` and the CHANGELOG is updated
|
||||
7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated
|
||||
|
||||
When doing a patch release the process is similar:
|
||||
1. Initially the version of the latest release is `0.2.0`
|
||||
When doing a patch release, the process is similar:
|
||||
1. Initially, the version of the latest release is `0.2.0`
|
||||
2. Checkout the `v0.2.x` branch
|
||||
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
|
||||
4. Follow steps 3-7 of the regular release example
|
||||
|
||||
58
Cargo.toml
58
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.29.2"
|
||||
version = "0.29.5"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2021"
|
||||
@@ -10,10 +10,17 @@ readme = "README.md"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
rust-version = "1.65.0"
|
||||
rust-version = "1.70.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["rwh_04", "rwh_05", "rwh_06", "serde"]
|
||||
features = [
|
||||
"rwh_04",
|
||||
"rwh_05",
|
||||
"rwh_06",
|
||||
"serde",
|
||||
# Enabled to get docs to compile
|
||||
"android-native-activity",
|
||||
]
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
# These are all tested in CI
|
||||
targets = [
|
||||
@@ -54,7 +61,7 @@ cfg_aliases = "0.1.1"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
cursor-icon = "1.0.0"
|
||||
cursor-icon = "1.1.0"
|
||||
log = "0.4"
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
once_cell = "1.12"
|
||||
@@ -74,18 +81,18 @@ softbuffer = "0.3.0"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android-activity = "0.5.0"
|
||||
ndk = "0.8.0"
|
||||
ndk = { version = "0.8.0", default-features = false }
|
||||
ndk-sys = "0.5.0"
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
core-foundation = "0.9.3"
|
||||
objc2 = "0.4.1"
|
||||
objc2 = "0.5.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.23.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.icrate]
|
||||
version = "0.0.4"
|
||||
version = "0.1.0"
|
||||
features = [
|
||||
"dispatch",
|
||||
"Foundation",
|
||||
@@ -98,10 +105,31 @@ features = [
|
||||
"Foundation_NSProcessInfo",
|
||||
"Foundation_NSThread",
|
||||
"Foundation_NSNumber",
|
||||
"AppKit",
|
||||
"AppKit_NSAppearance",
|
||||
"AppKit_NSApplication",
|
||||
"AppKit_NSBitmapImageRep",
|
||||
"AppKit_NSButton",
|
||||
"AppKit_NSColor",
|
||||
"AppKit_NSControl",
|
||||
"AppKit_NSCursor",
|
||||
"AppKit_NSEvent",
|
||||
"AppKit_NSGraphicsContext",
|
||||
"AppKit_NSImage",
|
||||
"AppKit_NSImageRep",
|
||||
"AppKit_NSMenu",
|
||||
"AppKit_NSMenuItem",
|
||||
"AppKit_NSPasteboard",
|
||||
"AppKit_NSResponder",
|
||||
"AppKit_NSScreen",
|
||||
"AppKit_NSTextInputContext",
|
||||
"AppKit_NSView",
|
||||
"AppKit_NSWindow",
|
||||
"AppKit_NSWindowTabGroup",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies.icrate]
|
||||
version = "0.0.4"
|
||||
version = "0.1.0"
|
||||
features = [
|
||||
"dispatch",
|
||||
"Foundation",
|
||||
@@ -153,13 +181,13 @@ memmap2 = { version = "0.9.0", optional = true }
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
|
||||
sctk-adwaita = { version = "0.7.0", default_features = false, optional = true }
|
||||
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
|
||||
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
|
||||
wayland-client = { version = "0.31.1", optional = true }
|
||||
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
|
||||
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
||||
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
||||
xkbcommon-dl = "0.4.0"
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
@@ -172,6 +200,7 @@ version = "0.3.64"
|
||||
features = [
|
||||
'AbortController',
|
||||
'AbortSignal',
|
||||
'Blob',
|
||||
'console',
|
||||
'CssStyleDeclaration',
|
||||
'Document',
|
||||
@@ -183,6 +212,11 @@ features = [
|
||||
'FocusEvent',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'HtmlImageElement',
|
||||
'ImageBitmap',
|
||||
'ImageBitmapOptions',
|
||||
'ImageBitmapRenderingContext',
|
||||
'ImageData',
|
||||
'IntersectionObserver',
|
||||
'IntersectionObserverEntry',
|
||||
'KeyboardEvent',
|
||||
@@ -192,6 +226,7 @@ features = [
|
||||
'Node',
|
||||
'PageTransitionEvent',
|
||||
'PointerEvent',
|
||||
'PremultiplyAlpha',
|
||||
'ResizeObserver',
|
||||
'ResizeObserverBoxOptions',
|
||||
'ResizeObserverEntry',
|
||||
@@ -199,7 +234,8 @@ features = [
|
||||
'ResizeObserverSize',
|
||||
'VisibilityState',
|
||||
'Window',
|
||||
'WheelEvent'
|
||||
'WheelEvent',
|
||||
'Url',
|
||||
]
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
|
||||
18
FEATURES.md
18
FEATURES.md
@@ -1,6 +1,6 @@
|
||||
# Winit Scope
|
||||
|
||||
Winit aims to expose an interface that abstracts over window creation and input handling, and can
|
||||
Winit aims to expose an interface that abstracts over window creation and input handling and can
|
||||
be used to create both games and applications. It supports the following main graphical platforms:
|
||||
- Desktop
|
||||
- Windows 7+ (10+ is tested regularly)
|
||||
@@ -45,10 +45,10 @@ be released and the library will enter maintenance mode. For the most part, new
|
||||
be added past this point. New platform features may be accepted and exposed through point releases.
|
||||
|
||||
### Tier upgrades
|
||||
Some platform features could in theory be exposed across multiple platforms, but have not gone
|
||||
Some platform features could, in theory, be exposed across multiple platforms, but have not gone
|
||||
through the implementation work necessary to function on all platforms. When one of these features
|
||||
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
|
||||
If that gets accepted, the platform-specific functions gets deprecated and become permanently
|
||||
If that gets accepted, the platform-specific functions get deprecated and become permanently
|
||||
exposed through the core, cross-platform API.
|
||||
|
||||
# Features
|
||||
@@ -88,7 +88,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **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
|
||||
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
|
||||
@@ -105,7 +105,8 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **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 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.
|
||||
@@ -151,12 +152,12 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
* Valid orientations
|
||||
* Home indicator visibility
|
||||
* Status bar visibility and style
|
||||
* Deferrring system gestures
|
||||
* Deferring system gestures
|
||||
* Getting the device idiom
|
||||
* Getting the preferred video mode
|
||||
|
||||
### Web
|
||||
* Get if systems preferred color scheme is "dark"
|
||||
* Get if the systems preferred color scheme is "dark"
|
||||
|
||||
## Usability
|
||||
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
||||
@@ -166,7 +167,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
Legend:
|
||||
|
||||
- ✔️: Works as intended
|
||||
- ▢: Mostly works but some bugs are known
|
||||
- ▢: Mostly works, but some bugs are known
|
||||
- ❌: Missing feature or large bugs making it unusable
|
||||
- **N/A**: Not applicable for this platform
|
||||
- ❓: Unknown status
|
||||
@@ -206,6 +207,7 @@ Legend:
|
||||
|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** |
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
|
||||
The winit maintainers would like to recognize the following former winit
|
||||
contributors, without whom winit would not exist in its current form. We thank
|
||||
them deeply for their time and efforts, and wish them best of luck in their
|
||||
them deeply for their time and efforts and wish them the best of luck in their
|
||||
future endeavors:
|
||||
|
||||
* [@tomaka]: For creating the winit project and guiding it through its early
|
||||
years of existence.
|
||||
* [@vberger]: For diligently creating the Wayland backend, and being its
|
||||
* [@vberger]: For diligently creating the Wayland backend and being its
|
||||
extremely helpful and benevolent maintainer for years.
|
||||
* [@francesca64]: For taking over the responsibility of maintaining almost every
|
||||
winit backend, and standardizing HiDPI support across all of them.
|
||||
* [@Osspial]: For heroically landing EventLoop 2.0, and valiantly ushering in a
|
||||
winit backend and standardizing HiDPI support across all of them.
|
||||
* [@Osspial]: For heroically landing EventLoop 2.0 and valiantly ushering in a
|
||||
vastly more sustainable era of winit.
|
||||
* [@goddessfreya]: For selflessly taking over maintainership of glutin, and her
|
||||
* [@goddessfreya]: For selflessly taking over maintainership of glutin and her
|
||||
stellar dedication to improving both winit and glutin.
|
||||
* [@ArturKovacs]: For consistently maintaining the macOS backend, and his
|
||||
immense involvement in designing and implementing the new keyboard API.
|
||||
* [@ArturKovacs]: For consistently maintaining the macOS backend and for his immense involvement in designing and implementing the new keyboard API.
|
||||
|
||||
[@tomaka]: https://github.com/tomaka
|
||||
[@vberger]: https://github.com/vberger
|
||||
|
||||
31
README.md
31
README.md
@@ -6,7 +6,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.29.2"
|
||||
winit = "0.29.5"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -26,7 +26,7 @@ Join us in any of these:
|
||||
|
||||
Winit is a window creation and management library. It can create windows and lets you handle
|
||||
events (for example: the window being resized, a key being pressed, a mouse movement, etc.)
|
||||
produced by window.
|
||||
produced by the window.
|
||||
|
||||
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to
|
||||
show something on the window you need to use the platform-specific getters provided by winit, or
|
||||
@@ -42,7 +42,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
|
||||
|
||||
## MSRV Policy
|
||||
|
||||
The Minimum Supported Rust Version (MSRV) of this crate is **1.65**. Changes to
|
||||
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
|
||||
the MSRV will be accompanied by a minor version bump.
|
||||
|
||||
As a **tentative** policy, the upper bound of the MSRV is given by the following
|
||||
@@ -53,12 +53,11 @@ min(sid, stable - 3)
|
||||
```
|
||||
|
||||
Where `sid` is the current version of `rustc` provided by [Debian Sid], and
|
||||
`stable` is the latest stable version of Rust. This bound may be broken in the
|
||||
event of a major ecosystem shift or a security vulnerability.
|
||||
`stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability.
|
||||
|
||||
[Debian Sid]: https://packages.debian.org/sid/rustc
|
||||
|
||||
The exception to this is for the Android platform, where a higher Rust version
|
||||
The exception is for the Android platform, where a higher Rust version
|
||||
must be used for certain Android features. In this case, the MSRV will be
|
||||
capped at the latest stable version of Rust minus three. This inconsistency is
|
||||
not reflected in Cargo metadata, as it is not powerful enough to expose this
|
||||
@@ -86,7 +85,7 @@ either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
|
||||
create a `<canvas>` element which you can then retrieve][web canvas getter] and
|
||||
insert it into the DOM yourself.
|
||||
|
||||
For example code using Winit with WebAssembly, check out the [web example]. For
|
||||
For the example code using Winit with WebAssembly, check out the [web example]. For
|
||||
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
|
||||
book].
|
||||
|
||||
@@ -109,14 +108,14 @@ glue crate (prior to `0.28` it used
|
||||
|
||||
The version of the glue crate that your application depends on _must_ match the
|
||||
version that Winit depends on because the glue crate is responsible for your
|
||||
application's main entrypoint. If Cargo resolves multiple versions they will
|
||||
application's main entry point. If Cargo resolves multiple versions, they will
|
||||
clash.
|
||||
|
||||
`winit` glue compatibility table:
|
||||
|
||||
| winit | ndk-glue |
|
||||
| :---: | :--------------------------: |
|
||||
| 0.29.2| `android-activity = "0.5"` |
|
||||
| 0.29 | `android-activity = "0.5"` |
|
||||
| 0.28 | `android-activity = "0.4"` |
|
||||
| 0.27 | `ndk-glue = "0.7"` |
|
||||
| 0.26 | `ndk-glue = "0.5"` |
|
||||
@@ -127,7 +126,7 @@ The recommended way to avoid a conflict with the glue version is to avoid explic
|
||||
depending on the `android-activity` crate, and instead consume the API that
|
||||
is re-exported by Winit under `winit::platform::android::activity::*`
|
||||
|
||||
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
|
||||
Running on an Android device needs a dynamic system library. Add this to Cargo.toml:
|
||||
|
||||
```toml
|
||||
[lib]
|
||||
@@ -135,14 +134,14 @@ name = "main"
|
||||
crate-type = ["cdylib"]
|
||||
```
|
||||
|
||||
All Android applications are based on an `Activity` subclass and the
|
||||
All Android applications are based on an `Activity` subclass, and the
|
||||
`android-activity` crate is designed to support different choices for this base
|
||||
class. Your application _must_ specify the base class it needs via a feature flag:
|
||||
|
||||
| Base Class | Feature Flag | Notes |
|
||||
| :--------------: | :---------------: | :-----: |
|
||||
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
|
||||
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
|
||||
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
|
||||
|
||||
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
|
||||
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
|
||||
@@ -155,9 +154,9 @@ For more details, refer to these `android-activity` [example applications](https
|
||||
|
||||
##### Converting from `ndk-glue` to `android-activity`
|
||||
|
||||
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be:
|
||||
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.29.2", features = [ "android-native-activity" ] }`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.5", 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 logging as above).
|
||||
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
|
||||
|
||||
@@ -173,7 +172,7 @@ If you encounter problems, you should try doing your initialization inside
|
||||
#### iOS
|
||||
|
||||
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
|
||||
by all UI related code, see issue [#1705]. You should consider creating your windows
|
||||
by all UI-related code (see issue [#1705]). It would be best to consider creating your windows
|
||||
inside `Event::Resumed`.
|
||||
|
||||
|
||||
@@ -184,5 +183,5 @@ inside `Event::Resumed`.
|
||||
|
||||
#### Redox OS
|
||||
|
||||
Redox OS has some functionality not present yet, that will be implemented when
|
||||
Redox OS has some functionality not yet present that will be implemented when
|
||||
its orbital display server provides it.
|
||||
|
||||
@@ -6,6 +6,7 @@ disallowed-methods = [
|
||||
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
|
||||
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
|
||||
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
|
||||
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
|
||||
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
|
||||
|
||||
@@ -5,7 +5,7 @@ These images are used in the documentation of `winit`.
|
||||
## keyboard_*.svg
|
||||
|
||||
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
|
||||
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It is
|
||||
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.
|
||||
|
||||
@@ -17,7 +17,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
dpi::{LogicalPosition, LogicalSize, Position},
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
raw_window_handle::HasRawWindowHandle,
|
||||
raw_window_handle::HasWindowHandle,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
|
||||
@@ -26,14 +26,13 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
event_loop: &EventLoopWindowTarget<()>,
|
||||
windows: &mut HashMap<WindowId, Window>,
|
||||
) {
|
||||
let parent = parent.raw_window_handle().unwrap();
|
||||
let parent = parent.window_handle().unwrap();
|
||||
let mut builder = WindowBuilder::new()
|
||||
.with_title("child window")
|
||||
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
|
||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
||||
.with_visible(true);
|
||||
// `with_parent_window` is unsafe. Parent window must be a valid window.
|
||||
builder = unsafe { builder.with_parent_window(Some(parent)) };
|
||||
builder = builder.with_parent_window(Some(parent));
|
||||
let child_window = builder.build(event_loop).unwrap();
|
||||
|
||||
let id = child_window.id();
|
||||
|
||||
94
examples/custom_cursors.rs
Normal file
94
examples/custom_cursors.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
#![allow(clippy::single_match, clippy::disallowed_methods)]
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
keyboard::Key,
|
||||
window::{CustomCursor, WindowBuilder},
|
||||
};
|
||||
|
||||
fn decode_cursor<T>(bytes: &[u8], window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
|
||||
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
|
||||
let samples = img.into_flat_samples();
|
||||
let (_, w, h) = samples.extents();
|
||||
let (w, h) = (w as u16, h as u16);
|
||||
let builder = CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap();
|
||||
|
||||
builder.build(window_target)
|
||||
}
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
#[cfg(not(wasm_platform))]
|
||||
SimpleLogger::new()
|
||||
.with_level(log::LevelFilter::Info)
|
||||
.init()
|
||||
.unwrap();
|
||||
#[cfg(wasm_platform)]
|
||||
console_log::init_with_level(log::Level::Debug).unwrap();
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let builder = WindowBuilder::new().with_title("A fantastic window!");
|
||||
#[cfg(wasm_platform)]
|
||||
let builder = {
|
||||
use winit::platform::web::WindowBuilderExtWebSys;
|
||||
builder.with_append(true)
|
||||
};
|
||||
let window = builder.build(&event_loop).unwrap();
|
||||
|
||||
let mut cursor_idx = 0;
|
||||
let mut cursor_visible = true;
|
||||
|
||||
let custom_cursors = [
|
||||
decode_cursor(include_bytes!("data/cross.png"), &event_loop),
|
||||
decode_cursor(include_bytes!("data/cross2.png"), &event_loop),
|
||||
];
|
||||
|
||||
event_loop.run(move |event, _elwt| match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
state: ElementState::Pressed,
|
||||
logical_key: key,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key.as_ref() {
|
||||
Key::Character("1") => {
|
||||
log::debug!("Setting cursor to {:?}", cursor_idx);
|
||||
window.set_custom_cursor(&custom_cursors[cursor_idx]);
|
||||
cursor_idx = (cursor_idx + 1) % 2;
|
||||
}
|
||||
Key::Character("2") => {
|
||||
log::debug!("Setting cursor icon to default");
|
||||
window.set_cursor_icon(Default::default());
|
||||
}
|
||||
Key::Character("3") => {
|
||||
cursor_visible = !cursor_visible;
|
||||
log::debug!("Setting cursor visibility to {:?}", cursor_visible);
|
||||
window.set_cursor_visible(cursor_visible);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
#[cfg(not(wasm_platform))]
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
#[cfg(not(wasm_platform))]
|
||||
_elwt.exit();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
}
|
||||
BIN
examples/data/cross.png
Normal file
BIN
examples/data/cross.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 B |
BIN
examples/data/cross2.png
Normal file
BIN
examples/data/cross2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 B |
56
examples/focus.rs
Normal file
56
examples/focus.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
//! Example for focusing a window.
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
#[cfg(not(wasm_platform))]
|
||||
use std::time;
|
||||
#[cfg(wasm_platform)]
|
||||
use web_time as time;
|
||||
use winit::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut deadline = time::Instant::now() + time::Duration::from_secs(3);
|
||||
event_loop.run(move |event, elwt| {
|
||||
match event {
|
||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
||||
// Timeout reached; focus the window.
|
||||
println!("Re-focusing the window.");
|
||||
deadline += time::Duration::from_secs(3);
|
||||
window.focus_window();
|
||||
}
|
||||
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
// Notify the windowing system that we'll be presenting to the window.
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
elwt.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(deadline));
|
||||
})
|
||||
}
|
||||
@@ -53,6 +53,13 @@ pub(super) fn fill_window(window: &Window) {
|
||||
}
|
||||
|
||||
GC.with(|gc| {
|
||||
let size = window.inner_size();
|
||||
let (Some(width), Some(height)) =
|
||||
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface = gc
|
||||
@@ -61,13 +68,9 @@ pub(super) fn fill_window(window: &Window) {
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xFF181818;
|
||||
let size = window.inner_size();
|
||||
|
||||
surface
|
||||
.resize(
|
||||
NonZeroU32::new(size.width).expect("Width must be greater than zero"),
|
||||
NonZeroU32::new(size.height).expect("Height must be greater than zero"),
|
||||
)
|
||||
.resize(width, height)
|
||||
.expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface
|
||||
|
||||
278
src/cursor.rs
Normal file
278
src/cursor.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
use core::fmt;
|
||||
use std::hash::Hasher;
|
||||
use std::sync::Arc;
|
||||
use std::{error::Error, hash::Hash};
|
||||
|
||||
use crate::event_loop::EventLoopWindowTarget;
|
||||
use crate::platform_impl::{self, PlatformCustomCursor, PlatformCustomCursorBuilder};
|
||||
|
||||
/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
|
||||
pub const MAX_CURSOR_SIZE: u16 = 2048;
|
||||
|
||||
const PIXEL_SIZE: usize = 4;
|
||||
|
||||
/// Use a custom image as a cursor (mouse pointer).
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **Web**: Some browsers have limits on cursor sizes usually at 128x128.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use winit::{
|
||||
/// event::{Event, WindowEvent},
|
||||
/// event_loop::{ControlFlow, EventLoop},
|
||||
/// window::{CustomCursor, Window},
|
||||
/// };
|
||||
///
|
||||
/// let mut event_loop = EventLoop::new().unwrap();
|
||||
///
|
||||
/// let w = 10;
|
||||
/// let h = 10;
|
||||
/// let rgba = vec![255; (w * h * 4) as usize];
|
||||
///
|
||||
/// #[cfg(not(target_family = "wasm"))]
|
||||
/// let builder = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
|
||||
///
|
||||
/// #[cfg(target_family = "wasm")]
|
||||
/// let builder = {
|
||||
/// use winit::platform::web::CustomCursorExtWebSys;
|
||||
/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
|
||||
/// };
|
||||
///
|
||||
/// let custom_cursor = builder.build(&event_loop);
|
||||
///
|
||||
/// let window = Window::new(&event_loop).unwrap();
|
||||
/// window.set_custom_cursor(&custom_cursor);
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct CustomCursor {
|
||||
/// Platforms should make sure this is cheap to clone.
|
||||
pub(crate) inner: PlatformCustomCursor,
|
||||
}
|
||||
|
||||
impl CustomCursor {
|
||||
/// Creates a new cursor from an rgba buffer.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Setting cursor could be delayed due to the creation of `Blob` objects,
|
||||
/// which are async by nature.
|
||||
pub fn from_rgba(
|
||||
rgba: impl Into<Vec<u8>>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<CustomCursorBuilder, BadImage> {
|
||||
Ok(CustomCursorBuilder {
|
||||
inner: PlatformCustomCursorBuilder::from_rgba(
|
||||
rgba.into(),
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a [`CustomCursor`].
|
||||
///
|
||||
/// See [`CustomCursor`] for more details.
|
||||
#[derive(Debug)]
|
||||
pub struct CustomCursorBuilder {
|
||||
pub(crate) inner: PlatformCustomCursorBuilder,
|
||||
}
|
||||
|
||||
impl CustomCursorBuilder {
|
||||
pub fn build<T>(self, window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
|
||||
CustomCursor {
|
||||
inner: PlatformCustomCursor::build(self.inner, &window_target.p),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BadImage {
|
||||
/// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't
|
||||
/// guarantee that the cursor will work, but should avoid many platform and device specific
|
||||
/// limits.
|
||||
TooLarge { width: u16, height: u16 },
|
||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
||||
/// safely interpreted as 32bpp RGBA pixels.
|
||||
ByteCountNotDivisibleBy4 { byte_count: usize },
|
||||
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
|
||||
/// At least one of your arguments is incorrect.
|
||||
DimensionsVsPixelCount {
|
||||
width: u16,
|
||||
height: u16,
|
||||
width_x_height: u64,
|
||||
pixel_count: u64,
|
||||
},
|
||||
/// Produced when the hotspot is outside the image bounds
|
||||
HotspotOutOfBounds {
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for BadImage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BadImage::TooLarge { width, height } => write!(f,
|
||||
"The specified dimensions ({width:?}x{height:?}) are too large. The maximum is {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
|
||||
),
|
||||
BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
|
||||
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
|
||||
),
|
||||
BadImage::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height,
|
||||
pixel_count,
|
||||
} => write!(f,
|
||||
"The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.",
|
||||
),
|
||||
BadImage::HotspotOutOfBounds {
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
} => write!(f,
|
||||
"The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds ({width:?}x{height:?}).",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadImage {}
|
||||
|
||||
/// Platforms export this directly as `PlatformCustomCursorBuilder` if they need to only work with images.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OnlyCursorImageBuilder(pub(crate) CursorImage);
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl OnlyCursorImageBuilder {
|
||||
pub(crate) fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching.
|
||||
#[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 {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl OnlyCursorImage {
|
||||
fn build<T>(
|
||||
builder: OnlyCursorImageBuilder,
|
||||
_: &platform_impl::EventLoopWindowTarget<T>,
|
||||
) -> Self {
|
||||
Self(Arc::new(builder.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct CursorImage {
|
||||
pub(crate) rgba: Vec<u8>,
|
||||
pub(crate) width: u16,
|
||||
pub(crate) height: u16,
|
||||
pub(crate) hotspot_x: u16,
|
||||
pub(crate) hotspot_y: u16,
|
||||
}
|
||||
|
||||
impl CursorImage {
|
||||
pub(crate) fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
|
||||
return Err(BadImage::TooLarge { width, height });
|
||||
}
|
||||
|
||||
if rgba.len() % PIXEL_SIZE != 0 {
|
||||
return Err(BadImage::ByteCountNotDivisibleBy4 {
|
||||
byte_count: rgba.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
|
||||
let width_x_height = width as u64 * height as u64;
|
||||
if pixel_count != width_x_height {
|
||||
return Err(BadImage::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height,
|
||||
pixel_count,
|
||||
});
|
||||
}
|
||||
|
||||
if hotspot_x >= width || hotspot_y >= height {
|
||||
return Err(BadImage::HotspotOutOfBounds {
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(CursorImage {
|
||||
rgba,
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct NoCustomCursor;
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl NoCustomCursor {
|
||||
pub(crate) fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn build<T>(self, _: &platform_impl::EventLoopWindowTarget<T>) -> NoCustomCursor {
|
||||
self
|
||||
}
|
||||
}
|
||||
39
src/dpi.rs
39
src/dpi.rs
@@ -4,13 +4,13 @@
|
||||
//!
|
||||
//! Modern computer screens don't have a consistent relationship between resolution and size.
|
||||
//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
|
||||
//! normally being less than a quarter the size of their desktop counterparts. What's more, neither
|
||||
//! desktop nor mobile screens are consistent resolutions within their own size classes - common
|
||||
//! typically being less than a quarter the size of their desktop counterparts. Moreover, neither
|
||||
//! desktop nor mobile screens have consistent resolutions within their own size classes - common
|
||||
//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
|
||||
//! and beyond.
|
||||
//!
|
||||
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
|
||||
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen,
|
||||
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen and
|
||||
//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
|
||||
//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
|
||||
//! problematic with text rendering, where quarter-sized text becomes a significant legibility
|
||||
@@ -25,12 +25,12 @@
|
||||
//!
|
||||
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is
|
||||
//! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
|
||||
//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device
|
||||
//! for example, a button that's usually 50 pixels across would be 100 pixels across on a device
|
||||
//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
|
||||
//!
|
||||
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
|
||||
//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's
|
||||
//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather
|
||||
//! usually a mistake since there's no consistent mapping between the scale factor and the screen's
|
||||
//! actual DPI. Unless printing to a physical medium, you should work in scaled pixels rather
|
||||
//! than any DPI-dependent units.
|
||||
//!
|
||||
//! ### Position and Size types
|
||||
@@ -42,11 +42,11 @@
|
||||
//! coordinates as input, allowing you to use the most convenient coordinate system for your
|
||||
//! particular application.
|
||||
//!
|
||||
//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the
|
||||
//! Winit's position and size types are generic over their exact pixel type, `P`, to allow the
|
||||
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
|
||||
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
|
||||
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
|
||||
//! will truncate the fractional part of the float, rather than properly round to the nearest
|
||||
//! will truncate the fractional part of the float rather than properly round to the nearest
|
||||
//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the
|
||||
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
|
||||
//! int, although rounding lessens the problem.
|
||||
@@ -55,34 +55,35 @@
|
||||
//!
|
||||
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
|
||||
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
|
||||
//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your
|
||||
//! application's UI elements and adjust how the platform changes the window's size to reflect the new
|
||||
//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor
|
||||
//! monitor or if the user changes their DPI settings. This allows you to rescale your application's
|
||||
//! UI elements and adjust how the platform changes the window's size to reflect the new scale
|
||||
//! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor
|
||||
//! can be found by calling [`window.scale_factor()`].
|
||||
//!
|
||||
//! ## How is the scale factor calculated?
|
||||
//!
|
||||
//! Scale factor is calculated differently on different platforms:
|
||||
//! The scale factor is calculated differently on different platforms:
|
||||
//!
|
||||
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
|
||||
//! display settings. While users are free to select any option they want, they're only given a
|
||||
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
|
||||
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7. The scale factor is
|
||||
//! global and changing it requires logging out. See [this article][windows_1] for technical
|
||||
//! details.
|
||||
//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
|
||||
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
|
||||
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
|
||||
//! the specific value varies across devices.
|
||||
//! - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific
|
||||
//! displays. When available, the user may pick a per-monitor scaling factor from a set of
|
||||
//! pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default,
|
||||
//! but the specific value varies across devices.
|
||||
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
|
||||
//! currently uses a three-pronged approach:
|
||||
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
|
||||
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable if present.
|
||||
//! + If not present, use the value set in `Xft.dpi` in Xresources.
|
||||
//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
|
||||
//!
|
||||
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
|
||||
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
|
||||
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
|
||||
//! - **Wayland:** Scale factor is suggested by the the compositor.
|
||||
//! - **Wayland:** The scale factor is suggested by the compositor for each window individually. The
|
||||
//! monitor scale factor may differ from the window scale factor.
|
||||
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
|
||||
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
|
||||
//! information.
|
||||
|
||||
@@ -224,14 +224,22 @@ impl<T> EventLoop<T> {
|
||||
/// (that Rust doesn't see) that will also mean that the rest of the function is never executed
|
||||
/// and any values not passed to this function will *not* be dropped.
|
||||
///
|
||||
/// Web applications are recommended to use `spawn()` instead of `run()` to avoid the need
|
||||
/// Web applications are recommended to use
|
||||
#[cfg_attr(
|
||||
wasm_platform,
|
||||
doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]"
|
||||
)]
|
||||
#[cfg_attr(not(wasm_platform), doc = "`EventLoopExtWebSys::spawn()`")]
|
||||
/// [^1] instead of [`run()`] to avoid the need
|
||||
/// for the Javascript exception trick, and to make it clearer that the event loop runs
|
||||
/// asynchronously (via the browser's own, internal, event loop) and doesn't block the
|
||||
/// current thread of execution like it does on other platforms.
|
||||
///
|
||||
/// This function won't be available with `target_feature = "exception-handling"`.
|
||||
///
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
|
||||
/// [`run()`]: Self::run()
|
||||
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on WASM.
|
||||
#[inline]
|
||||
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
|
||||
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
|
||||
|
||||
@@ -49,11 +49,7 @@ impl fmt::Display for BadIcon {
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadIcon {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl Error for BadIcon {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct RgbaIcon {
|
||||
|
||||
67
src/lib.rs
67
src/lib.rs
@@ -10,12 +10,12 @@
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Once this is done there are two ways to create a [`Window`]:
|
||||
//! Once this is done, there are two ways to create a [`Window`]:
|
||||
//!
|
||||
//! - Calling [`Window::new(&event_loop)`][window_new].
|
||||
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
|
||||
//!
|
||||
//! The first method is the simplest, and will give you default values for everything. The second
|
||||
//! The first method is the simplest and will give you default values for everything. The second
|
||||
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
|
||||
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
|
||||
//!
|
||||
@@ -26,17 +26,37 @@
|
||||
//! window or a key getting pressed while the window is focused. Devices can generate
|
||||
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
|
||||
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
|
||||
//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired.
|
||||
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if desired.
|
||||
//!
|
||||
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
|
||||
//! You can retrieve events by calling [`EventLoop::run()`]. This function will
|
||||
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
|
||||
//! will run until [`exit()`] is used, at which point [`Event`]`::`[`LoopExiting`].
|
||||
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
|
||||
//!
|
||||
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
||||
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
|
||||
//! most other platforms. However, this model can be re-implemented to an extent with
|
||||
//! [`EventLoopExtPumpEvents::pump_events`]. See that method's documentation for more reasons about why
|
||||
//! it's discouraged, beyond compatibility reasons.
|
||||
#![cfg_attr(
|
||||
any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform
|
||||
),
|
||||
doc = "[`EventLoopExtPumpEvents::pump_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_events()]"
|
||||
)]
|
||||
#![cfg_attr(
|
||||
not(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform
|
||||
)),
|
||||
doc = "`EventLoopExtPumpEvents::pump_events()`"
|
||||
)]
|
||||
//! [^1]. See that method's documentation for more reasons about why
|
||||
//! it's discouraged beyond compatibility reasons.
|
||||
//!
|
||||
//!
|
||||
//! ```no_run
|
||||
@@ -72,9 +92,9 @@
|
||||
//!
|
||||
//! // Queue a RedrawRequested event.
|
||||
//! //
|
||||
//! // You only need to call this if you've determined that you need to redraw, in
|
||||
//! // You only need to call this if you've determined that you need to redraw in
|
||||
//! // applications which do not always need to. Applications that redraw continuously
|
||||
//! // can just render here instead.
|
||||
//! // can render here instead.
|
||||
//! window.request_redraw();
|
||||
//! },
|
||||
//! Event::WindowEvent {
|
||||
@@ -92,16 +112,16 @@
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
|
||||
//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`]
|
||||
//! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
|
||||
//! compared to the value returned by [`Window::id()`] to determine which [`Window`]
|
||||
//! dispatched the event.
|
||||
//!
|
||||
//! # Drawing on the window
|
||||
//!
|
||||
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
|
||||
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you to
|
||||
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the
|
||||
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
|
||||
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
||||
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
||||
//!
|
||||
//! Note that many platforms will display garbage data in the window's client area if the
|
||||
//! application doesn't render anything to the window by the time the desktop compositor is ready to
|
||||
@@ -110,9 +130,8 @@
|
||||
//! window visible only once you're ready to render into it.
|
||||
//!
|
||||
//! [`EventLoop`]: event_loop::EventLoop
|
||||
//! [`EventLoopExtPumpEvents::pump_events`]: ./platform/pump_events/trait.EventLoopExtPumpEvents.html#tymethod.pump_events
|
||||
//! [`EventLoop::new()`]: event_loop::EventLoop::new
|
||||
//! [event_loop_run]: event_loop::EventLoop::run
|
||||
//! [`EventLoop::run()`]: event_loop::EventLoop::run
|
||||
//! [`exit()`]: event_loop::EventLoopWindowTarget::exit
|
||||
//! [`Window`]: window::Window
|
||||
//! [`WindowId`]: window::WindowId
|
||||
@@ -120,15 +139,14 @@
|
||||
//! [window_new]: window::Window::new
|
||||
//! [window_builder_new]: window::WindowBuilder::new
|
||||
//! [window_builder_build]: window::WindowBuilder::build
|
||||
//! [window_id_fn]: window::Window::id
|
||||
//! [`Event`]: event::Event
|
||||
//! [`Window::id()`]: window::Window::id
|
||||
//! [`WindowEvent`]: event::WindowEvent
|
||||
//! [`DeviceEvent`]: event::DeviceEvent
|
||||
//! [`UserEvent`]: event::Event::UserEvent
|
||||
//! [`LoopExiting`]: event::Event::LoopExiting
|
||||
//! [`platform`]: platform
|
||||
//! [`Event::UserEvent`]: event::Event::UserEvent
|
||||
//! [`Event::LoopExiting`]: event::Event::LoopExiting
|
||||
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
|
||||
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
|
||||
//! [^1]: `EventLoopExtPumpEvents::pump_events()` is only available on Windows, macOS, Android, X11 and Wayland.
|
||||
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
@@ -154,6 +172,7 @@ extern crate bitflags;
|
||||
pub mod dpi;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
mod cursor;
|
||||
pub mod event;
|
||||
pub mod event_loop;
|
||||
mod icon;
|
||||
@@ -165,13 +184,13 @@ pub mod window;
|
||||
pub mod platform;
|
||||
|
||||
/// Wrapper for objects which winit will access on the main thread so they are effectively `Send`
|
||||
/// and `Sync`, since they always excute on a single thread.
|
||||
/// and `Sync`, since they always execute on a single thread.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Winit can run only one event loop at the time and the event loop itself is tied to some thread.
|
||||
/// The objects could be send across the threads, but once passed to winit, they execute on the
|
||||
/// mean thread if the platform demands it. Thus marking such objects as `Send + Sync` is safe.
|
||||
/// Winit can run only one event loop at a time, and the event loop itself is tied to some thread.
|
||||
/// The objects could be sent across the threads, but once passed to winit, they execute on the
|
||||
/// main thread if the platform demands it. Thus, marking such objects as `Send + Sync` is safe.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct SendSyncWrapper<T>(pub(crate) T);
|
||||
|
||||
@@ -138,14 +138,18 @@ impl MonitorHandle {
|
||||
self.inner.refresh_rate_millihertz()
|
||||
}
|
||||
|
||||
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
|
||||
/// pixels and vice versa, use [`Window::scale_factor`].
|
||||
///
|
||||
/// See the [`dpi`](crate::dpi) module for more information.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
|
||||
/// - **Wayland:** May differ from [`Window::scale_factor`].
|
||||
/// - **Android:** Always returns 1.0.
|
||||
///
|
||||
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.inner.scale_factor()
|
||||
|
||||
@@ -84,5 +84,10 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
|
||||
/// use winit::platform::android::activity::AndroidApp;
|
||||
/// ```
|
||||
pub mod activity {
|
||||
// We enable the `"native-activity"` feature just so that we can build the
|
||||
// docs, but it'll be very confusing for users to see the docs with that
|
||||
// feature enabled, so we avoid inlining it so that they're forced to view
|
||||
// it on the crate's own docs.rs page.
|
||||
#[doc(no_inline)]
|
||||
pub use android_activity::*;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use icrate::Foundation::MainThreadMarker;
|
||||
use objc2::rc::Id;
|
||||
|
||||
use crate::{
|
||||
@@ -365,7 +366,9 @@ impl MonitorHandleExtMacOS for MonitorHandle {
|
||||
}
|
||||
|
||||
fn ns_screen(&self) -> Option<*mut c_void> {
|
||||
self.inner.ns_screen().map(|s| Id::as_ptr(&s) as _)
|
||||
// SAFETY: We only use the marker to get a pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
self.inner.ns_screen(mtm).map(|s| Id::as_ptr(&s) as _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -155,19 +155,19 @@ pub trait EventLoopExtPumpEvents {
|
||||
/// - **Windows**: The implementation will use `PeekMessage` when checking for
|
||||
/// window messages to avoid blocking your external event loop.
|
||||
///
|
||||
/// - **MacOS**: The implementation works in terms of stopping the global `NSApp`
|
||||
/// - **MacOS**: The implementation works in terms of stopping the global application
|
||||
/// whenever the application `RunLoop` indicates that it is preparing to block
|
||||
/// and wait for new events.
|
||||
///
|
||||
/// This is very different to the polling APIs that are available on other
|
||||
/// platforms (the lower level polling primitives on MacOS are private
|
||||
/// implementation details for `NSApp` which aren't accessible to application
|
||||
/// developers)
|
||||
/// implementation details for `NSApplication` which aren't accessible to
|
||||
/// application developers)
|
||||
///
|
||||
/// It's likely this will be less efficient than polling on other OSs and
|
||||
/// it also means the `NSApp` is stopped while outside of the Winit
|
||||
/// it also means the `NSApplication` is stopped while outside of the Winit
|
||||
/// event loop - and that's observable (for example to crates like `rfd`)
|
||||
/// because the `NSApp` is global state.
|
||||
/// because the `NSApplication` is global state.
|
||||
///
|
||||
/// If you render outside of Winit you are likely to see window resizing artifacts
|
||||
/// since MacOS expects applications to render synchronously during any `drawRect`
|
||||
|
||||
@@ -35,9 +35,9 @@ pub trait EventLoopExtRunOnDemand {
|
||||
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
|
||||
///
|
||||
/// # Caveats
|
||||
/// - This extension isn't available on all platforms, since it's not always possible to
|
||||
/// return to the caller (specifically this is impossible on iOS and Web - though with
|
||||
/// the Web backend it is possible to use `spawn()` more than once instead).
|
||||
/// - This extension isn't available on all platforms, since it's not always possible to return
|
||||
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
|
||||
/// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead).
|
||||
/// - No [`Window`] state can be carried between separate runs of the event loop.
|
||||
///
|
||||
/// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you specifically need
|
||||
@@ -57,8 +57,13 @@ pub trait EventLoopExtRunOnDemand {
|
||||
/// on an event loop that is internal to the browser itself.
|
||||
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS.
|
||||
///
|
||||
/// [`exit()`]: EventLoopWindowTarget::exit
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
|
||||
#[cfg_attr(
|
||||
not(wasm_platform),
|
||||
doc = "[^1]: `spawn()` is only available on `wasm` platforms."
|
||||
)]
|
||||
///
|
||||
/// [`exit()`]: EventLoopWindowTarget::exit()
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
|
||||
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
|
||||
|
||||
@@ -27,9 +27,12 @@
|
||||
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
|
||||
use crate::cursor::CustomCursorBuilder;
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::event_loop::EventLoopWindowTarget;
|
||||
use crate::platform_impl::PlatformCustomCursorBuilder;
|
||||
use crate::window::CustomCursor;
|
||||
use crate::window::{Window, WindowBuilder};
|
||||
use crate::SendSyncWrapper;
|
||||
|
||||
@@ -109,13 +112,28 @@ pub trait EventLoopExtWebSys {
|
||||
|
||||
/// Initializes the winit event loop.
|
||||
///
|
||||
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
|
||||
/// satisfy its `!` return type.
|
||||
/// Unlike
|
||||
#[cfg_attr(
|
||||
all(wasm_platform, target_feature = "exception-handling"),
|
||||
doc = "`run()`"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(all(wasm_platform, target_feature = "exception-handling")),
|
||||
doc = "[`run()`]"
|
||||
)]
|
||||
/// [^1], this returns immediately, and doesn't throw an exception in order to
|
||||
/// satisfy its [`!`] return type.
|
||||
///
|
||||
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop
|
||||
/// by calling this function again. This can be useful if you want to recreate the event loop
|
||||
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
|
||||
/// event loop when switching between tabs on a single page application.
|
||||
///
|
||||
#[cfg_attr(
|
||||
not(all(wasm_platform, target_feature = "exception-handling")),
|
||||
doc = "[`run()`]: EventLoop::run()"
|
||||
)]
|
||||
/// [^1]: `run()` is _not_ available on WASM when the target supports `exception-handling`.
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
|
||||
@@ -185,3 +203,24 @@ pub enum PollStrategy {
|
||||
#[default]
|
||||
Scheduler,
|
||||
}
|
||||
|
||||
pub trait CustomCursorExtWebSys {
|
||||
/// 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) -> CustomCursorBuilder;
|
||||
}
|
||||
|
||||
impl CustomCursorExtWebSys for CustomCursor {
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder {
|
||||
CustomCursorBuilder {
|
||||
inner: PlatformCustomCursorBuilder::Url {
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,6 +751,18 @@ impl DeviceId {
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OwnedWindowHandle {}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
// Parent windows are currently unsupported, though owned window
|
||||
// handles would be implementable.
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Window {
|
||||
app: AndroidApp,
|
||||
redraw_requester: RedrawRequester,
|
||||
@@ -906,6 +918,8 @@ impl Window {
|
||||
|
||||
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
|
||||
|
||||
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {}
|
||||
|
||||
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
@@ -1031,6 +1045,8 @@ impl Display for OsError {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
|
||||
@@ -200,6 +200,10 @@ impl AppState {
|
||||
)
|
||||
}
|
||||
|
||||
fn has_terminated(&self) -> bool {
|
||||
matches!(self.state(), AppStateImpl::Terminated)
|
||||
}
|
||||
|
||||
fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
|
||||
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
|
||||
AppStateImpl::NotLaunched {
|
||||
@@ -243,7 +247,7 @@ impl AppState {
|
||||
fn wakeup_transition(&mut self) -> Option<EventWrapper> {
|
||||
// before `AppState::did_finish_launching` is called, pretend there is no running
|
||||
// event loop.
|
||||
if !self.has_launched() {
|
||||
if !self.has_launched() || self.has_terminated() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -390,7 +394,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
fn events_cleared_transition(&mut self) {
|
||||
if !self.has_launched() {
|
||||
if !self.has_launched() || self.has_terminated() {
|
||||
return;
|
||||
}
|
||||
let (waiting_event_handler, old) = match self.take_state() {
|
||||
@@ -586,6 +590,10 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
|
||||
events: I,
|
||||
) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
if this.has_terminated() {
|
||||
return;
|
||||
}
|
||||
|
||||
let (mut event_handler, active_control_flow, processing_redraws) =
|
||||
match this.try_user_callback_transition() {
|
||||
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
|
||||
@@ -737,7 +745,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
|
||||
|
||||
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
if !this.has_launched() {
|
||||
if !this.has_launched() || this.has_terminated() {
|
||||
return;
|
||||
}
|
||||
match this.state_mut() {
|
||||
|
||||
@@ -289,7 +289,7 @@ fn setup_control_flow_observers() {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
|
||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||
kCFRunLoopExit => {} // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -304,7 +304,7 @@ fn setup_control_flow_observers() {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
|
||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||
kCFRunLoopExit => {} // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +73,12 @@ pub(crate) use self::{
|
||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
|
||||
},
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||
window::{OwnedWindowHandle, PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||
};
|
||||
|
||||
use self::uikit::UIScreen;
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::{
|
||||
use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger};
|
||||
use objc2::mutability::IsRetainable;
|
||||
use objc2::rc::Id;
|
||||
use objc2::Message;
|
||||
|
||||
use super::uikit::{UIScreen, UIScreenMode};
|
||||
use crate::{
|
||||
@@ -20,16 +21,15 @@ use crate::{
|
||||
#[derive(Debug)]
|
||||
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
|
||||
|
||||
impl<T: IsRetainable> Clone for MainThreadBoundDelegateImpls<T> {
|
||||
impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)),
|
||||
)
|
||||
Self(MainThreadMarker::run_on_main(|mtm| {
|
||||
MainThreadBound::new(Id::clone(self.0.get(mtm)), mtm)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
// SAFETY: Marker only used to get the pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
@@ -37,7 +37,7 @@ impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// SAFETY: Marker only used to get the pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
@@ -45,7 +45,7 @@ impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable> Eq for MainThreadBoundDelegateImpls<T> {}
|
||||
impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub struct VideoMode {
|
||||
@@ -100,11 +100,9 @@ pub struct MonitorHandle {
|
||||
|
||||
impl Clone for MonitorHandle {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
ui_screen: self
|
||||
.ui_screen
|
||||
.get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)),
|
||||
}
|
||||
MainThreadMarker::run_on_main(|mtm| Self {
|
||||
ui_screen: MainThreadBound::new(self.ui_screen.get(mtm).clone(), mtm),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,16 +166,16 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<String> {
|
||||
self.ui_screen.get_on_main(|ui_screen, mtm| {
|
||||
MainThreadMarker::run_on_main(|mtm| {
|
||||
let main = UIScreen::main(mtm);
|
||||
if *ui_screen == main {
|
||||
if *self.ui_screen(mtm) == main {
|
||||
Some("Primary".to_string())
|
||||
} else if *ui_screen == main.mirroredScreen() {
|
||||
} else if *self.ui_screen(mtm) == main.mirroredScreen() {
|
||||
Some("Mirrored".to_string())
|
||||
} else {
|
||||
UIScreen::screens(mtm)
|
||||
.iter()
|
||||
.position(|rhs| rhs == &**ui_screen)
|
||||
.position(|rhs| rhs == &**self.ui_screen(mtm))
|
||||
.map(|idx| idx.to_string())
|
||||
}
|
||||
})
|
||||
@@ -186,31 +184,32 @@ impl MonitorHandle {
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
let bounds = self
|
||||
.ui_screen
|
||||
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
|
||||
.get_on_main(|ui_screen| ui_screen.nativeBounds());
|
||||
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
|
||||
}
|
||||
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
let bounds = self
|
||||
.ui_screen
|
||||
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
|
||||
.get_on_main(|ui_screen| ui_screen.nativeBounds());
|
||||
(bounds.origin.x as f64, bounds.origin.y as f64).into()
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.ui_screen
|
||||
.get_on_main(|ui_screen, _| ui_screen.nativeScale()) as f64
|
||||
.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
Some(
|
||||
self.ui_screen
|
||||
.get_on_main(|ui_screen, _| refresh_rate_millihertz(ui_screen)),
|
||||
.get_on_main(|ui_screen| refresh_rate_millihertz(ui_screen)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.ui_screen.get_on_main(|ui_screen, mtm| {
|
||||
MainThreadMarker::run_on_main(|mtm| {
|
||||
let ui_screen = self.ui_screen(mtm);
|
||||
// Use Ord impl of RootVideoMode
|
||||
|
||||
let modes: BTreeSet<_> = ui_screen
|
||||
@@ -230,8 +229,12 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
pub fn preferred_video_mode(&self) -> VideoMode {
|
||||
self.ui_screen.get_on_main(|ui_screen, mtm| {
|
||||
VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm)
|
||||
MainThreadMarker::run_on_main(|mtm| {
|
||||
VideoMode::new(
|
||||
self.ui_screen(mtm).clone(),
|
||||
self.ui_screen(mtm).preferredMode().unwrap(),
|
||||
mtm,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::cell::Cell;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet};
|
||||
use objc2::declare::{Ivar, IvarDrop};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyClass;
|
||||
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
|
||||
use objc2::{
|
||||
declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType, DeclaredClass,
|
||||
};
|
||||
|
||||
use super::app_state::{self, EventWrapper};
|
||||
use super::uikit::{
|
||||
@@ -37,6 +37,8 @@ declare_class!(
|
||||
const NAME: &'static str = "WinitUIView";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitView {}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(drawRect:)]
|
||||
fn draw_rect(&self, rect: CGRect) {
|
||||
@@ -274,11 +276,7 @@ pub struct ViewControllerState {
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
pub(crate) struct WinitViewController {
|
||||
state: IvarDrop<Box<ViewControllerState>, "_state">,
|
||||
}
|
||||
|
||||
mod view_controller_ivars;
|
||||
pub(crate) struct WinitViewController;
|
||||
|
||||
unsafe impl ClassType for WinitViewController {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
@@ -287,28 +285,8 @@ declare_class!(
|
||||
const NAME: &'static str = "WinitUIViewController";
|
||||
}
|
||||
|
||||
unsafe impl WinitViewController {
|
||||
#[method(init)]
|
||||
unsafe fn init(this: *mut Self) -> Option<NonNull<Self>> {
|
||||
let this: Option<&mut Self> = msg_send![super(this), init];
|
||||
this.map(|this| {
|
||||
// These are set in WinitViewController::new, it's just to set them
|
||||
// to _something_.
|
||||
Ivar::write(
|
||||
&mut this.state,
|
||||
Box::new(ViewControllerState {
|
||||
prefers_status_bar_hidden: Cell::new(false),
|
||||
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
|
||||
prefers_home_indicator_auto_hidden: Cell::new(false),
|
||||
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
|
||||
preferred_screen_edges_deferring_system_gestures: Cell::new(
|
||||
UIRectEdge::NONE,
|
||||
),
|
||||
}),
|
||||
);
|
||||
NonNull::from(this)
|
||||
})
|
||||
}
|
||||
impl DeclaredClass for WinitViewController {
|
||||
type Ivars = ViewControllerState;
|
||||
}
|
||||
|
||||
unsafe impl WinitViewController {
|
||||
@@ -319,27 +297,27 @@ declare_class!(
|
||||
|
||||
#[method(prefersStatusBarHidden)]
|
||||
fn prefers_status_bar_hidden(&self) -> bool {
|
||||
self.state.prefers_status_bar_hidden.get()
|
||||
self.ivars().prefers_status_bar_hidden.get()
|
||||
}
|
||||
|
||||
#[method(preferredStatusBarStyle)]
|
||||
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
|
||||
self.state.preferred_status_bar_style.get()
|
||||
self.ivars().preferred_status_bar_style.get()
|
||||
}
|
||||
|
||||
#[method(prefersHomeIndicatorAutoHidden)]
|
||||
fn prefers_home_indicator_auto_hidden(&self) -> bool {
|
||||
self.state.prefers_home_indicator_auto_hidden.get()
|
||||
self.ivars().prefers_home_indicator_auto_hidden.get()
|
||||
}
|
||||
|
||||
#[method(supportedInterfaceOrientations)]
|
||||
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
|
||||
self.state.supported_orientations.get()
|
||||
self.ivars().supported_orientations.get()
|
||||
}
|
||||
|
||||
#[method(preferredScreenEdgesDeferringSystemGestures)]
|
||||
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
|
||||
self.state
|
||||
self.ivars()
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.get()
|
||||
}
|
||||
@@ -348,17 +326,17 @@ declare_class!(
|
||||
|
||||
impl WinitViewController {
|
||||
pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) {
|
||||
self.state.prefers_status_bar_hidden.set(val);
|
||||
self.ivars().prefers_status_bar_hidden.set(val);
|
||||
self.setNeedsStatusBarAppearanceUpdate();
|
||||
}
|
||||
|
||||
pub(crate) fn set_preferred_status_bar_style(&self, val: UIStatusBarStyle) {
|
||||
self.state.preferred_status_bar_style.set(val);
|
||||
self.ivars().preferred_status_bar_style.set(val);
|
||||
self.setNeedsStatusBarAppearanceUpdate();
|
||||
}
|
||||
|
||||
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
|
||||
self.state.prefers_home_indicator_auto_hidden.set(val);
|
||||
self.ivars().prefers_home_indicator_auto_hidden.set(val);
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.home_indicator_hidden {
|
||||
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
|
||||
@@ -368,7 +346,7 @@ impl WinitViewController {
|
||||
}
|
||||
|
||||
pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) {
|
||||
self.state
|
||||
self.ivars()
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.set(val);
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
@@ -401,7 +379,7 @@ impl WinitViewController {
|
||||
| UIInterfaceOrientationMask::PortraitUpsideDown
|
||||
}
|
||||
};
|
||||
self.state.supported_orientations.set(mask);
|
||||
self.ivars().supported_orientations.set(mask);
|
||||
UIViewController::attemptRotationToDeviceOrientation();
|
||||
}
|
||||
|
||||
@@ -411,7 +389,15 @@ impl WinitViewController {
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
view: &UIView,
|
||||
) -> Id<Self> {
|
||||
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), init] };
|
||||
// These are set properly below, we just to set them to something in the meantime.
|
||||
let this = Self::alloc().set_ivars(ViewControllerState {
|
||||
prefers_status_bar_hidden: Cell::new(false),
|
||||
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
|
||||
prefers_home_indicator_auto_hidden: Cell::new(false),
|
||||
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
|
||||
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::NONE),
|
||||
});
|
||||
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
|
||||
this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
|
||||
|
||||
@@ -446,6 +432,8 @@ declare_class!(
|
||||
const NAME: &'static str = "WinitUIWindow";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitUIWindow {}
|
||||
|
||||
unsafe impl WinitUIWindow {
|
||||
#[method(becomeKeyWindow)]
|
||||
fn become_key_window(&self) {
|
||||
@@ -518,6 +506,8 @@ declare_class!(
|
||||
const NAME: &'static str = "WinitApplicationDelegate";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitApplicationDelegate {}
|
||||
|
||||
// UIApplicationDelegate protocol
|
||||
unsafe impl WinitApplicationDelegate {
|
||||
#[method(application:didFinishLaunchingWithOptions:)]
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
icon::Icon,
|
||||
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
|
||||
platform_impl::platform::{
|
||||
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
|
||||
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, PlatformCustomCursor,
|
||||
},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
@@ -25,6 +25,19 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OwnedWindowHandle {}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
// Parent windows are currently unsupported, though owned window
|
||||
// handles would be implementable (would work similar to macOS).
|
||||
warn!("parent windows are unsupported on iOS");
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Inner {
|
||||
window: Id<WinitUIWindow>,
|
||||
view_controller: Id<WinitViewController>,
|
||||
@@ -177,6 +190,10 @@ impl Inner {
|
||||
debug!("`Window::set_cursor_icon` ignored on iOS")
|
||||
}
|
||||
|
||||
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {
|
||||
debug!("`Window::set_custom_cursor` ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
@@ -355,14 +372,14 @@ impl Inner {
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
|
||||
let mut window_handle = rwh_06::UiKitWindowHandle::new({
|
||||
let ui_view = Id::as_ptr(&self.view) as _;
|
||||
std::ptr::NonNull::new(ui_view).expect("Id<T> should never be null")
|
||||
});
|
||||
window_handle.ui_view_controller =
|
||||
std::ptr::NonNull::new(Id::as_ptr(&self.view_controller) as _);
|
||||
Ok(rwh_06::RawWindowHandle::UiKit(window_handle))
|
||||
rwh_06::RawWindowHandle::UiKit(window_handle)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
@@ -516,7 +533,19 @@ impl Window {
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
|
||||
self.inner.get_on_main(|inner, _mtm| f(inner))
|
||||
self.inner.get_on_main(|inner| f(inner))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub(crate) fn raw_window_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
if let Some(mtm) = MainThreadMarker::new() {
|
||||
Ok(self.inner.get(mtm).raw_window_handle_rwh_06())
|
||||
} else {
|
||||
Err(rwh_06::HandleError::Unavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ pub use x11::XNotSupported;
|
||||
#[cfg(x11_platform)]
|
||||
use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError};
|
||||
|
||||
pub(crate) use crate::cursor::OnlyCursorImage as PlatformCustomCursor;
|
||||
pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder;
|
||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
@@ -139,6 +141,43 @@ impl fmt::Display for OsError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum OwnedWindowHandle {
|
||||
#[cfg(x11_platform)]
|
||||
X(x11rb::protocol::xproto::Window),
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland,
|
||||
}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
// TODO: Do we need to do something extra to extend the lifetime of
|
||||
// the window lives beyond the passed-in handle?
|
||||
match handle.as_raw() {
|
||||
#[cfg(x11_platform)]
|
||||
rwh_06::RawWindowHandle::Xlib(handle) => {
|
||||
Self::X(handle.window as x11rb::protocol::xproto::Window)
|
||||
}
|
||||
#[cfg(x11_platform)]
|
||||
rwh_06::RawWindowHandle::Xcb(handle) => Self::X(handle.window.get()),
|
||||
#[cfg(wayland_platform)]
|
||||
rwh_06::RawWindowHandle::Wayland(_handle) => {
|
||||
// Wayland does not currently support parent windows, but it
|
||||
// could support owned handles.
|
||||
Self::Wayland
|
||||
}
|
||||
#[cfg(not(x11_platform))]
|
||||
handle => panic!("invalid window handle {handle:?} on Wayland"),
|
||||
#[cfg(not(wayland_platform))]
|
||||
handle => panic!("invalid window handle {handle:?} on X11"),
|
||||
#[cfg(all(x11_platform, wayland_platform))]
|
||||
handle => panic!("invalid window handle {handle:?} on X11 or Wayland"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Window {
|
||||
#[cfg(x11_platform)]
|
||||
X(x11::Window),
|
||||
@@ -424,6 +463,11 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_custom_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))
|
||||
|
||||
@@ -393,7 +393,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
window.resize(new_logical_size);
|
||||
window.request_inner_size(new_logical_size.into());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ use sctk::seat::SeatState;
|
||||
use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler};
|
||||
use sctk::shell::xdg::XdgShell;
|
||||
use sctk::shell::WaylandSurface;
|
||||
use sctk::shm::slot::SlotPool;
|
||||
use sctk::shm::{Shm, ShmHandler};
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
|
||||
@@ -50,7 +51,7 @@ pub struct WinitState {
|
||||
pub compositor_state: Arc<CompositorState>,
|
||||
|
||||
/// The state of the subcompositor.
|
||||
pub subcompositor_state: Arc<SubcompositorState>,
|
||||
pub subcompositor_state: Option<Arc<SubcompositorState>>,
|
||||
|
||||
/// The seat state responsible for all sorts of input.
|
||||
pub seat_state: SeatState,
|
||||
@@ -58,6 +59,9 @@ pub struct WinitState {
|
||||
/// The shm for software buffers, such as cursors.
|
||||
pub shm: Shm,
|
||||
|
||||
/// The pool where custom cursors are allocated.
|
||||
pub custom_cursor_pool: Arc<Mutex<SlotPool>>,
|
||||
|
||||
/// The XDG shell that is used for widnows.
|
||||
pub xdg_shell: XdgShell,
|
||||
|
||||
@@ -124,12 +128,17 @@ impl WinitState {
|
||||
let registry_state = RegistryState::new(globals);
|
||||
let compositor_state =
|
||||
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
|
||||
let subcompositor_state = SubcompositorState::bind(
|
||||
let subcompositor_state = match SubcompositorState::bind(
|
||||
compositor_state.wl_compositor().clone(),
|
||||
globals,
|
||||
queue_handle,
|
||||
)
|
||||
.map_err(WaylandError::Bind)?;
|
||||
) {
|
||||
Ok(c) => Some(c),
|
||||
Err(e) => {
|
||||
warn!("Subcompositor protocol not available, ignoring CSD: {e:?}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let output_state = OutputState::new(globals, queue_handle);
|
||||
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
|
||||
@@ -148,13 +157,17 @@ impl WinitState {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let shm = Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
|
||||
let custom_cursor_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap()));
|
||||
|
||||
Ok(Self {
|
||||
registry_state,
|
||||
compositor_state: Arc::new(compositor_state),
|
||||
subcompositor_state: Arc::new(subcompositor_state),
|
||||
subcompositor_state: subcompositor_state.map(Arc::new),
|
||||
output_state,
|
||||
seat_state,
|
||||
shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
|
||||
shm,
|
||||
custom_cursor_pool,
|
||||
|
||||
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
|
||||
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
|
||||
@@ -294,7 +307,11 @@ impl WindowHandler for WinitState {
|
||||
&mut self.events_sink,
|
||||
);
|
||||
|
||||
self.window_compositor_updates[pos].size = Some(new_size);
|
||||
// NOTE: Only update when the value is `Some` to not override consequent configures with
|
||||
// the same sizes.
|
||||
if new_size.is_some() {
|
||||
self.window_compositor_updates[pos].size = new_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
56
src/platform_impl/linux/wayland/types/cursor.rs
Normal file
56
src/platform_impl/linux/wayland/types/cursor.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use cursor_icon::CursorIcon;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_shm::Format;
|
||||
use sctk::shm::slot::{Buffer, SlotPool};
|
||||
|
||||
use crate::cursor::CursorImage;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SelectedCursor {
|
||||
Named(CursorIcon),
|
||||
Custom(CustomCursor),
|
||||
}
|
||||
|
||||
impl Default for SelectedCursor {
|
||||
fn default() -> Self {
|
||||
Self::Named(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CustomCursor {
|
||||
pub buffer: Buffer,
|
||||
pub w: i32,
|
||||
pub h: i32,
|
||||
pub hotspot_x: i32,
|
||||
pub hotspot_y: i32,
|
||||
}
|
||||
|
||||
impl CustomCursor {
|
||||
pub(crate) fn new(pool: &mut SlotPool, image: &CursorImage) -> Self {
|
||||
let (buffer, canvas) = pool
|
||||
.create_buffer(
|
||||
image.width as i32,
|
||||
image.height as i32,
|
||||
4 * (image.width as i32),
|
||||
Format::Argb8888,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for (canvas_chunk, rgba_chunk) in canvas.chunks_exact_mut(4).zip(image.rgba.chunks_exact(4))
|
||||
{
|
||||
canvas_chunk[0] = rgba_chunk[2];
|
||||
canvas_chunk[1] = rgba_chunk[1];
|
||||
canvas_chunk[2] = rgba_chunk[0];
|
||||
canvas_chunk[3] = rgba_chunk[3];
|
||||
}
|
||||
|
||||
CustomCursor {
|
||||
buffer,
|
||||
w: image.width as i32,
|
||||
h: image.height as i32,
|
||||
hotspot_x: image.hotspot_x as i32,
|
||||
hotspot_y: image.hotspot_y as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Wayland protocol implementation boilerplate.
|
||||
|
||||
pub mod cursor;
|
||||
pub mod kwin_blur;
|
||||
pub mod wp_fractional_scaling;
|
||||
pub mod wp_viewporter;
|
||||
|
||||
@@ -20,8 +20,8 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
use crate::event_loop::AsyncRequestSerial;
|
||||
use crate::platform_impl::{
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
|
||||
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
|
||||
PlatformIcon, PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
|
||||
};
|
||||
use crate::window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
@@ -97,11 +97,9 @@ impl Window {
|
||||
.map(|activation_state| activation_state.global().clone());
|
||||
let display = event_loop_window_target.connection.display();
|
||||
|
||||
// XXX The initial scale factor must be 1, but it might cause sizing issues on HiDPI.
|
||||
let size: LogicalSize<u32> = attributes
|
||||
let size: Size = attributes
|
||||
.inner_size
|
||||
.map(|size| size.to_logical::<u32>(1.))
|
||||
.unwrap_or((800, 600).into());
|
||||
.unwrap_or(LogicalSize::new(800., 600.).into());
|
||||
|
||||
// We prefer server side decorations, however to not have decorations we ask for client
|
||||
// side decorations instead.
|
||||
@@ -141,7 +139,8 @@ impl Window {
|
||||
// Set the window title.
|
||||
window_state.set_title(attributes.title);
|
||||
|
||||
// Set the min and max sizes.
|
||||
// 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);
|
||||
@@ -315,12 +314,9 @@ impl Window {
|
||||
#[inline]
|
||||
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state.resize(size.to_logical::<u32>(scale_factor));
|
||||
|
||||
let new_size = window_state.request_inner_size(size);
|
||||
self.request_redraw();
|
||||
|
||||
Some(window_state.inner_size().to_physical(scale_factor))
|
||||
Some(new_size)
|
||||
}
|
||||
|
||||
/// Set the minimum inner size for the window.
|
||||
@@ -510,6 +506,14 @@ impl Window {
|
||||
self.window_state.lock().unwrap().set_cursor(cursor);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
|
||||
self.window_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_custom_cursor(&cursor.0);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
self.window_state
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//! The state of the window, which is shared with the event-loop.
|
||||
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use log::{info, warn};
|
||||
@@ -19,20 +18,23 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
|
||||
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
|
||||
use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
|
||||
|
||||
use sctk::compositor::{CompositorState, Region};
|
||||
use sctk::seat::pointer::ThemedPointer;
|
||||
use sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt};
|
||||
use sctk::seat::pointer::{PointerDataExt, ThemedPointer};
|
||||
use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
|
||||
use sctk::shell::xdg::XdgSurface;
|
||||
use sctk::shell::WaylandSurface;
|
||||
use sctk::shm::slot::SlotPool;
|
||||
use sctk::shm::Shm;
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use crate::cursor::CursorImage;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError};
|
||||
use crate::event::WindowEvent;
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::make_wid;
|
||||
use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
|
||||
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
|
||||
use crate::platform_impl::WindowId;
|
||||
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
|
||||
@@ -55,23 +57,22 @@ pub struct WindowState {
|
||||
/// The connection to Wayland server.
|
||||
pub connection: Connection,
|
||||
|
||||
/// The underlying SCTK window.
|
||||
pub window: ManuallyDrop<Window>,
|
||||
|
||||
/// The window frame, which is created from the configure request.
|
||||
frame: Option<WinitFrame>,
|
||||
|
||||
/// The `Shm` to set cursor.
|
||||
pub shm: WlShm,
|
||||
|
||||
// A shared pool where to allocate custom cursors.
|
||||
custom_cursor_pool: Arc<Mutex<SlotPool>>,
|
||||
|
||||
/// The last received configure.
|
||||
pub last_configure: Option<WindowConfigure>,
|
||||
|
||||
/// The pointers observed on the window.
|
||||
pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
|
||||
|
||||
/// Cursor icon.
|
||||
pub cursor_icon: CursorIcon,
|
||||
selected_cursor: SelectedCursor,
|
||||
|
||||
/// Wether the cursor is visible.
|
||||
pub cursor_visible: bool,
|
||||
@@ -133,6 +134,10 @@ pub struct WindowState {
|
||||
/// sends `None` for the new size in the configure.
|
||||
stateless_size: LogicalSize<u32>,
|
||||
|
||||
/// Initial window size provided by the user. Removed on the first
|
||||
/// configure.
|
||||
initial_size: Option<Size>,
|
||||
|
||||
/// The state of the frame callback.
|
||||
frame_callback_state: FrameCallbackState,
|
||||
|
||||
@@ -145,6 +150,9 @@ pub struct WindowState {
|
||||
///
|
||||
/// The value is the serial of the event triggered moved.
|
||||
has_pending_move: Option<u32>,
|
||||
|
||||
/// The underlying SCTK window.
|
||||
pub window: Window,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
@@ -153,7 +161,7 @@ impl WindowState {
|
||||
connection: Connection,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
winit_state: &WinitState,
|
||||
size: LogicalSize<u32>,
|
||||
initial_size: Size,
|
||||
window: Window,
|
||||
theme: Option<Theme>,
|
||||
) -> Self {
|
||||
@@ -175,7 +183,7 @@ impl WindowState {
|
||||
connection,
|
||||
csd_fails: false,
|
||||
cursor_grab_mode: GrabState::new(),
|
||||
cursor_icon: CursorIcon::Default,
|
||||
selected_cursor: Default::default(),
|
||||
cursor_visible: true,
|
||||
decorate: true,
|
||||
fractional_scale,
|
||||
@@ -194,14 +202,16 @@ impl WindowState {
|
||||
resizable: true,
|
||||
scale_factor: 1.,
|
||||
shm: winit_state.shm.wl_shm().clone(),
|
||||
size,
|
||||
stateless_size: size,
|
||||
custom_cursor_pool: winit_state.custom_cursor_pool.clone(),
|
||||
size: initial_size.to_logical(1.),
|
||||
stateless_size: initial_size.to_logical(1.),
|
||||
initial_size: Some(initial_size),
|
||||
text_inputs: Vec::new(),
|
||||
theme,
|
||||
title: String::default(),
|
||||
transparent: false,
|
||||
viewport,
|
||||
window: ManuallyDrop::new(window),
|
||||
window,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,16 +260,27 @@ impl WindowState {
|
||||
&mut self,
|
||||
configure: WindowConfigure,
|
||||
shm: &Shm,
|
||||
subcompositor: &Arc<SubcompositorState>,
|
||||
subcompositor: &Option<Arc<SubcompositorState>>,
|
||||
event_sink: &mut EventSink,
|
||||
) -> LogicalSize<u32> {
|
||||
if configure.decoration_mode == DecorationMode::Client
|
||||
&& self.frame.is_none()
|
||||
&& !self.csd_fails
|
||||
{
|
||||
) -> Option<LogicalSize<u32>> {
|
||||
// NOTE: when using fractional scaling or wl_compositor@v6 the scaling
|
||||
// should be delivered before the first configure, thus apply it to
|
||||
// properly scale the physical sizes provided by the users.
|
||||
if let Some(initial_size) = self.initial_size.take() {
|
||||
self.size = initial_size.to_logical(self.scale_factor());
|
||||
self.stateless_size = self.size;
|
||||
}
|
||||
|
||||
if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
|
||||
configure.decoration_mode == DecorationMode::Client
|
||||
&& self.frame.is_none()
|
||||
&& !self.csd_fails
|
||||
}) {
|
||||
match WinitFrame::new(
|
||||
&*self.window,
|
||||
&self.window,
|
||||
shm,
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
self.compositor.clone(),
|
||||
subcompositor.clone(),
|
||||
self.queue_handle.clone(),
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
@@ -297,37 +318,90 @@ impl WindowState {
|
||||
event_sink.push_window_event(WindowEvent::Occluded(occluded), window_id);
|
||||
}
|
||||
|
||||
let new_size = if let Some(frame) = self.frame.as_mut() {
|
||||
let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
|
||||
// Configure the window states.
|
||||
frame.update_state(configure.state);
|
||||
|
||||
match configure.new_size {
|
||||
(Some(width), Some(height)) => {
|
||||
let (width, height) = frame.subtract_borders(width, height);
|
||||
(
|
||||
width.map(|w| w.get()).unwrap_or(1),
|
||||
height.map(|h| h.get()).unwrap_or(1),
|
||||
)
|
||||
.into()
|
||||
let width = width.map(|w| w.get()).unwrap_or(1);
|
||||
let height = height.map(|h| h.get()).unwrap_or(1);
|
||||
((width, height).into(), false)
|
||||
}
|
||||
(_, _) if stateless => self.stateless_size,
|
||||
_ => self.size,
|
||||
(_, _) if stateless => (self.stateless_size, true),
|
||||
_ => (self.size, true),
|
||||
}
|
||||
} else {
|
||||
match configure.new_size {
|
||||
(Some(width), Some(height)) => (width.get(), height.get()).into(),
|
||||
_ if stateless => self.stateless_size,
|
||||
_ => self.size,
|
||||
(Some(width), Some(height)) => ((width.get(), height.get()).into(), false),
|
||||
_ if stateless => (self.stateless_size, true),
|
||||
_ => (self.size, true),
|
||||
}
|
||||
};
|
||||
|
||||
// XXX Set the configure before doing a resize.
|
||||
// Apply configure bounds only when compositor let the user decide what size to pick.
|
||||
if constrain {
|
||||
let bounds = self.inner_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
|
||||
.1
|
||||
.map(|bound_h| new_size.height.min(bound_h.get()))
|
||||
.unwrap_or(new_size.height);
|
||||
}
|
||||
|
||||
let new_state = configure.state;
|
||||
let old_state = self
|
||||
.last_configure
|
||||
.as_ref()
|
||||
.map(|configure| configure.state);
|
||||
|
||||
let state_change_requires_resize = old_state
|
||||
.map(|old_state| {
|
||||
!old_state
|
||||
.symmetric_difference(new_state)
|
||||
.difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
|
||||
.is_empty()
|
||||
})
|
||||
// NOTE: `None` is present for the initial configure, thus we must always resize.
|
||||
.unwrap_or(true);
|
||||
|
||||
// NOTE: Set the configure before doing a resize, since we query it during it.
|
||||
self.last_configure = Some(configure);
|
||||
|
||||
// XXX Update the new size right away.
|
||||
self.resize(new_size);
|
||||
if state_change_requires_resize || new_size != self.inner_size() {
|
||||
self.resize(new_size);
|
||||
Some(new_size)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
new_size
|
||||
/// Compute the bounds for the inner size of the surface.
|
||||
fn inner_size_bounds(
|
||||
&self,
|
||||
configure: &WindowConfigure,
|
||||
) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
|
||||
let configure_bounds = match configure.suggested_bounds {
|
||||
Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)),
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
if let Some(frame) = self.frame.as_ref() {
|
||||
let (width, height) = frame.subtract_borders(
|
||||
configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()),
|
||||
configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()),
|
||||
);
|
||||
(
|
||||
configure_bounds.0.and(width),
|
||||
configure_bounds.1.and(height),
|
||||
)
|
||||
} else {
|
||||
configure_bounds
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -537,7 +611,7 @@ impl WindowState {
|
||||
/// Refresh the decorations frame if it's present returning whether the client should redraw.
|
||||
pub fn refresh_frame(&mut self) -> bool {
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
if frame.is_dirty() {
|
||||
if !frame.is_hidden() && frame.is_dirty() {
|
||||
return frame.draw();
|
||||
}
|
||||
}
|
||||
@@ -548,7 +622,10 @@ impl WindowState {
|
||||
/// Reload the cursor style on the given window.
|
||||
pub fn reload_cursor_style(&mut self) {
|
||||
if self.cursor_visible {
|
||||
self.set_cursor(self.cursor_icon);
|
||||
match &self.selected_cursor {
|
||||
SelectedCursor::Named(icon) => self.set_cursor(*icon),
|
||||
SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
|
||||
}
|
||||
} else {
|
||||
self.set_cursor_visible(self.cursor_visible);
|
||||
}
|
||||
@@ -568,8 +645,22 @@ 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> {
|
||||
if self
|
||||
.last_configure
|
||||
.as_ref()
|
||||
.map(Self::is_stateless)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
self.resize(inner_size.to_logical(self.scale_factor()))
|
||||
}
|
||||
|
||||
self.inner_size().to_physical(self.scale_factor())
|
||||
}
|
||||
|
||||
/// Resize the window to the new inner size.
|
||||
pub fn resize(&mut self, inner_size: LogicalSize<u32>) {
|
||||
fn resize(&mut self, inner_size: LogicalSize<u32>) {
|
||||
self.size = inner_size;
|
||||
|
||||
// Update the stateless size.
|
||||
@@ -620,10 +711,8 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Set the cursor icon.
|
||||
///
|
||||
/// Providing `None` will hide the cursor.
|
||||
pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
|
||||
self.cursor_icon = cursor_icon;
|
||||
self.selected_cursor = SelectedCursor::Named(cursor_icon);
|
||||
|
||||
if !self.cursor_visible {
|
||||
return;
|
||||
@@ -636,6 +725,54 @@ impl WindowState {
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the custom cursor icon.
|
||||
pub(crate) fn set_custom_cursor(&mut self, cursor: &CursorImage) {
|
||||
let cursor = {
|
||||
let mut pool = self.custom_cursor_pool.lock().unwrap();
|
||||
CustomCursor::new(&mut pool, cursor)
|
||||
};
|
||||
|
||||
if self.cursor_visible {
|
||||
self.apply_custom_cursor(&cursor);
|
||||
}
|
||||
|
||||
self.selected_cursor = SelectedCursor::Custom(cursor);
|
||||
}
|
||||
|
||||
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
|
||||
self.apply_on_poiner(|pointer, _| {
|
||||
let surface = pointer.surface();
|
||||
|
||||
let scale = surface
|
||||
.data::<SurfaceData>()
|
||||
.unwrap()
|
||||
.surface_data()
|
||||
.scale_factor();
|
||||
|
||||
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);
|
||||
}
|
||||
surface.commit();
|
||||
|
||||
let serial = pointer
|
||||
.pointer()
|
||||
.data::<WinitPointerData>()
|
||||
.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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Set maximum inner window size.
|
||||
pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
|
||||
// Ensure that the window has the right minimum size.
|
||||
@@ -770,7 +907,10 @@ impl WindowState {
|
||||
self.cursor_visible = cursor_visible;
|
||||
|
||||
if self.cursor_visible {
|
||||
self.set_cursor(self.cursor_icon);
|
||||
match &self.selected_cursor {
|
||||
SelectedCursor::Named(icon) => self.set_cursor(*icon),
|
||||
SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
|
||||
}
|
||||
} else {
|
||||
for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
|
||||
let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial();
|
||||
@@ -843,7 +983,7 @@ impl WindowState {
|
||||
|
||||
/// Set the IME position.
|
||||
pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
|
||||
// XXX This won't fly unless user will have a way to request IME window per seat, since
|
||||
// FIXME: This won't fly unless user will have a way to request IME window per seat, since
|
||||
// the ime windows will be overlapping, but winit doesn't expose API to specify for
|
||||
// which seat we're setting IME position.
|
||||
let (x, y) = (position.x as i32, position.y as i32);
|
||||
@@ -874,7 +1014,7 @@ impl WindowState {
|
||||
pub fn set_scale_factor(&mut self, scale_factor: f64) {
|
||||
self.scale_factor = scale_factor;
|
||||
|
||||
// XXX when fractional scaling is not used update the buffer scale.
|
||||
// NOTE: When fractional scaling is not used update the buffer scale.
|
||||
if self.fractional_scale.is_none() {
|
||||
let _ = self.window.set_buffer_scale(self.scale_factor as _);
|
||||
}
|
||||
@@ -959,13 +1099,6 @@ impl WindowState {
|
||||
|
||||
impl Drop for WindowState {
|
||||
fn drop(&mut self) {
|
||||
let surface = self.window.wl_surface().clone();
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.window);
|
||||
}
|
||||
|
||||
// Cleanup objects.
|
||||
|
||||
if let Some(blur) = self.blur.take() {
|
||||
blur.release();
|
||||
}
|
||||
@@ -978,7 +1111,8 @@ impl Drop for WindowState {
|
||||
viewport.destroy();
|
||||
}
|
||||
|
||||
surface.destroy();
|
||||
// NOTE: the wl_surface used by the window is being cleaned up when
|
||||
// dropping SCTK `Window`.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1028,7 +1162,7 @@ impl From<ResizeDirection> for XdgResizeEdge {
|
||||
}
|
||||
}
|
||||
|
||||
// XXX rust doesn't allow from `Option`.
|
||||
// NOTE: Rust doesn't allow `From<Option<Theme>>`.
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
|
||||
match theme {
|
||||
|
||||
@@ -570,6 +570,14 @@ impl<T: 'static> EventProcessor<T> {
|
||||
event: WindowEvent::Destroyed,
|
||||
});
|
||||
}
|
||||
ffi::PropertyNotify => {
|
||||
let xev: &ffi::XPropertyEvent = xev.as_ref();
|
||||
let atom = xev.atom as xproto::Atom;
|
||||
|
||||
if atom == xproto::Atom::from(xproto::AtomEnum::RESOURCE_MANAGER) {
|
||||
self.process_dpi_change(&mut callback);
|
||||
}
|
||||
}
|
||||
|
||||
ffi::VisibilityNotify => {
|
||||
let xev: &ffi::XVisibilityEvent = xev.as_ref();
|
||||
@@ -1306,72 +1314,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
}
|
||||
if event_type == self.randr_event_offset as c_int {
|
||||
// In the future, it would be quite easy to emit monitor hotplug events.
|
||||
let prev_list = wt.xconn.invalidate_cached_monitor_list();
|
||||
if let Some(prev_list) = prev_list {
|
||||
let new_list = wt
|
||||
.xconn
|
||||
.available_monitors()
|
||||
.expect("Failed to get monitor list");
|
||||
for new_monitor in new_list {
|
||||
// Previous list may be empty, in case of disconnecting and
|
||||
// reconnecting the only one monitor. We still need to emit events in
|
||||
// this case.
|
||||
let maybe_prev_scale_factor = prev_list
|
||||
.iter()
|
||||
.find(|prev_monitor| prev_monitor.name == new_monitor.name)
|
||||
.map(|prev_monitor| prev_monitor.scale_factor);
|
||||
if Some(new_monitor.scale_factor) != maybe_prev_scale_factor {
|
||||
for (window_id, window) in wt.windows.borrow().iter() {
|
||||
if let Some(window) = window.upgrade() {
|
||||
// Check if the window is on this monitor
|
||||
let monitor =
|
||||
window.shared_state_lock().last_monitor.clone();
|
||||
if monitor.name == new_monitor.name {
|
||||
let (width, height) = window.inner_size_physical();
|
||||
let (new_width, new_height) = window.adjust_for_dpi(
|
||||
// If we couldn't determine the previous scale
|
||||
// factor (e.g., because all monitors were closed
|
||||
// before), just pick whatever the current monitor
|
||||
// has set as a baseline.
|
||||
maybe_prev_scale_factor
|
||||
.unwrap_or(monitor.scale_factor),
|
||||
new_monitor.scale_factor,
|
||||
width,
|
||||
height,
|
||||
&window.shared_state_lock(),
|
||||
);
|
||||
|
||||
let window_id = crate::window::WindowId(*window_id);
|
||||
let old_inner_size = PhysicalSize::new(width, height);
|
||||
let inner_size = Arc::new(Mutex::new(
|
||||
PhysicalSize::new(new_width, new_height),
|
||||
));
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: new_monitor.scale_factor,
|
||||
inner_size_writer: InnerSizeWriter::new(
|
||||
Arc::downgrade(&inner_size),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
let new_inner_size = *inner_size.lock().unwrap();
|
||||
drop(inner_size);
|
||||
|
||||
if new_inner_size != old_inner_size {
|
||||
let (new_width, new_height) = new_inner_size.into();
|
||||
window.request_inner_size_physical(
|
||||
new_width, new_height,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.process_dpi_change(&mut callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1464,6 +1407,45 @@ impl<T: 'static> EventProcessor<T> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn process_dpi_change<F>(&self, callback: &mut F)
|
||||
where
|
||||
F: FnMut(Event<T>),
|
||||
{
|
||||
let wt = get_xtarget(&self.target);
|
||||
|
||||
// In the future, it would be quite easy to emit monitor hotplug events.
|
||||
let prev_list = {
|
||||
let prev_list = wt.xconn.invalidate_cached_monitor_list();
|
||||
match prev_list {
|
||||
Some(prev_list) => prev_list,
|
||||
None => return,
|
||||
}
|
||||
};
|
||||
|
||||
let new_list = wt
|
||||
.xconn
|
||||
.available_monitors()
|
||||
.expect("Failed to get monitor list");
|
||||
for new_monitor in new_list {
|
||||
// Previous list may be empty, in case of disconnecting and
|
||||
// reconnecting the only one monitor. We still need to emit events in
|
||||
// this case.
|
||||
let maybe_prev_scale_factor = prev_list
|
||||
.iter()
|
||||
.find(|prev_monitor| prev_monitor.name == new_monitor.name)
|
||||
.map(|prev_monitor| prev_monitor.scale_factor);
|
||||
if Some(new_monitor.scale_factor) != maybe_prev_scale_factor {
|
||||
for window in wt.windows.borrow().iter().filter_map(|(_, w)| w.upgrade()) {
|
||||
window.refresh_dpi_for_monitor(
|
||||
&new_monitor,
|
||||
maybe_prev_scale_factor,
|
||||
&mut *callback,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_first_touch(first: &mut Option<u64>, num: &mut u32, id: u64, phase: TouchPhase) -> bool {
|
||||
|
||||
@@ -1,8 +1 @@
|
||||
use x11_dl::xmd::CARD32;
|
||||
pub use x11_dl::{
|
||||
error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*,
|
||||
};
|
||||
|
||||
// Isn't defined by x11_dl
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const IconicState: CARD32 = 3;
|
||||
pub use x11_dl::{error::OpenError, xcursor::*, xinput2::*, xlib::*, xlib_xcb::*};
|
||||
|
||||
@@ -81,6 +81,7 @@ use crate::{
|
||||
// Xinput constants not defined in x11rb
|
||||
const ALL_DEVICES: u16 = 0;
|
||||
const ALL_MASTER_DEVICES: u16 = 1;
|
||||
const ICONIC_STATE: u32 = 3;
|
||||
|
||||
type X11Source = Generic<BorrowedFd<'static>>;
|
||||
|
||||
@@ -1128,3 +1129,9 @@ impl Device {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the raw X11 representation for a 32-bit floating point to a double.
|
||||
#[inline]
|
||||
fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 {
|
||||
(fp as f64) / ((1 << 16) as f64)
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ impl XConnection {
|
||||
return Ok(MonitorHandle::dummy());
|
||||
}
|
||||
|
||||
let default = monitors.get(0).unwrap();
|
||||
let default = monitors.first().unwrap();
|
||||
|
||||
let window_rect = match window_rect {
|
||||
Some(rect) => rect,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::ffi::CString;
|
||||
use std::{ffi::CString, iter, slice, sync::Arc};
|
||||
|
||||
use x11rb::connection::Connection;
|
||||
|
||||
use crate::window::CursorIcon;
|
||||
use crate::{cursor::CursorImage, window::CursorIcon};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -19,6 +19,11 @@ impl XConnection {
|
||||
.expect("Failed to set cursor");
|
||||
}
|
||||
|
||||
pub(crate) fn set_custom_cursor(&self, window: xproto::Window, cursor: &CustomCursor) {
|
||||
self.update_cursor(window, cursor.inner.cursor)
|
||||
.expect("Failed to set cursor");
|
||||
}
|
||||
|
||||
fn create_empty_cursor(&self) -> ffi::Cursor {
|
||||
let data = 0;
|
||||
let pixmap = unsafe {
|
||||
@@ -56,10 +61,22 @@ impl XConnection {
|
||||
None => return self.create_empty_cursor(),
|
||||
};
|
||||
|
||||
let name = CString::new(cursor.name()).unwrap();
|
||||
unsafe {
|
||||
(self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char)
|
||||
let mut xcursor = 0;
|
||||
for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) {
|
||||
let name = CString::new(name).unwrap();
|
||||
xcursor = unsafe {
|
||||
(self.xcursor.XcursorLibraryLoadCursor)(
|
||||
self.display,
|
||||
name.as_ptr() as *const c_char,
|
||||
)
|
||||
};
|
||||
|
||||
if xcursor != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
xcursor
|
||||
}
|
||||
|
||||
fn update_cursor(&self, window: xproto::Window, cursor: ffi::Cursor) -> Result<(), X11Error> {
|
||||
@@ -74,3 +91,74 @@ impl XConnection {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SelectedCursor {
|
||||
Custom(CustomCursor),
|
||||
Named(CursorIcon),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CustomCursor {
|
||||
inner: Arc<CustomCursorInner>,
|
||||
}
|
||||
|
||||
impl CustomCursor {
|
||||
pub(crate) unsafe fn new(xconn: &Arc<XConnection>, image: &CursorImage) -> Self {
|
||||
unsafe {
|
||||
let ximage =
|
||||
(xconn.xcursor.XcursorImageCreate)(image.width as i32, image.height as i32);
|
||||
if ximage.is_null() {
|
||||
panic!("failed to allocate cursor image");
|
||||
}
|
||||
(*ximage).xhot = image.hotspot_x as u32;
|
||||
(*ximage).yhot = image.hotspot_y as u32;
|
||||
(*ximage).delay = 0;
|
||||
|
||||
let dst = slice::from_raw_parts_mut((*ximage).pixels, image.rgba.len() / 4);
|
||||
for (dst, chunk) in dst.iter_mut().zip(image.rgba.chunks_exact(4)) {
|
||||
*dst = (chunk[0] as u32) << 16
|
||||
| (chunk[1] as u32) << 8
|
||||
| (chunk[2] as u32)
|
||||
| (chunk[3] as u32) << 24;
|
||||
}
|
||||
|
||||
let cursor = (xconn.xcursor.XcursorImageLoadCursor)(xconn.display, ximage);
|
||||
(xconn.xcursor.XcursorImageDestroy)(ximage);
|
||||
Self {
|
||||
inner: Arc::new(CustomCursorInner {
|
||||
xconn: xconn.clone(),
|
||||
cursor,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CustomCursorInner {
|
||||
xconn: Arc<XConnection>,
|
||||
cursor: ffi::Cursor,
|
||||
}
|
||||
|
||||
impl Drop for CustomCursorInner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFreeCursor)(self.xconn.display, self.cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CustomCursorInner {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.cursor == other.cursor
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for CustomCursorInner {}
|
||||
|
||||
impl Default for SelectedCursor {
|
||||
fn default() -> Self {
|
||||
SelectedCursor::Named(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@ mod randr;
|
||||
mod window_property;
|
||||
mod wm;
|
||||
|
||||
pub use self::{
|
||||
client_msg::*, geometry::*, hint::*, icon::*, input::*, randr::*, window_property::*, wm::*,
|
||||
};
|
||||
pub use self::{cursor::*, geometry::*, hint::*, input::*, window_property::*, wm::*};
|
||||
|
||||
use std::{
|
||||
mem::{self, MaybeUninit},
|
||||
|
||||
@@ -38,7 +38,7 @@ impl XConnection {
|
||||
// Retrieve DPI from Xft.dpi property
|
||||
pub fn get_xft_dpi(&self) -> Option<f64> {
|
||||
self.database()
|
||||
.get_string("Xfi.dpi", "")
|
||||
.get_string("Xft.dpi", "")
|
||||
.and_then(|s| f64::from_str(s).ok())
|
||||
}
|
||||
pub fn get_output_info(
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::{
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use cursor_icon::CursorIcon;
|
||||
use x11rb::{
|
||||
connection::Connection,
|
||||
properties::{WmHints, WmHintsState, WmSizeHints, WmSizeHintsSpecification},
|
||||
@@ -22,20 +23,27 @@ use x11rb::{
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::{Event, InnerSizeWriter, WindowEvent},
|
||||
event_loop::AsyncRequestSerial,
|
||||
platform_impl::{
|
||||
x11::{atoms::*, MonitorHandle as X11MonitorHandle, WakeSender, X11Error},
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
|
||||
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
|
||||
x11::{
|
||||
atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender,
|
||||
X11Error,
|
||||
},
|
||||
OwnedWindowHandle as PlatformOwnedWindowHandle,
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
|
||||
PlatformIcon, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
|
||||
},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
WindowAttributes, WindowButtons, WindowLevel,
|
||||
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
|
||||
WindowButtons, WindowLevel,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
ffi, util, CookieResultExt, EventLoopWindowTarget, ImeRequest, ImeSender, VoidCookie, WindowId,
|
||||
ffi,
|
||||
util::{self, CustomCursor, SelectedCursor},
|
||||
CookieResultExt, EventLoopWindowTarget, ImeRequest, ImeSender, VoidCookie, WindowId,
|
||||
XConnection,
|
||||
};
|
||||
|
||||
@@ -122,7 +130,7 @@ pub(crate) struct UnownedWindow {
|
||||
root: xproto::Window, // never changes
|
||||
#[allow(dead_code)]
|
||||
screen_id: i32, // never changes
|
||||
cursor: Mutex<CursorIcon>,
|
||||
selected_cursor: Mutex<SelectedCursor>,
|
||||
cursor_grabbed_mode: Mutex<CursorGrabMode>,
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
cursor_visible: Mutex<bool>,
|
||||
@@ -150,15 +158,12 @@ impl UnownedWindow {
|
||||
) -> Result<UnownedWindow, RootOsError> {
|
||||
let xconn = &event_loop.xconn;
|
||||
let atoms = xconn.atoms();
|
||||
#[cfg(feature = "rwh_06")]
|
||||
let root = match window_attrs.parent_window.0 {
|
||||
Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window,
|
||||
Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(),
|
||||
Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"),
|
||||
let root = match window_attrs.parent_window {
|
||||
Some(PlatformOwnedWindowHandle::X(handle)) => handle,
|
||||
#[cfg(wayland_platform)]
|
||||
Some(handle) => panic!("invalid window handle {handle:?} on X11"),
|
||||
None => event_loop.root,
|
||||
};
|
||||
#[cfg(not(feature = "rwh_06"))]
|
||||
let root = event_loop.root;
|
||||
|
||||
let mut monitors = leap!(xconn.available_monitors());
|
||||
let guessed_monitor = if monitors.is_empty() {
|
||||
@@ -276,7 +281,8 @@ impl UnownedWindow {
|
||||
| EventMask::KEYMAP_STATE
|
||||
| EventMask::BUTTON_PRESS
|
||||
| EventMask::BUTTON_RELEASE
|
||||
| EventMask::POINTER_MOTION;
|
||||
| EventMask::POINTER_MOTION
|
||||
| EventMask::PROPERTY_CHANGE;
|
||||
|
||||
aux = aux.event_mask(event_mask).border_pixel(0);
|
||||
|
||||
@@ -350,7 +356,7 @@ impl UnownedWindow {
|
||||
visual,
|
||||
root,
|
||||
screen_id,
|
||||
cursor: Default::default(),
|
||||
selected_cursor: Default::default(),
|
||||
cursor_grabbed_mode: Mutex::new(CursorGrabMode::None),
|
||||
cursor_visible: Mutex::new(true),
|
||||
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
|
||||
@@ -923,6 +929,51 @@ impl UnownedWindow {
|
||||
})
|
||||
}
|
||||
|
||||
/// Refresh the API for the given monitor.
|
||||
#[inline]
|
||||
pub(super) fn refresh_dpi_for_monitor<T: 'static>(
|
||||
&self,
|
||||
new_monitor: &X11MonitorHandle,
|
||||
maybe_prev_scale_factor: Option<f64>,
|
||||
mut callback: impl FnMut(Event<T>),
|
||||
) {
|
||||
// Check if the self is on this monitor
|
||||
let monitor = self.shared_state_lock().last_monitor.clone();
|
||||
if monitor.name == new_monitor.name {
|
||||
let (width, height) = self.inner_size_physical();
|
||||
let (new_width, new_height) = self.adjust_for_dpi(
|
||||
// If we couldn't determine the previous scale
|
||||
// factor (e.g., because all monitors were closed
|
||||
// before), just pick whatever the current monitor
|
||||
// has set as a baseline.
|
||||
maybe_prev_scale_factor.unwrap_or(monitor.scale_factor),
|
||||
new_monitor.scale_factor,
|
||||
width,
|
||||
height,
|
||||
&self.shared_state_lock(),
|
||||
);
|
||||
|
||||
let window_id = crate::window::WindowId(self.id());
|
||||
let old_inner_size = PhysicalSize::new(width, height);
|
||||
let inner_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height)));
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: new_monitor.scale_factor,
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)),
|
||||
},
|
||||
});
|
||||
|
||||
let new_inner_size = *inner_size.lock().unwrap();
|
||||
drop(inner_size);
|
||||
|
||||
if new_inner_size != old_inner_size {
|
||||
let (new_width, new_height) = new_inner_size.into();
|
||||
self.request_inner_size_physical(new_width, new_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_minimized_inner(&self, minimized: bool) -> Result<VoidCookie<'_>, X11Error> {
|
||||
let atoms = self.xconn.atoms();
|
||||
|
||||
@@ -1327,7 +1378,8 @@ impl UnownedWindow {
|
||||
self.xwindow as xproto::Window,
|
||||
xproto::AtomEnum::WM_NORMAL_HINTS,
|
||||
)?
|
||||
.reply()?;
|
||||
.reply()?
|
||||
.unwrap_or_default();
|
||||
callback(&mut normal_hints);
|
||||
normal_hints
|
||||
.set(
|
||||
@@ -1380,6 +1432,7 @@ impl UnownedWindow {
|
||||
)
|
||||
.ok()
|
||||
.and_then(|cookie| cookie.reply().ok())
|
||||
.flatten()
|
||||
.and_then(|hints| hints.size_increment)
|
||||
.map(|(width, height)| (width as u32, height as u32).into())
|
||||
}
|
||||
@@ -1483,13 +1536,29 @@ impl UnownedWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
let old_cursor = replace(&mut *self.cursor.lock().unwrap(), cursor);
|
||||
let old_cursor = replace(
|
||||
&mut *self.selected_cursor.lock().unwrap(),
|
||||
SelectedCursor::Named(cursor),
|
||||
);
|
||||
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
if cursor != old_cursor && *self.cursor_visible.lock().unwrap() {
|
||||
if SelectedCursor::Named(cursor) != old_cursor && *self.cursor_visible.lock().unwrap() {
|
||||
self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
|
||||
let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor.0) };
|
||||
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
if *self.cursor_visible.lock().unwrap() {
|
||||
self.xconn.set_custom_cursor(self.xwindow, &new_cursor);
|
||||
}
|
||||
|
||||
*self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(new_cursor);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
|
||||
@@ -1576,13 +1645,23 @@ impl UnownedWindow {
|
||||
return;
|
||||
}
|
||||
let cursor = if visible {
|
||||
Some(*self.cursor.lock().unwrap())
|
||||
Some((*self.selected_cursor.lock().unwrap()).clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
*visible_lock = visible;
|
||||
drop(visible_lock);
|
||||
self.xconn.set_cursor_icon(self.xwindow, cursor);
|
||||
match cursor {
|
||||
Some(SelectedCursor::Custom(cursor)) => {
|
||||
self.xconn.set_custom_cursor(self.xwindow, &cursor);
|
||||
}
|
||||
Some(SelectedCursor::Named(cursor)) => {
|
||||
self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
|
||||
}
|
||||
None => {
|
||||
self.xconn.set_cursor_icon(self.xwindow, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1692,8 +1771,8 @@ impl UnownedWindow {
|
||||
| xproto::EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
),
|
||||
[
|
||||
(window.x as u32 + pointer.win_x as u32),
|
||||
(window.y as u32 + pointer.win_y as u32),
|
||||
(window.x as u32 + xinput_fp1616_to_float(pointer.win_x) as u32),
|
||||
(window.y as u32 + xinput_fp1616_to_float(pointer.win_y) as u32),
|
||||
action.try_into().unwrap(),
|
||||
1, // Button 1
|
||||
1,
|
||||
@@ -1735,9 +1814,9 @@ impl UnownedWindow {
|
||||
let state_type_atom = atoms[CARD32];
|
||||
let is_minimized = if let Ok(state) =
|
||||
self.xconn
|
||||
.get_property(self.xwindow, state_atom, state_type_atom)
|
||||
.get_property::<u32>(self.xwindow, state_atom, state_type_atom)
|
||||
{
|
||||
state.contains(&(ffi::IconicState as c_ulong))
|
||||
state.contains(&super::ICONIC_STATE)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@@ -1774,6 +1853,7 @@ impl UnownedWindow {
|
||||
WmHints::get(self.xconn.xcb_connection(), self.xwindow as xproto::Window)
|
||||
.ok()
|
||||
.and_then(|cookie| cookie.reply().ok())
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
wm_hints.urgent = request_type.is_some();
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use icrate::AppKit::{
|
||||
NSApplication, NSEvent, NSEventModifierFlagCommand, NSEventTypeKeyUp, NSEventTypeLeftMouseDown,
|
||||
NSEventTypeLeftMouseDragged, NSEventTypeLeftMouseUp, NSEventTypeMouseMoved,
|
||||
NSEventTypeOtherMouseDown, NSEventTypeOtherMouseDragged, NSEventTypeOtherMouseUp,
|
||||
NSEventTypeRightMouseDown, NSEventTypeRightMouseDragged, NSEventTypeRightMouseUp, NSResponder,
|
||||
};
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::{declare_class, msg_send, mutability, ClassType};
|
||||
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
|
||||
|
||||
use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
|
||||
use super::event::flags_contains;
|
||||
use super::{app_state::AppState, DEVICE_ID};
|
||||
use crate::event::{DeviceEvent, ElementState, Event};
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(super) struct WinitApplication;
|
||||
|
||||
unsafe impl ClassType for WinitApplication {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSApplication;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitApplication";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitApplication {}
|
||||
|
||||
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)
|
||||
@@ -27,13 +34,13 @@ declare_class!(
|
||||
// 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 = event.type_();
|
||||
let modifier_flags = event.modifierFlags();
|
||||
if event_type == NSEventType::NSKeyUp
|
||||
&& modifier_flags.contains(NSEventModifierFlags::NSCommandKeyMask)
|
||||
let event_type = unsafe { event.r#type() };
|
||||
let modifier_flags = unsafe { event.modifierFlags() };
|
||||
if event_type == NSEventTypeKeyUp
|
||||
&& flags_contains(modifier_flags, NSEventModifierFlagCommand)
|
||||
{
|
||||
if let Some(key_window) = self.keyWindow() {
|
||||
unsafe { key_window.sendEvent(event) };
|
||||
key_window.sendEvent(event);
|
||||
}
|
||||
} else {
|
||||
maybe_dispatch_device_event(event);
|
||||
@@ -44,14 +51,15 @@ declare_class!(
|
||||
);
|
||||
|
||||
fn maybe_dispatch_device_event(event: &NSEvent) {
|
||||
let event_type = event.type_();
|
||||
let event_type = unsafe { event.r#type() };
|
||||
#[allow(non_upper_case_globals)]
|
||||
match event_type {
|
||||
NSEventType::NSMouseMoved
|
||||
| NSEventType::NSLeftMouseDragged
|
||||
| NSEventType::NSOtherMouseDragged
|
||||
| NSEventType::NSRightMouseDragged => {
|
||||
let delta_x = event.deltaX() as f64;
|
||||
let delta_y = event.deltaY() as f64;
|
||||
NSEventTypeMouseMoved
|
||||
| NSEventTypeLeftMouseDragged
|
||||
| NSEventTypeOtherMouseDragged
|
||||
| NSEventTypeRightMouseDragged => {
|
||||
let delta_x = unsafe { event.deltaX() } as f64;
|
||||
let delta_y = unsafe { event.deltaY() } as f64;
|
||||
|
||||
if delta_x != 0.0 {
|
||||
queue_device_event(DeviceEvent::Motion {
|
||||
@@ -73,17 +81,15 @@ fn maybe_dispatch_device_event(event: &NSEvent) {
|
||||
});
|
||||
}
|
||||
}
|
||||
NSEventType::NSLeftMouseDown
|
||||
| NSEventType::NSRightMouseDown
|
||||
| NSEventType::NSOtherMouseDown => {
|
||||
NSEventTypeLeftMouseDown | NSEventTypeRightMouseDown | NSEventTypeOtherMouseDown => {
|
||||
queue_device_event(DeviceEvent::Button {
|
||||
button: event.buttonNumber() as u32,
|
||||
button: unsafe { event.buttonNumber() } as u32,
|
||||
state: ElementState::Pressed,
|
||||
});
|
||||
}
|
||||
NSEventType::NSLeftMouseUp | NSEventType::NSRightMouseUp | NSEventType::NSOtherMouseUp => {
|
||||
NSEventTypeLeftMouseUp | NSEventTypeRightMouseUp | NSEventTypeOtherMouseUp => {
|
||||
queue_device_event(DeviceEvent::Button {
|
||||
button: event.buttonNumber() as u32,
|
||||
button: unsafe { event.buttonNumber() } as u32,
|
||||
state: ElementState::Released,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,54 +1,41 @@
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::declare::{IvarBool, IvarEncode};
|
||||
use icrate::AppKit::{NSApplicationActivationPolicy, NSApplicationDelegate};
|
||||
use icrate::Foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType};
|
||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
|
||||
use super::app_state::AppState;
|
||||
use super::appkit::NSApplicationActivationPolicy;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct State {
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ApplicationDelegate {
|
||||
activation_policy: IvarEncode<NSApplicationActivationPolicy, "_activation_policy">,
|
||||
default_menu: IvarBool<"_default_menu">,
|
||||
activate_ignoring_other_apps: IvarBool<"_activate_ignoring_other_apps">,
|
||||
}
|
||||
|
||||
mod ivars;
|
||||
pub(super) struct ApplicationDelegate;
|
||||
|
||||
unsafe impl ClassType for ApplicationDelegate {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitApplicationDelegate";
|
||||
}
|
||||
|
||||
unsafe impl ApplicationDelegate {
|
||||
#[method(initWithActivationPolicy:defaultMenu:activateIgnoringOtherApps:)]
|
||||
unsafe fn init(
|
||||
this: *mut Self,
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
) -> Option<NonNull<Self>> {
|
||||
let this: Option<&mut Self> = unsafe { msg_send![super(this), init] };
|
||||
this.map(|this| {
|
||||
*this.activation_policy = activation_policy;
|
||||
*this.default_menu = default_menu;
|
||||
*this.activate_ignoring_other_apps = activate_ignoring_other_apps;
|
||||
NonNull::from(this)
|
||||
})
|
||||
}
|
||||
impl DeclaredClass for ApplicationDelegate {
|
||||
type Ivars = State;
|
||||
}
|
||||
|
||||
unsafe impl NSObjectProtocol for ApplicationDelegate {}
|
||||
|
||||
unsafe impl NSApplicationDelegate for ApplicationDelegate {
|
||||
#[method(applicationDidFinishLaunching:)]
|
||||
fn did_finish_launching(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("applicationDidFinishLaunching:");
|
||||
AppState::launched(
|
||||
*self.activation_policy,
|
||||
*self.default_menu,
|
||||
*self.activate_ignoring_other_apps,
|
||||
self.ivars().activation_policy,
|
||||
self.ivars().default_menu,
|
||||
self.ivars().activate_ignoring_other_apps,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,17 +50,16 @@ declare_class!(
|
||||
|
||||
impl ApplicationDelegate {
|
||||
pub(super) fn new(
|
||||
mtm: MainThreadMarker,
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
) -> Id<Self> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
Self::alloc(),
|
||||
initWithActivationPolicy: activation_policy,
|
||||
defaultMenu: default_menu,
|
||||
activateIgnoringOtherApps: activate_ignoring_other_apps,
|
||||
]
|
||||
}
|
||||
let this = mtm.alloc().set_ivars(State {
|
||||
activation_policy,
|
||||
default_menu,
|
||||
activate_ignoring_other_apps,
|
||||
});
|
||||
unsafe { msg_send_id![super(this), init] }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,14 @@ use std::{
|
||||
};
|
||||
|
||||
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};
|
||||
use icrate::Foundation::{is_main_thread, NSSize};
|
||||
use icrate::AppKit::{NSApplication, NSApplicationActivationPolicy};
|
||||
use icrate::Foundation::{is_main_thread, MainThreadMarker, NSSize};
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent};
|
||||
use super::{
|
||||
event_loop::PanicInfo, menu, observer::EventLoopWaker, util::Never, window::WinitWindow,
|
||||
event::dummy_event, event_loop::PanicInfo, menu, observer::EventLoopWaker, util::Never,
|
||||
window::WinitWindow,
|
||||
};
|
||||
use crate::{
|
||||
dpi::PhysicalSize,
|
||||
@@ -57,14 +58,14 @@ impl<T> EventLoopHandler<T> {
|
||||
where
|
||||
F: FnOnce(&mut EventLoopHandler<T>, RefMut<'_, dyn FnMut(Event<T>, &RootWindowTarget<T>)>),
|
||||
{
|
||||
// The `NSApp` and our `HANDLER` are global state and so it's possible that
|
||||
// we could get a delegate callback after the application has exit an
|
||||
// `NSApplication` and our `HANDLER` are global state and so it's possible
|
||||
// that we could get a delegate callback after the application has exit an
|
||||
// `EventLoop`. If the loop has been exit then our weak `self.callback`
|
||||
// will fail to upgrade.
|
||||
//
|
||||
// We don't want to panic or output any verbose logging if we fail to
|
||||
// upgrade the weak reference since it might be valid that the application
|
||||
// re-starts the `NSApp` after exiting a Winit `EventLoop`
|
||||
// re-starts the `NSApplication` after exiting a Winit `EventLoop`
|
||||
if let Some(callback) = self.callback.upgrade() {
|
||||
let callback = callback.borrow_mut();
|
||||
(f)(self, callback);
|
||||
@@ -144,9 +145,9 @@ impl Handler {
|
||||
|
||||
/// `true` after `ApplicationDelegate::applicationDidFinishLaunching` called
|
||||
///
|
||||
/// NB: This is global / `NSApp` state and since the app will only be launched
|
||||
/// once but an `EventLoop` may be run more than once then only the first
|
||||
/// `EventLoop` will observe the `NSApp` before it is launched.
|
||||
/// NB: This is global / `NSApplication` state and since the app will only
|
||||
/// be launched once but an `EventLoop` may be run more than once then only
|
||||
/// the first `EventLoop` will observe the application before it is launched.
|
||||
fn is_launched(&self) -> bool {
|
||||
self.launched.load(Ordering::Acquire)
|
||||
}
|
||||
@@ -158,8 +159,8 @@ impl Handler {
|
||||
|
||||
/// `true` if an `EventLoop` is currently running
|
||||
///
|
||||
/// NB: This is global / `NSApp` state and may persist beyond the lifetime of
|
||||
/// a running `EventLoop`.
|
||||
/// NB: This is global / `NSApplication` state and may persist beyond the
|
||||
/// lifetime of a running `EventLoop`.
|
||||
///
|
||||
/// # Caveat
|
||||
/// This is only intended to be called from the main thread
|
||||
@@ -167,7 +168,7 @@ impl Handler {
|
||||
self.running.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Set when an `EventLoop` starts running, after the `NSApp` is launched
|
||||
/// Set when an `EventLoop` starts running, after the `NSApplication` is launched
|
||||
///
|
||||
/// # Caveat
|
||||
/// This is only intended to be called from the main thread
|
||||
@@ -180,8 +181,8 @@ impl Handler {
|
||||
/// Since an `EventLoop` may be run more than once we need make sure to reset the
|
||||
/// `control_flow` state back to `Poll` each time the loop exits.
|
||||
///
|
||||
/// Note: that if the `NSApp` has been launched then that state is preserved, and we won't
|
||||
/// need to re-launch the app if subsequent EventLoops are run.
|
||||
/// 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.
|
||||
///
|
||||
/// # Caveat
|
||||
/// This is only intended to be called from the main thread
|
||||
@@ -329,7 +330,7 @@ impl Handler {
|
||||
) {
|
||||
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
|
||||
let new_inner_size = Arc::new(Mutex::new(suggested_size));
|
||||
let event = Event::WindowEvent {
|
||||
let scale_factor_changed_event = Event::WindowEvent {
|
||||
window_id: WindowId(window.id()),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
@@ -337,13 +338,19 @@ impl Handler {
|
||||
},
|
||||
};
|
||||
|
||||
callback.handle_nonuser_event(event);
|
||||
callback.handle_nonuser_event(scale_factor_changed_event);
|
||||
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
let logical_size = physical_size.to_logical(scale_factor);
|
||||
let size = NSSize::new(logical_size.width, logical_size.height);
|
||||
window.setContentSize(size);
|
||||
|
||||
let resized_event = Event::WindowEvent {
|
||||
window_id: WindowId(window.id()),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
};
|
||||
callback.handle_nonuser_event(resized_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -386,7 +393,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
// If `pump_events` is called to progress the event loop then we bootstrap the event
|
||||
// loop via `[NSApp run]` but will use `CFRunLoopRunInMode` for subsequent calls to
|
||||
// loop via `-[NSAppplication run]` but will use `CFRunLoopRunInMode` for subsequent calls to
|
||||
// `pump_events`
|
||||
pub fn request_stop_on_launch() {
|
||||
HANDLER.request_stop_app_on_launch();
|
||||
@@ -453,13 +460,15 @@ impl AppState {
|
||||
create_default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
) {
|
||||
let app = NSApp();
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let app = NSApplication::sharedApplication(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(activation_policy);
|
||||
|
||||
window_activation_hack(&app);
|
||||
#[allow(deprecated)]
|
||||
app.activateIgnoringOtherApps(activate_ignoring_other_apps);
|
||||
|
||||
HANDLER.set_launched();
|
||||
@@ -467,21 +476,22 @@ impl AppState {
|
||||
if create_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();
|
||||
menu::initialize(&app);
|
||||
}
|
||||
|
||||
Self::start_running();
|
||||
|
||||
// If the `NSApp` is being launched via `EventLoop::pump_events()` then we'll
|
||||
// If the application is being launched via `EventLoop::pump_events()` then we'll
|
||||
// want to stop the app once it is launched (and return to the external loop)
|
||||
//
|
||||
// In this case we still want to consider Winit's `EventLoop` to be "running",
|
||||
// so we call `start_running()` above.
|
||||
if HANDLER.should_stop_app_on_launch() {
|
||||
// Note: the original idea had been to only stop the underlying `RunLoop`
|
||||
// for the app but that didn't work as expected (`[NSApp run]` effectively
|
||||
// ignored the attempt to stop the RunLoop and re-started it.). So we
|
||||
// return from `pump_events` by stopping the `NSApp`
|
||||
// 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.
|
||||
Self::stop();
|
||||
}
|
||||
}
|
||||
@@ -494,9 +504,9 @@ impl AppState {
|
||||
|
||||
// Return when in callback due to https://github.com/rust-windowing/winit/issues/1779
|
||||
if panic_info.is_panicking()
|
||||
|| HANDLER.get_in_callback()
|
||||
|| !HANDLER.have_callback()
|
||||
|| !HANDLER.is_running()
|
||||
|| HANDLER.get_in_callback()
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -583,11 +593,12 @@ impl AppState {
|
||||
}
|
||||
|
||||
pub fn stop() {
|
||||
let app = NSApp();
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
autoreleasepool(|_| {
|
||||
app.stop(None);
|
||||
// To stop event loop immediately, we need to post some event here.
|
||||
app.postEvent_atStart(&NSEvent::dummy(), true);
|
||||
app.postEvent_atStart(&dummy_event().unwrap(), true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -601,9 +612,9 @@ impl AppState {
|
||||
// XXX: how does it make sense that `get_in_callback()` can ever return `true` here if we're
|
||||
// about to return to the `CFRunLoop` to poll for new events?
|
||||
if panic_info.is_panicking()
|
||||
|| HANDLER.get_in_callback()
|
||||
|| !HANDLER.have_callback()
|
||||
|| !HANDLER.is_running()
|
||||
|| HANDLER.get_in_callback()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
use icrate::Foundation::{NSArray, NSObject, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSAppearance;
|
||||
|
||||
unsafe impl ClassType for NSAppearance {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
type NSAppearanceName = NSString;
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSAppearance {
|
||||
#[method_id(appearanceNamed:)]
|
||||
pub fn appearanceNamed(name: &NSAppearanceName) -> Id<Self>;
|
||||
|
||||
#[method_id(bestMatchFromAppearancesWithNames:)]
|
||||
pub fn bestMatchFromAppearancesWithNames(
|
||||
&self,
|
||||
appearances: &NSArray<NSAppearanceName>,
|
||||
) -> Id<NSAppearanceName>;
|
||||
}
|
||||
);
|
||||
@@ -1,140 +0,0 @@
|
||||
use icrate::Foundation::{MainThreadMarker, NSArray, NSInteger, NSObject, NSUInteger};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
use objc2::{Encode, Encoding};
|
||||
|
||||
use super::{NSAppearance, NSEvent, NSMenu, NSResponder, NSWindow};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSApplication;
|
||||
|
||||
unsafe impl ClassType for NSApplication {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
pub(crate) fn NSApp() -> Id<NSApplication> {
|
||||
// TODO: Only allow access from main thread
|
||||
NSApplication::shared(unsafe { MainThreadMarker::new_unchecked() })
|
||||
}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSApplication {
|
||||
/// This can only be called on the main thread since it may initialize
|
||||
/// the application and since it's parameters may be changed by the main
|
||||
/// thread at any time (hence it is only safe to access on the main thread).
|
||||
pub fn shared(_mtm: MainThreadMarker) -> Id<Self> {
|
||||
let app: Option<_> = unsafe { msg_send_id![Self::class(), sharedApplication] };
|
||||
// SAFETY: `sharedApplication` always initializes the app if it isn't already
|
||||
unsafe { app.unwrap_unchecked() }
|
||||
}
|
||||
|
||||
#[method_id(currentEvent)]
|
||||
pub fn currentEvent(&self) -> Option<Id<NSEvent>>;
|
||||
|
||||
#[method(postEvent:atStart:)]
|
||||
pub fn postEvent_atStart(&self, event: &NSEvent, front_of_queue: bool);
|
||||
|
||||
#[method(presentationOptions)]
|
||||
pub fn presentationOptions(&self) -> NSApplicationPresentationOptions;
|
||||
|
||||
#[method_id(windows)]
|
||||
pub fn windows(&self) -> Id<NSArray<NSWindow>>;
|
||||
|
||||
#[method_id(keyWindow)]
|
||||
pub fn keyWindow(&self) -> Option<Id<NSWindow>>;
|
||||
|
||||
// TODO: NSApplicationDelegate
|
||||
#[method(setDelegate:)]
|
||||
pub fn setDelegate(&self, delegate: &AnyObject);
|
||||
|
||||
#[method(setPresentationOptions:)]
|
||||
pub fn setPresentationOptions(&self, options: NSApplicationPresentationOptions);
|
||||
|
||||
#[method(hide:)]
|
||||
pub fn hide(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(orderFrontCharacterPalette:)]
|
||||
#[allow(dead_code)]
|
||||
pub fn orderFrontCharacterPalette(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(hideOtherApplications:)]
|
||||
pub fn hideOtherApplications(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(stop:)]
|
||||
pub fn stop(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(activateIgnoringOtherApps:)]
|
||||
pub fn activateIgnoringOtherApps(&self, ignore: bool);
|
||||
|
||||
#[method(requestUserAttention:)]
|
||||
pub fn requestUserAttention(&self, type_: NSRequestUserAttentionType) -> NSInteger;
|
||||
|
||||
#[method(setActivationPolicy:)]
|
||||
pub fn setActivationPolicy(&self, policy: NSApplicationActivationPolicy) -> bool;
|
||||
|
||||
#[method(setMainMenu:)]
|
||||
pub fn setMainMenu(&self, menu: &NSMenu);
|
||||
|
||||
#[method_id(effectiveAppearance)]
|
||||
pub fn effectiveAppearance(&self) -> Id<NSAppearance>;
|
||||
|
||||
#[method(setAppearance:)]
|
||||
pub fn setAppearance(&self, appearance: Option<&NSAppearance>);
|
||||
|
||||
#[method(run)]
|
||||
pub unsafe fn run(&self);
|
||||
}
|
||||
);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)] // NSInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSApplicationActivationPolicy {
|
||||
NSApplicationActivationPolicyRegular = 0,
|
||||
NSApplicationActivationPolicyAccessory = 1,
|
||||
NSApplicationActivationPolicyProhibited = 2,
|
||||
NSApplicationActivationPolicyERROR = -1,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSApplicationActivationPolicy {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NSApplicationPresentationOptions: NSUInteger {
|
||||
const NSApplicationPresentationDefault = 0;
|
||||
const NSApplicationPresentationAutoHideDock = 1 << 0;
|
||||
const NSApplicationPresentationHideDock = 1 << 1;
|
||||
const NSApplicationPresentationAutoHideMenuBar = 1 << 2;
|
||||
const NSApplicationPresentationHideMenuBar = 1 << 3;
|
||||
const NSApplicationPresentationDisableAppleMenu = 1 << 4;
|
||||
const NSApplicationPresentationDisableProcessSwitching = 1 << 5;
|
||||
const NSApplicationPresentationDisableForceQuit = 1 << 6;
|
||||
const NSApplicationPresentationDisableSessionTermination = 1 << 7;
|
||||
const NSApplicationPresentationDisableHideApplication = 1 << 8;
|
||||
const NSApplicationPresentationDisableMenuBarTransparency = 1 << 9;
|
||||
const NSApplicationPresentationFullScreen = 1 << 10;
|
||||
const NSApplicationPresentationAutoHideToolbar = 1 << 11;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSApplicationPresentationOptions {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[repr(usize)] // NSUInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSRequestUserAttentionType {
|
||||
NSCriticalRequest = 0,
|
||||
NSInformationalRequest = 10,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSRequestUserAttentionType {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::{extern_class, mutability, ClassType};
|
||||
|
||||
use super::{NSControl, NSResponder, NSView};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSButton;
|
||||
|
||||
unsafe impl ClassType for NSButton {
|
||||
#[inherits(NSView, NSResponder, NSObject)]
|
||||
type Super = NSControl;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
@@ -1,28 +0,0 @@
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// An object that stores color data and sometimes opacity (alpha value).
|
||||
///
|
||||
/// <https://developer.apple.com/documentation/appkit/nscolor?language=objc>
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSColor;
|
||||
|
||||
unsafe impl ClassType for NSColor {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: Documentation clearly states:
|
||||
// > Color objects are immutable and thread-safe
|
||||
unsafe impl Send for NSColor {}
|
||||
unsafe impl Sync for NSColor {}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSColor {
|
||||
#[method_id(clearColor)]
|
||||
pub fn clear() -> Id<Self>;
|
||||
}
|
||||
);
|
||||
@@ -1,25 +0,0 @@
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::{NSResponder, NSView};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSControl;
|
||||
|
||||
unsafe impl ClassType for NSControl {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSView;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSControl {
|
||||
#[method(setEnabled:)]
|
||||
pub fn setEnabled(&self, enabled: bool);
|
||||
|
||||
#[method(isEnabled)]
|
||||
pub fn isEnabled(&self) -> bool;
|
||||
}
|
||||
);
|
||||
@@ -1,241 +0,0 @@
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use icrate::ns_string;
|
||||
use icrate::Foundation::{
|
||||
NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSString,
|
||||
};
|
||||
use objc2::rc::{DefaultId, Id};
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, sel, ClassType};
|
||||
|
||||
use super::NSImage;
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
extern_class!(
|
||||
/// <https://developer.apple.com/documentation/appkit/nscursor?language=objc>
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSCursor;
|
||||
|
||||
unsafe impl ClassType for NSCursor {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: NSCursor is immutable, stated here:
|
||||
// https://developer.apple.com/documentation/appkit/nscursor/1527062-image?language=objc
|
||||
unsafe impl Send for NSCursor {}
|
||||
unsafe impl Sync for NSCursor {}
|
||||
|
||||
macro_rules! def_cursor {
|
||||
{$(
|
||||
$(#[$($m:meta)*])*
|
||||
pub fn $name:ident();
|
||||
)*} => {$(
|
||||
$(#[$($m)*])*
|
||||
pub fn $name() -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::class(), $name] }
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
macro_rules! def_undocumented_cursor {
|
||||
{$(
|
||||
$(#[$($m:meta)*])*
|
||||
pub fn $name:ident();
|
||||
)*} => {$(
|
||||
$(#[$($m)*])*
|
||||
pub fn $name() -> Id<Self> {
|
||||
unsafe { Self::from_selector(sel!($name)).unwrap_or_else(|| Default::default()) }
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
extern_methods!(
|
||||
/// Documented cursors
|
||||
unsafe impl NSCursor {
|
||||
def_cursor!(
|
||||
pub fn arrowCursor();
|
||||
pub fn pointingHandCursor();
|
||||
pub fn openHandCursor();
|
||||
pub fn closedHandCursor();
|
||||
pub fn IBeamCursor();
|
||||
pub fn IBeamCursorForVerticalLayout();
|
||||
pub fn dragCopyCursor();
|
||||
pub fn dragLinkCursor();
|
||||
pub fn operationNotAllowedCursor();
|
||||
pub fn contextualMenuCursor();
|
||||
pub fn crosshairCursor();
|
||||
pub fn resizeRightCursor();
|
||||
pub fn resizeUpCursor();
|
||||
pub fn resizeLeftCursor();
|
||||
pub fn resizeDownCursor();
|
||||
pub fn resizeLeftRightCursor();
|
||||
pub fn resizeUpDownCursor();
|
||||
);
|
||||
|
||||
// Creating cursors should be thread-safe, though using them for anything probably isn't.
|
||||
pub fn new(image: &NSImage, hotSpot: NSPoint) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::alloc(), initWithImage: image, hotSpot: hotSpot] }
|
||||
}
|
||||
|
||||
pub fn invisible() -> Id<Self> {
|
||||
// 16x16 GIF data for invisible cursor
|
||||
// You can reproduce this via ImageMagick.
|
||||
// $ convert -size 16x16 xc:none cursor.gif
|
||||
static CURSOR_BYTES: &[u8] = &[
|
||||
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C,
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9,
|
||||
0xCB, 0xED, 0x0F, 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B,
|
||||
];
|
||||
|
||||
static CURSOR: Lazy<Id<NSCursor>> = Lazy::new(|| {
|
||||
// TODO: Consider using `dataWithBytesNoCopy:`
|
||||
let data = NSData::with_bytes(CURSOR_BYTES);
|
||||
let image = NSImage::new_with_data(&data);
|
||||
NSCursor::new(&image, NSPoint::new(0.0, 0.0))
|
||||
});
|
||||
|
||||
CURSOR.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Undocumented cursors
|
||||
unsafe impl NSCursor {
|
||||
#[method(respondsToSelector:)]
|
||||
fn class_responds_to(sel: Sel) -> bool;
|
||||
|
||||
#[method_id(performSelector:)]
|
||||
unsafe fn from_selector_unchecked(sel: Sel) -> Id<Self>;
|
||||
|
||||
unsafe fn from_selector(sel: Sel) -> Option<Id<Self>> {
|
||||
if Self::class_responds_to(sel) {
|
||||
Some(unsafe { Self::from_selector_unchecked(sel) })
|
||||
} else {
|
||||
warn!("Cursor `{:?}` appears to be invalid", sel);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def_undocumented_cursor!(
|
||||
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
|
||||
pub fn _helpCursor();
|
||||
pub fn _zoomInCursor();
|
||||
pub fn _zoomOutCursor();
|
||||
pub fn _windowResizeNorthEastCursor();
|
||||
pub fn _windowResizeNorthWestCursor();
|
||||
pub fn _windowResizeSouthEastCursor();
|
||||
pub fn _windowResizeSouthWestCursor();
|
||||
pub fn _windowResizeNorthEastSouthWestCursor();
|
||||
pub fn _windowResizeNorthWestSouthEastCursor();
|
||||
|
||||
// While these two are available, the former just loads a white arrow,
|
||||
// and the latter loads an ugly deflated beachball!
|
||||
// pub fn _moveCursor();
|
||||
// pub fn _waitCursor();
|
||||
|
||||
// An even more undocumented cursor...
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
|
||||
pub fn busyButClickableCursor();
|
||||
);
|
||||
}
|
||||
|
||||
/// Webkit cursors
|
||||
unsafe impl NSCursor {
|
||||
// Note that loading `busybutclickable` with this code won't animate
|
||||
// the frames; instead you'll just get them all in a column.
|
||||
unsafe fn load_webkit_cursor(name: &NSString) -> Id<Self> {
|
||||
// Snatch a cursor from WebKit; They fit the style of the native
|
||||
// cursors, and will seem completely standard to macOS users.
|
||||
//
|
||||
// https://stackoverflow.com/a/21786835/5435443
|
||||
let root = ns_string!("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors");
|
||||
let cursor_path = root.stringByAppendingPathComponent(name);
|
||||
|
||||
let pdf_path = cursor_path.stringByAppendingPathComponent(ns_string!("cursor.pdf"));
|
||||
let image = NSImage::new_by_referencing_file(&pdf_path);
|
||||
|
||||
// TODO: Handle PLists better
|
||||
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
|
||||
let info: Id<NSDictionary<NSObject, NSObject>> = unsafe {
|
||||
msg_send_id![
|
||||
<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()
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
let hotspot = NSPoint::new(x, y);
|
||||
Self::new(&image, hotspot)
|
||||
}
|
||||
|
||||
pub fn moveCursor() -> Id<Self> {
|
||||
unsafe { Self::load_webkit_cursor(ns_string!("move")) }
|
||||
}
|
||||
|
||||
pub fn cellCursor() -> Id<Self> {
|
||||
unsafe { Self::load_webkit_cursor(ns_string!("cell")) }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl NSCursor {
|
||||
pub fn from_icon(icon: CursorIcon) -> Id<Self> {
|
||||
match icon {
|
||||
CursorIcon::Default => Default::default(),
|
||||
CursorIcon::Pointer => Self::pointingHandCursor(),
|
||||
CursorIcon::Grab => Self::openHandCursor(),
|
||||
CursorIcon::Grabbing => Self::closedHandCursor(),
|
||||
CursorIcon::Text => Self::IBeamCursor(),
|
||||
CursorIcon::VerticalText => Self::IBeamCursorForVerticalLayout(),
|
||||
CursorIcon::Copy => Self::dragCopyCursor(),
|
||||
CursorIcon::Alias => Self::dragLinkCursor(),
|
||||
CursorIcon::NotAllowed | CursorIcon::NoDrop => Self::operationNotAllowedCursor(),
|
||||
CursorIcon::ContextMenu => Self::contextualMenuCursor(),
|
||||
CursorIcon::Crosshair => Self::crosshairCursor(),
|
||||
CursorIcon::EResize => Self::resizeRightCursor(),
|
||||
CursorIcon::NResize => Self::resizeUpCursor(),
|
||||
CursorIcon::WResize => Self::resizeLeftCursor(),
|
||||
CursorIcon::SResize => Self::resizeDownCursor(),
|
||||
CursorIcon::EwResize | CursorIcon::ColResize => Self::resizeLeftRightCursor(),
|
||||
CursorIcon::NsResize | CursorIcon::RowResize => Self::resizeUpDownCursor(),
|
||||
CursorIcon::Help => Self::_helpCursor(),
|
||||
CursorIcon::ZoomIn => Self::_zoomInCursor(),
|
||||
CursorIcon::ZoomOut => Self::_zoomOutCursor(),
|
||||
CursorIcon::NeResize => Self::_windowResizeNorthEastCursor(),
|
||||
CursorIcon::NwResize => Self::_windowResizeNorthWestCursor(),
|
||||
CursorIcon::SeResize => Self::_windowResizeSouthEastCursor(),
|
||||
CursorIcon::SwResize => Self::_windowResizeSouthWestCursor(),
|
||||
CursorIcon::NeswResize => Self::_windowResizeNorthEastSouthWestCursor(),
|
||||
CursorIcon::NwseResize => Self::_windowResizeNorthWestSouthEastCursor(),
|
||||
// This is the wrong semantics for `Wait`, but it's the same as
|
||||
// what's used in Safari and Chrome.
|
||||
CursorIcon::Wait | CursorIcon::Progress => Self::busyButClickableCursor(),
|
||||
CursorIcon::Move | CursorIcon::AllScroll => Self::moveCursor(),
|
||||
CursorIcon::Cell => Self::cellCursor(),
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultId for NSCursor {
|
||||
fn default_id() -> Id<Self> {
|
||||
Self::arrowCursor()
|
||||
}
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
use std::os::raw::c_ushort;
|
||||
|
||||
use icrate::Foundation::{
|
||||
CGFloat, NSCopying, NSInteger, NSObject, NSPoint, NSString, NSTimeInterval, NSUInteger,
|
||||
};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSEvent;
|
||||
|
||||
unsafe impl ClassType for NSEvent {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// > Safely handled only on the same thread, whether that be the main thread
|
||||
// > or a secondary thread; otherwise you run the risk of having events get
|
||||
// > out of sequence.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123383>
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSEvent {
|
||||
#[method_id(
|
||||
otherEventWithType:
|
||||
location:
|
||||
modifierFlags:
|
||||
timestamp:
|
||||
windowNumber:
|
||||
context:
|
||||
subtype:
|
||||
data1:
|
||||
data2:
|
||||
)]
|
||||
unsafe fn otherEventWithType(
|
||||
type_: NSEventType,
|
||||
location: NSPoint,
|
||||
flags: NSEventModifierFlags,
|
||||
time: NSTimeInterval,
|
||||
window_num: NSInteger,
|
||||
context: Option<&NSObject>, // NSGraphicsContext
|
||||
subtype: NSEventSubtype,
|
||||
data1: NSInteger,
|
||||
data2: NSInteger,
|
||||
) -> Id<Self>;
|
||||
|
||||
pub fn dummy() -> Id<Self> {
|
||||
unsafe {
|
||||
Self::otherEventWithType(
|
||||
NSEventType::NSApplicationDefined,
|
||||
NSPoint::new(0.0, 0.0),
|
||||
NSEventModifierFlags::empty(),
|
||||
0.0,
|
||||
0,
|
||||
None,
|
||||
NSEventSubtype::NSWindowExposedEventType,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[method_id(
|
||||
keyEventWithType:
|
||||
location:
|
||||
modifierFlags:
|
||||
timestamp:
|
||||
windowNumber:
|
||||
context:
|
||||
characters:
|
||||
charactersIgnoringModifiers:
|
||||
isARepeat:
|
||||
keyCode:
|
||||
)]
|
||||
pub fn keyEventWithType(
|
||||
type_: NSEventType,
|
||||
location: NSPoint,
|
||||
modifier_flags: NSEventModifierFlags,
|
||||
timestamp: NSTimeInterval,
|
||||
window_num: NSInteger,
|
||||
context: Option<&NSObject>,
|
||||
characters: &NSString,
|
||||
characters_ignoring_modifiers: &NSString,
|
||||
is_a_repeat: bool,
|
||||
scancode: c_ushort,
|
||||
) -> Id<Self>;
|
||||
|
||||
#[method(locationInWindow)]
|
||||
pub fn locationInWindow(&self) -> NSPoint;
|
||||
|
||||
// TODO: MainThreadMarker
|
||||
#[method(pressedMouseButtons)]
|
||||
pub fn pressedMouseButtons() -> NSUInteger;
|
||||
|
||||
#[method(modifierFlags)]
|
||||
pub fn modifierFlags(&self) -> NSEventModifierFlags;
|
||||
|
||||
#[method(type)]
|
||||
pub fn type_(&self) -> NSEventType;
|
||||
|
||||
#[method(keyCode)]
|
||||
pub fn key_code(&self) -> c_ushort;
|
||||
|
||||
#[method(magnification)]
|
||||
pub fn magnification(&self) -> CGFloat;
|
||||
|
||||
#[method(phase)]
|
||||
pub fn phase(&self) -> NSEventPhase;
|
||||
|
||||
#[method(momentumPhase)]
|
||||
pub fn momentumPhase(&self) -> NSEventPhase;
|
||||
|
||||
#[method(deltaX)]
|
||||
pub fn deltaX(&self) -> CGFloat;
|
||||
|
||||
#[method(deltaY)]
|
||||
pub fn deltaY(&self) -> CGFloat;
|
||||
|
||||
#[method(buttonNumber)]
|
||||
pub fn buttonNumber(&self) -> NSInteger;
|
||||
|
||||
#[method(scrollingDeltaX)]
|
||||
pub fn scrollingDeltaX(&self) -> CGFloat;
|
||||
|
||||
#[method(scrollingDeltaY)]
|
||||
pub fn scrollingDeltaY(&self) -> CGFloat;
|
||||
|
||||
#[method(hasPreciseScrollingDeltas)]
|
||||
pub fn hasPreciseScrollingDeltas(&self) -> bool;
|
||||
|
||||
#[method(rotation)]
|
||||
pub fn rotation(&self) -> f32;
|
||||
|
||||
#[method(pressure)]
|
||||
pub fn pressure(&self) -> f32;
|
||||
|
||||
#[method(stage)]
|
||||
pub fn stage(&self) -> NSInteger;
|
||||
|
||||
#[method(isARepeat)]
|
||||
pub fn is_a_repeat(&self) -> bool;
|
||||
|
||||
#[method(windowNumber)]
|
||||
pub fn window_number(&self) -> NSInteger;
|
||||
|
||||
#[method(timestamp)]
|
||||
pub fn timestamp(&self) -> NSTimeInterval;
|
||||
|
||||
#[method_id(characters)]
|
||||
pub fn characters(&self) -> Option<Id<NSString>>;
|
||||
|
||||
#[method_id(charactersIgnoringModifiers)]
|
||||
pub fn charactersIgnoringModifiers(&self) -> Option<Id<NSString>>;
|
||||
|
||||
pub fn lshift_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICELSHIFTKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn rshift_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICERSHIFTKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn lctrl_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICELCTLKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn rctrl_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICERCTLKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn lalt_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICELALTKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn ralt_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICERALTKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn lcmd_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICELCMDKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn rcmd_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICERCMDKEYMASK != 0
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl NSCopying for NSEvent {}
|
||||
|
||||
// The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259
|
||||
const NX_DEVICELCTLKEYMASK: u32 = 0x00000001;
|
||||
const NX_DEVICELSHIFTKEYMASK: u32 = 0x00000002;
|
||||
const NX_DEVICERSHIFTKEYMASK: u32 = 0x00000004;
|
||||
const NX_DEVICELCMDKEYMASK: u32 = 0x00000008;
|
||||
const NX_DEVICERCMDKEYMASK: u32 = 0x00000010;
|
||||
const NX_DEVICELALTKEYMASK: u32 = 0x00000020;
|
||||
const NX_DEVICERALTKEYMASK: u32 = 0x00000040;
|
||||
const NX_DEVICERCTLKEYMASK: u32 = 0x00002000;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct NSEventModifierFlags: NSUInteger {
|
||||
const NSAlphaShiftKeyMask = 1 << 16;
|
||||
const NSShiftKeyMask = 1 << 17;
|
||||
const NSControlKeyMask = 1 << 18;
|
||||
const NSAlternateKeyMask = 1 << 19;
|
||||
const NSCommandKeyMask = 1 << 20;
|
||||
const NSNumericPadKeyMask = 1 << 21;
|
||||
const NSHelpKeyMask = 1 << 22;
|
||||
const NSFunctionKeyMask = 1 << 23;
|
||||
const NSDeviceIndependentModifierFlagsMask = 0xffff0000;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventModifierFlags {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct NSEventPhase: NSUInteger {
|
||||
const NSEventPhaseNone = 0;
|
||||
const NSEventPhaseBegan = 0x1 << 0;
|
||||
const NSEventPhaseStationary = 0x1 << 1;
|
||||
const NSEventPhaseChanged = 0x1 << 2;
|
||||
const NSEventPhaseEnded = 0x1 << 3;
|
||||
const NSEventPhaseCancelled = 0x1 << 4;
|
||||
const NSEventPhaseMayBegin = 0x1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventPhase {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(i16)] // short
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSEventSubtype {
|
||||
// TODO: Not sure what these values are
|
||||
// NSMouseEventSubtype = NX_SUBTYPE_DEFAULT,
|
||||
// NSTabletPointEventSubtype = NX_SUBTYPE_TABLET_POINT,
|
||||
// NSTabletProximityEventSubtype = NX_SUBTYPE_TABLET_PROXIMITY
|
||||
// NSTouchEventSubtype = NX_SUBTYPE_MOUSE_TOUCH,
|
||||
NSWindowExposedEventType = 0,
|
||||
NSApplicationActivatedEventType = 1,
|
||||
NSApplicationDeactivatedEventType = 2,
|
||||
NSWindowMovedEventType = 4,
|
||||
NSScreenChangedEventType = 8,
|
||||
NSAWTEventType = 16,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventSubtype {
|
||||
const ENCODING: Encoding = i16::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[repr(usize)] // NSUInteger
|
||||
pub enum NSEventType {
|
||||
NSLeftMouseDown = 1,
|
||||
NSLeftMouseUp = 2,
|
||||
NSRightMouseDown = 3,
|
||||
NSRightMouseUp = 4,
|
||||
NSMouseMoved = 5,
|
||||
NSLeftMouseDragged = 6,
|
||||
NSRightMouseDragged = 7,
|
||||
NSMouseEntered = 8,
|
||||
NSMouseExited = 9,
|
||||
NSKeyDown = 10,
|
||||
NSKeyUp = 11,
|
||||
NSFlagsChanged = 12,
|
||||
NSAppKitDefined = 13,
|
||||
NSSystemDefined = 14,
|
||||
NSApplicationDefined = 15,
|
||||
NSPeriodic = 16,
|
||||
NSCursorUpdate = 17,
|
||||
NSScrollWheel = 22,
|
||||
NSTabletPoint = 23,
|
||||
NSTabletProximity = 24,
|
||||
NSOtherMouseDown = 25,
|
||||
NSOtherMouseUp = 26,
|
||||
NSOtherMouseDragged = 27,
|
||||
NSEventTypeGesture = 29,
|
||||
NSEventTypeMagnify = 30,
|
||||
NSEventTypeSwipe = 31,
|
||||
NSEventTypeRotate = 18,
|
||||
NSEventTypeBeginGesture = 19,
|
||||
NSEventTypeEndGesture = 20,
|
||||
NSEventTypePressure = 34,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventType {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
use icrate::Foundation::{NSData, NSObject, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
// TODO: Can this be mutable?
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSImage;
|
||||
|
||||
unsafe impl ClassType for NSImage {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// Documented Thread-Unsafe, but:
|
||||
// > One thread can create an NSImage object, draw to the image buffer,
|
||||
// > and pass it off to the main thread for drawing. The underlying image
|
||||
// > cache is shared among all threads.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-126728>
|
||||
//
|
||||
// So really only unsafe to mutate on several threads.
|
||||
unsafe impl Send for NSImage {}
|
||||
unsafe impl Sync for NSImage {}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSImage {
|
||||
pub fn new_by_referencing_file(path: &NSString) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::alloc(), initByReferencingFile: path] }
|
||||
}
|
||||
|
||||
pub fn new_with_data(data: &NSData) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::alloc(), initWithData: data] }
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1,25 +0,0 @@
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::NSMenuItem;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSMenu;
|
||||
|
||||
unsafe impl ClassType for NSMenu {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSMenu {
|
||||
#[method_id(new)]
|
||||
pub fn new() -> Id<Self>;
|
||||
|
||||
#[method(addItem:)]
|
||||
pub fn addItem(&self, item: &NSMenuItem);
|
||||
}
|
||||
);
|
||||
@@ -1,43 +0,0 @@
|
||||
use icrate::Foundation::{NSObject, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::{NSEventModifierFlags, NSMenu};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSMenuItem;
|
||||
|
||||
unsafe impl ClassType for NSMenuItem {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSMenuItem {
|
||||
#[method_id(new)]
|
||||
pub fn new() -> Id<Self>;
|
||||
|
||||
pub fn newWithTitle(title: &NSString, action: Sel, key_equivalent: &NSString) -> Id<Self> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
Self::alloc(),
|
||||
initWithTitle: title,
|
||||
action: action,
|
||||
keyEquivalent: key_equivalent,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[method_id(separatorItem)]
|
||||
pub fn separatorItem() -> Id<Self>;
|
||||
|
||||
#[method(setKeyEquivalentModifierMask:)]
|
||||
pub fn setKeyEquivalentModifierMask(&self, mask: NSEventModifierFlags);
|
||||
|
||||
#[method(setSubmenu:)]
|
||||
pub fn setSubmenu(&self, submenu: &NSMenu);
|
||||
}
|
||||
);
|
||||
@@ -1,66 +0,0 @@
|
||||
//! Safe bindings for the AppKit framework.
|
||||
//!
|
||||
//! These are split out from the rest of `winit` to make safety easier to review.
|
||||
//! In the future, these should probably live in another crate like `cacao`.
|
||||
//!
|
||||
//! TODO: Main thread safety.
|
||||
// Objective-C methods have different conventions, and it's much easier to
|
||||
// understand if we just use the same names
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
#![allow(clippy::enum_variant_names)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
mod appearance;
|
||||
mod application;
|
||||
mod button;
|
||||
mod color;
|
||||
mod control;
|
||||
mod cursor;
|
||||
mod event;
|
||||
mod image;
|
||||
mod menu;
|
||||
mod menu_item;
|
||||
mod pasteboard;
|
||||
mod responder;
|
||||
mod screen;
|
||||
mod tab_group;
|
||||
mod text_input_client;
|
||||
mod text_input_context;
|
||||
mod version;
|
||||
mod view;
|
||||
mod window;
|
||||
|
||||
pub(crate) use self::appearance::NSAppearance;
|
||||
pub(crate) use self::application::{
|
||||
NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions,
|
||||
NSRequestUserAttentionType,
|
||||
};
|
||||
pub(crate) use self::button::NSButton;
|
||||
pub(crate) use self::color::NSColor;
|
||||
pub(crate) use self::control::NSControl;
|
||||
pub(crate) use self::cursor::NSCursor;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use self::event::{
|
||||
NSEvent, NSEventModifierFlags, NSEventPhase, NSEventSubtype, NSEventType,
|
||||
};
|
||||
pub(crate) use self::image::NSImage;
|
||||
pub(crate) use self::menu::NSMenu;
|
||||
pub(crate) use self::menu_item::NSMenuItem;
|
||||
pub(crate) use self::pasteboard::{NSFilenamesPboardType, NSPasteboard, NSPasteboardType};
|
||||
pub(crate) use self::responder::NSResponder;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use self::screen::{NSDeviceDescriptionKey, NSScreen};
|
||||
pub(crate) use self::tab_group::NSWindowTabGroup;
|
||||
pub(crate) use self::text_input_client::NSTextInputClient;
|
||||
pub(crate) use self::text_input_context::NSTextInputContext;
|
||||
pub(crate) use self::version::NSAppKitVersion;
|
||||
pub(crate) use self::view::{NSTrackingRectTag, NSView};
|
||||
pub(crate) use self::window::{
|
||||
NSBackingStoreType, NSWindow, NSWindowButton, NSWindowLevel, NSWindowOcclusionState,
|
||||
NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode,
|
||||
NSWindowTitleVisibility,
|
||||
};
|
||||
|
||||
#[link(name = "AppKit", kind = "framework")]
|
||||
extern "C" {}
|
||||
@@ -1,26 +0,0 @@
|
||||
use icrate::Foundation::{NSObject, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSPasteboard;
|
||||
|
||||
unsafe impl ClassType for NSPasteboard {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSPasteboard {
|
||||
#[method_id(propertyListForType:)]
|
||||
pub fn propertyListForType(&self, type_: &NSPasteboardType) -> Id<NSObject>;
|
||||
}
|
||||
);
|
||||
|
||||
pub type NSPasteboardType = NSString;
|
||||
|
||||
extern "C" {
|
||||
pub static NSFilenamesPboardType: &'static NSPasteboardType;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use icrate::Foundation::{NSArray, NSObject};
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::NSEvent;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NSResponder;
|
||||
|
||||
unsafe impl ClassType for NSResponder {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// Documented as "Thread-Unsafe".
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSResponder {
|
||||
#[method(interpretKeyEvents:)]
|
||||
pub(crate) unsafe fn interpretKeyEvents(&self, events: &NSArray<NSEvent>);
|
||||
}
|
||||
);
|
||||
@@ -1,65 +0,0 @@
|
||||
use icrate::ns_string;
|
||||
use icrate::Foundation::{CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSScreen;
|
||||
|
||||
unsafe impl ClassType for NSScreen {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: Main thread marker!
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSScreen {
|
||||
/// The application object must have been created.
|
||||
#[method_id(mainScreen)]
|
||||
pub fn main() -> Option<Id<Self>>;
|
||||
|
||||
/// The application object must have been created.
|
||||
#[method_id(screens)]
|
||||
pub fn screens() -> Id<NSArray<Self>>;
|
||||
|
||||
#[method(frame)]
|
||||
pub fn frame(&self) -> NSRect;
|
||||
|
||||
#[method(visibleFrame)]
|
||||
pub fn visibleFrame(&self) -> NSRect;
|
||||
|
||||
#[method_id(deviceDescription)]
|
||||
pub fn deviceDescription(&self) -> Id<NSDictionary<NSDeviceDescriptionKey, AnyObject>>;
|
||||
|
||||
pub fn display_id(&self) -> u32 {
|
||||
let key = ns_string!("NSScreenNumber");
|
||||
|
||||
objc2::rc::autoreleasepool(|_| {
|
||||
let device_description = self.deviceDescription();
|
||||
|
||||
// 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:
|
||||
// <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 };
|
||||
|
||||
obj.as_u32()
|
||||
})
|
||||
}
|
||||
|
||||
#[method(backingScaleFactor)]
|
||||
pub fn backingScaleFactor(&self) -> CGFloat;
|
||||
}
|
||||
);
|
||||
|
||||
pub type NSDeviceDescriptionKey = NSString;
|
||||
@@ -1,31 +0,0 @@
|
||||
use icrate::Foundation::{NSArray, NSObject};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::NSWindow;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSWindowTabGroup;
|
||||
|
||||
unsafe impl ClassType for NSWindowTabGroup {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSWindowTabGroup {
|
||||
#[method(selectNextTab)]
|
||||
pub fn selectNextTab(&self);
|
||||
|
||||
#[method(selectPreviousTab)]
|
||||
pub fn selectPreviousTab(&self);
|
||||
|
||||
#[method_id(windows)]
|
||||
pub fn tabbedWindows(&self) -> Option<Id<NSArray<NSWindow>>>;
|
||||
|
||||
#[method(setSelectedWindow:)]
|
||||
pub fn setSelectedWindow(&self, window: &NSWindow);
|
||||
}
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
use objc2::{extern_protocol, ProtocolType};
|
||||
|
||||
extern_protocol!(
|
||||
pub(crate) unsafe trait NSTextInputClient {
|
||||
// TODO: Methods
|
||||
}
|
||||
|
||||
unsafe impl ProtocolType for dyn NSTextInputClient {}
|
||||
);
|
||||
@@ -1,29 +0,0 @@
|
||||
use icrate::Foundation::{NSObject, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
type NSTextInputSourceIdentifier = NSString;
|
||||
|
||||
extern_class!(
|
||||
/// Main-Thread-Only!
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSTextInputContext;
|
||||
|
||||
unsafe impl ClassType for NSTextInputContext {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSTextInputContext {
|
||||
#[method(invalidateCharacterCoordinates)]
|
||||
pub fn invalidateCharacterCoordinates(&self);
|
||||
|
||||
#[method(discardMarkedText)]
|
||||
pub fn discardMarkedText(&self);
|
||||
|
||||
#[method_id(selectedKeyboardInputSource)]
|
||||
pub fn selectedKeyboardInputSource(&self) -> Option<Id<NSTextInputSourceIdentifier>>;
|
||||
}
|
||||
);
|
||||
@@ -1,62 +0,0 @@
|
||||
#[repr(transparent)]
|
||||
#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
|
||||
pub struct NSAppKitVersion(f64);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
impl NSAppKitVersion {
|
||||
pub fn current() -> Self {
|
||||
extern "C" {
|
||||
static NSAppKitVersionNumber: NSAppKitVersion;
|
||||
}
|
||||
|
||||
unsafe { NSAppKitVersionNumber }
|
||||
}
|
||||
|
||||
pub fn floor(self) -> Self {
|
||||
Self(self.0.floor())
|
||||
}
|
||||
|
||||
pub const NSAppKitVersionNumber10_0: Self = Self(577.0);
|
||||
pub const NSAppKitVersionNumber10_1: Self = Self(620.0);
|
||||
pub const NSAppKitVersionNumber10_2: Self = Self(663.0);
|
||||
pub const NSAppKitVersionNumber10_2_3: Self = Self(663.6);
|
||||
pub const NSAppKitVersionNumber10_3: Self = Self(743.0);
|
||||
pub const NSAppKitVersionNumber10_3_2: Self = Self(743.14);
|
||||
pub const NSAppKitVersionNumber10_3_3: Self = Self(743.2);
|
||||
pub const NSAppKitVersionNumber10_3_5: Self = Self(743.24);
|
||||
pub const NSAppKitVersionNumber10_3_7: Self = Self(743.33);
|
||||
pub const NSAppKitVersionNumber10_3_9: Self = Self(743.36);
|
||||
pub const NSAppKitVersionNumber10_4: Self = Self(824.0);
|
||||
pub const NSAppKitVersionNumber10_4_1: Self = Self(824.1);
|
||||
pub const NSAppKitVersionNumber10_4_3: Self = Self(824.23);
|
||||
pub const NSAppKitVersionNumber10_4_4: Self = Self(824.33);
|
||||
pub const NSAppKitVersionNumber10_4_7: Self = Self(824.41);
|
||||
pub const NSAppKitVersionNumber10_5: Self = Self(949.0);
|
||||
pub const NSAppKitVersionNumber10_5_2: Self = Self(949.27);
|
||||
pub const NSAppKitVersionNumber10_5_3: Self = Self(949.33);
|
||||
pub const NSAppKitVersionNumber10_6: Self = Self(1038.0);
|
||||
pub const NSAppKitVersionNumber10_7: Self = Self(1138.0);
|
||||
pub const NSAppKitVersionNumber10_7_2: Self = Self(1138.23);
|
||||
pub const NSAppKitVersionNumber10_7_3: Self = Self(1138.32);
|
||||
pub const NSAppKitVersionNumber10_7_4: Self = Self(1138.47);
|
||||
pub const NSAppKitVersionNumber10_8: Self = Self(1187.0);
|
||||
pub const NSAppKitVersionNumber10_9: Self = Self(1265.0);
|
||||
pub const NSAppKitVersionNumber10_10: Self = Self(1343.0);
|
||||
pub const NSAppKitVersionNumber10_10_2: Self = Self(1344.0);
|
||||
pub const NSAppKitVersionNumber10_10_3: Self = Self(1347.0);
|
||||
pub const NSAppKitVersionNumber10_10_4: Self = Self(1348.0);
|
||||
pub const NSAppKitVersionNumber10_10_5: Self = Self(1348.0);
|
||||
pub const NSAppKitVersionNumber10_10_Max: Self = Self(1349.0);
|
||||
pub const NSAppKitVersionNumber10_11: Self = Self(1404.0);
|
||||
pub const NSAppKitVersionNumber10_11_1: Self = Self(1404.13);
|
||||
pub const NSAppKitVersionNumber10_11_2: Self = Self(1404.34);
|
||||
pub const NSAppKitVersionNumber10_11_3: Self = Self(1404.34);
|
||||
pub const NSAppKitVersionNumber10_12: Self = Self(1504.0);
|
||||
pub const NSAppKitVersionNumber10_12_1: Self = Self(1504.60);
|
||||
pub const NSAppKitVersionNumber10_12_2: Self = Self(1504.76);
|
||||
pub const NSAppKitVersionNumber10_13: Self = Self(1561.0);
|
||||
pub const NSAppKitVersionNumber10_13_1: Self = Self(1561.1);
|
||||
pub const NSAppKitVersionNumber10_13_2: Self = Self(1561.2);
|
||||
pub const NSAppKitVersionNumber10_13_4: Self = Self(1561.4);
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
use std::ffi::c_void;
|
||||
use std::num::NonZeroIsize;
|
||||
use std::ptr;
|
||||
|
||||
use icrate::Foundation::{NSObject, NSPoint, NSRect};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::{NSCursor, NSResponder, NSTextInputContext, NSWindow};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSView;
|
||||
|
||||
unsafe impl ClassType for NSView {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// Documented as "Main Thread Only".
|
||||
// > generally thread safe, although operations on views such as creating,
|
||||
// > resizing, and moving should happen on the main thread.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
|
||||
//
|
||||
// > If you want to use a thread to draw to a view, bracket all drawing code
|
||||
// > between the lockFocusIfCanDraw and unlockFocus methods of NSView.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB>
|
||||
|
||||
extern_methods!(
|
||||
/// Getter methods
|
||||
unsafe impl NSView {
|
||||
#[method(frame)]
|
||||
pub fn frame(&self) -> NSRect;
|
||||
|
||||
#[method(bounds)]
|
||||
pub fn bounds(&self) -> NSRect;
|
||||
|
||||
#[method_id(inputContext)]
|
||||
pub fn inputContext(
|
||||
&self,
|
||||
// _mtm: MainThreadMarker,
|
||||
) -> Option<Id<NSTextInputContext>>;
|
||||
|
||||
#[method(hasMarkedText)]
|
||||
pub fn hasMarkedText(&self) -> bool;
|
||||
|
||||
#[method(convertPoint:fromView:)]
|
||||
pub fn convertPoint_fromView(&self, point: NSPoint, view: Option<&NSView>) -> NSPoint;
|
||||
|
||||
#[method_id(window)]
|
||||
pub fn window(&self) -> Option<Id<NSWindow>>;
|
||||
}
|
||||
|
||||
unsafe impl NSView {
|
||||
#[method(setWantsBestResolutionOpenGLSurface:)]
|
||||
pub fn setWantsBestResolutionOpenGLSurface(&self, value: bool);
|
||||
|
||||
#[method(setWantsLayer:)]
|
||||
pub fn setWantsLayer(&self, wants_layer: bool);
|
||||
|
||||
#[method(setPostsFrameChangedNotifications:)]
|
||||
pub fn setPostsFrameChangedNotifications(&self, value: bool);
|
||||
|
||||
#[method(removeTrackingRect:)]
|
||||
pub fn removeTrackingRect(&self, tag: NSTrackingRectTag);
|
||||
|
||||
#[method(addTrackingRect:owner:userData:assumeInside:)]
|
||||
unsafe fn inner_addTrackingRect(
|
||||
&self,
|
||||
rect: NSRect,
|
||||
owner: &AnyObject,
|
||||
user_data: *mut c_void,
|
||||
assume_inside: bool,
|
||||
) -> Option<NSTrackingRectTag>;
|
||||
|
||||
pub fn add_tracking_rect(&self, rect: NSRect, assume_inside: bool) -> NSTrackingRectTag {
|
||||
// SAFETY: The user data is NULL, so it is valid
|
||||
unsafe { self.inner_addTrackingRect(rect, self, ptr::null_mut(), assume_inside) }
|
||||
.expect("failed creating tracking rect")
|
||||
}
|
||||
|
||||
#[method(addCursorRect:cursor:)]
|
||||
// NSCursor safe to take by shared reference since it is already immutable
|
||||
pub fn addCursorRect(&self, rect: NSRect, cursor: &NSCursor);
|
||||
|
||||
#[method(setHidden:)]
|
||||
pub fn setHidden(&self, hidden: bool);
|
||||
}
|
||||
);
|
||||
|
||||
/// <https://developer.apple.com/documentation/appkit/nstrackingrecttag?language=objc>
|
||||
pub type NSTrackingRectTag = NonZeroIsize; // NSInteger, but non-zero!
|
||||
@@ -1,437 +0,0 @@
|
||||
use icrate::Foundation::{
|
||||
CGFloat, NSArray, NSInteger, NSObject, NSPoint, NSRect, NSSize, NSString, NSUInteger,
|
||||
};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::{
|
||||
NSButton, NSColor, NSEvent, NSPasteboardType, NSResponder, NSScreen, NSView, NSWindowTabGroup,
|
||||
};
|
||||
|
||||
extern_class!(
|
||||
/// Main-Thread-Only!
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NSWindow;
|
||||
|
||||
unsafe impl ClassType for NSWindow {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// Documented as "Main Thread Only", but:
|
||||
// > Thread safe in that you can create and manage them on a secondary thread.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123364>
|
||||
//
|
||||
// So could in theory be `Send`, and perhaps also `Sync` - but we would like
|
||||
// interior mutability on windows, since that's just much easier, and in that
|
||||
// case, they can't be!
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSWindow {
|
||||
#[method(frame)]
|
||||
pub(crate) fn frame(&self) -> NSRect;
|
||||
|
||||
#[method(backingScaleFactor)]
|
||||
pub(crate) fn backingScaleFactor(&self) -> CGFloat;
|
||||
|
||||
#[method_id(contentView)]
|
||||
pub(crate) fn contentView(&self) -> Id<NSView>;
|
||||
|
||||
#[method(setContentView:)]
|
||||
pub(crate) fn setContentView(&self, view: &NSView);
|
||||
|
||||
#[method(setInitialFirstResponder:)]
|
||||
pub(crate) fn setInitialFirstResponder(&self, view: &NSView);
|
||||
|
||||
#[method(makeFirstResponder:)]
|
||||
#[must_use]
|
||||
pub(crate) fn makeFirstResponder(&self, responder: Option<&NSResponder>) -> bool;
|
||||
|
||||
#[method(contentRectForFrameRect:)]
|
||||
pub(crate) fn contentRectForFrameRect(&self, windowFrame: NSRect) -> NSRect;
|
||||
|
||||
#[method_id(screen)]
|
||||
pub(crate) fn screen(&self) -> Option<Id<NSScreen>>;
|
||||
|
||||
#[method(setContentSize:)]
|
||||
pub(crate) fn setContentSize(&self, contentSize: NSSize);
|
||||
|
||||
#[method(setFrameTopLeftPoint:)]
|
||||
pub(crate) fn setFrameTopLeftPoint(&self, point: NSPoint);
|
||||
|
||||
#[method(setMinSize:)]
|
||||
pub(crate) fn setMinSize(&self, minSize: NSSize);
|
||||
|
||||
#[method(setMaxSize:)]
|
||||
pub(crate) fn setMaxSize(&self, maxSize: NSSize);
|
||||
|
||||
#[method(setResizeIncrements:)]
|
||||
pub(crate) fn setResizeIncrements(&self, increments: NSSize);
|
||||
|
||||
#[method(contentResizeIncrements)]
|
||||
pub(crate) fn contentResizeIncrements(&self) -> NSSize;
|
||||
|
||||
#[method(setContentResizeIncrements:)]
|
||||
pub(crate) fn setContentResizeIncrements(&self, increments: NSSize);
|
||||
|
||||
#[method(setFrame:display:)]
|
||||
pub(crate) fn setFrame_display(&self, frameRect: NSRect, flag: bool);
|
||||
|
||||
#[method(setMovable:)]
|
||||
pub(crate) fn setMovable(&self, movable: bool);
|
||||
|
||||
#[method(setSharingType:)]
|
||||
pub(crate) fn setSharingType(&self, sharingType: NSWindowSharingType);
|
||||
|
||||
#[method(setTabbingMode:)]
|
||||
pub(crate) fn setTabbingMode(&self, tabbingMode: NSWindowTabbingMode);
|
||||
|
||||
#[method(setOpaque:)]
|
||||
pub(crate) fn setOpaque(&self, opaque: bool);
|
||||
|
||||
#[method(hasShadow)]
|
||||
pub(crate) fn hasShadow(&self) -> bool;
|
||||
|
||||
#[method(setHasShadow:)]
|
||||
pub(crate) fn setHasShadow(&self, has_shadow: bool);
|
||||
|
||||
#[method(setIgnoresMouseEvents:)]
|
||||
pub(crate) fn setIgnoresMouseEvents(&self, ignores: bool);
|
||||
|
||||
#[method(setBackgroundColor:)]
|
||||
pub(crate) fn setBackgroundColor(&self, color: &NSColor);
|
||||
|
||||
#[method(styleMask)]
|
||||
pub(crate) fn styleMask(&self) -> NSWindowStyleMask;
|
||||
|
||||
#[method(setStyleMask:)]
|
||||
pub(crate) fn setStyleMask(&self, mask: NSWindowStyleMask);
|
||||
|
||||
#[method(registerForDraggedTypes:)]
|
||||
pub(crate) fn registerForDraggedTypes(&self, types: &NSArray<NSPasteboardType>);
|
||||
|
||||
#[method(makeKeyAndOrderFront:)]
|
||||
pub(crate) fn makeKeyAndOrderFront(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(orderFront:)]
|
||||
pub(crate) fn orderFront(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(miniaturize:)]
|
||||
pub(crate) fn miniaturize(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(deminiaturize:)]
|
||||
pub(crate) fn deminiaturize(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(toggleFullScreen:)]
|
||||
pub(crate) fn toggleFullScreen(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(orderOut:)]
|
||||
pub(crate) fn orderOut(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(zoom:)]
|
||||
pub(crate) fn zoom(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(selectNextKeyView:)]
|
||||
pub(crate) fn selectNextKeyView(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(selectPreviousKeyView:)]
|
||||
pub(crate) fn selectPreviousKeyView(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method_id(firstResponder)]
|
||||
pub(crate) fn firstResponder(&self) -> Option<Id<NSResponder>>;
|
||||
|
||||
#[method_id(standardWindowButton:)]
|
||||
pub(crate) fn standardWindowButton(&self, kind: NSWindowButton) -> Option<Id<NSButton>>;
|
||||
|
||||
#[method(setTitle:)]
|
||||
pub(crate) fn setTitle(&self, title: &NSString);
|
||||
|
||||
#[method_id(title)]
|
||||
pub(crate) fn title_(&self) -> Id<NSString>;
|
||||
|
||||
#[method(setReleasedWhenClosed:)]
|
||||
pub(crate) fn setReleasedWhenClosed(&self, val: bool);
|
||||
|
||||
#[method(setAcceptsMouseMovedEvents:)]
|
||||
pub(crate) fn setAcceptsMouseMovedEvents(&self, val: bool);
|
||||
|
||||
#[method(setTitlebarAppearsTransparent:)]
|
||||
pub(crate) fn setTitlebarAppearsTransparent(&self, val: bool);
|
||||
|
||||
#[method(setTitleVisibility:)]
|
||||
pub(crate) fn setTitleVisibility(&self, visibility: NSWindowTitleVisibility);
|
||||
|
||||
#[method(setMovableByWindowBackground:)]
|
||||
pub(crate) fn setMovableByWindowBackground(&self, val: bool);
|
||||
|
||||
#[method(setLevel:)]
|
||||
pub(crate) fn setLevel(&self, level: NSWindowLevel);
|
||||
|
||||
#[method(setAllowsAutomaticWindowTabbing:)]
|
||||
pub(crate) fn setAllowsAutomaticWindowTabbing(val: bool);
|
||||
|
||||
#[method(setTabbingIdentifier:)]
|
||||
pub(crate) fn setTabbingIdentifier(&self, identifier: &NSString);
|
||||
|
||||
#[method(setDocumentEdited:)]
|
||||
pub(crate) fn setDocumentEdited(&self, val: bool);
|
||||
|
||||
#[method(occlusionState)]
|
||||
pub(crate) fn occlusionState(&self) -> NSWindowOcclusionState;
|
||||
|
||||
#[method(center)]
|
||||
pub(crate) fn center(&self);
|
||||
|
||||
#[method(isResizable)]
|
||||
pub(crate) fn isResizable(&self) -> bool;
|
||||
|
||||
#[method(isMiniaturizable)]
|
||||
pub(crate) fn isMiniaturizable(&self) -> bool;
|
||||
|
||||
#[method(hasCloseBox)]
|
||||
pub(crate) fn hasCloseBox(&self) -> bool;
|
||||
|
||||
#[method(isMiniaturized)]
|
||||
pub(crate) fn isMiniaturized(&self) -> bool;
|
||||
|
||||
#[method(isVisible)]
|
||||
pub(crate) fn isVisible(&self) -> bool;
|
||||
|
||||
#[method(isKeyWindow)]
|
||||
pub(crate) fn isKeyWindow(&self) -> bool;
|
||||
|
||||
#[method(isZoomed)]
|
||||
pub(crate) fn isZoomed(&self) -> bool;
|
||||
|
||||
#[method(allowsAutomaticWindowTabbing)]
|
||||
pub(crate) fn allowsAutomaticWindowTabbing() -> bool;
|
||||
|
||||
#[method(selectNextTab)]
|
||||
pub(crate) fn selectNextTab(&self);
|
||||
|
||||
#[method_id(tabbingIdentifier)]
|
||||
pub(crate) fn tabbingIdentifier(&self) -> Id<NSString>;
|
||||
|
||||
#[method_id(tabGroup)]
|
||||
pub(crate) fn tabGroup(&self) -> Option<Id<NSWindowTabGroup>>;
|
||||
|
||||
#[method(isDocumentEdited)]
|
||||
pub(crate) fn isDocumentEdited(&self) -> bool;
|
||||
|
||||
#[method(close)]
|
||||
pub(crate) fn close(&self);
|
||||
|
||||
#[method(performWindowDragWithEvent:)]
|
||||
// TODO: Can this actually accept NULL?
|
||||
pub(crate) fn performWindowDragWithEvent(&self, event: Option<&NSEvent>);
|
||||
|
||||
#[method(invalidateCursorRectsForView:)]
|
||||
pub(crate) fn invalidateCursorRectsForView(&self, view: &NSView);
|
||||
|
||||
#[method(setDelegate:)]
|
||||
pub(crate) fn setDelegate(&self, delegate: Option<&NSObject>);
|
||||
|
||||
#[method(sendEvent:)]
|
||||
pub(crate) unsafe fn sendEvent(&self, event: &NSEvent);
|
||||
|
||||
#[method(addChildWindow:ordered:)]
|
||||
pub(crate) unsafe fn addChildWindow(&self, child: &NSWindow, ordered: NSWindowOrderingMode);
|
||||
}
|
||||
);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)] // NSInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowTitleVisibility {
|
||||
#[doc(alias = "NSWindowTitleVisible")]
|
||||
Visible = 0,
|
||||
#[doc(alias = "NSWindowTitleHidden")]
|
||||
Hidden = 1,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowTitleVisibility {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(usize)] // NSUInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowButton {
|
||||
#[doc(alias = "NSWindowCloseButton")]
|
||||
Close = 0,
|
||||
#[doc(alias = "NSWindowMiniaturizeButton")]
|
||||
Miniaturize = 1,
|
||||
#[doc(alias = "NSWindowZoomButton")]
|
||||
Zoom = 2,
|
||||
#[doc(alias = "NSWindowToolbarButton")]
|
||||
Toolbar = 3,
|
||||
#[doc(alias = "NSWindowDocumentIconButton")]
|
||||
DocumentIcon = 4,
|
||||
#[doc(alias = "NSWindowDocumentVersionsButton")]
|
||||
DocumentVersions = 6,
|
||||
#[doc(alias = "NSWindowFullScreenButton")]
|
||||
#[deprecated = "Deprecated since macOS 10.12"]
|
||||
FullScreen = 7,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowButton {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
// 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)]
|
||||
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;
|
||||
}
|
||||
use window_level::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct NSWindowLevel(pub NSInteger);
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl NSWindowLevel {
|
||||
#[doc(alias = "BelowNormalWindowLevel")]
|
||||
pub const BELOW_NORMAL: Self = Self((kCGNormalWindowLevel - 1) as _);
|
||||
#[doc(alias = "NSNormalWindowLevel")]
|
||||
pub const Normal: Self = Self(kCGNormalWindowLevel as _);
|
||||
#[doc(alias = "NSFloatingWindowLevel")]
|
||||
pub const Floating: Self = Self(kCGFloatingWindowLevel as _);
|
||||
#[doc(alias = "NSTornOffMenuWindowLevel")]
|
||||
pub const TornOffMenu: Self = Self(kCGTornOffMenuWindowLevel as _);
|
||||
#[doc(alias = "NSModalPanelWindowLevel")]
|
||||
pub const ModalPanel: Self = Self(kCGModalPanelWindowLevel as _);
|
||||
#[doc(alias = "NSMainMenuWindowLevel")]
|
||||
pub const MainMenu: Self = Self(kCGMainMenuWindowLevel as _);
|
||||
#[doc(alias = "NSStatusWindowLevel")]
|
||||
pub const Status: Self = Self(kCGStatusWindowLevel as _);
|
||||
#[doc(alias = "NSPopUpMenuWindowLevel")]
|
||||
pub const PopUpMenu: Self = Self(kCGPopUpMenuWindowLevel as _);
|
||||
#[doc(alias = "NSScreenSaverWindowLevel")]
|
||||
pub const ScreenSaver: Self = Self(kCGScreenSaverWindowLevel as _);
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowLevel {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct NSWindowOcclusionState: NSUInteger {
|
||||
const NSWindowOcclusionStateVisible = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowOcclusionState {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NSWindowStyleMask: NSUInteger {
|
||||
const NSBorderlessWindowMask = 0;
|
||||
const NSTitledWindowMask = 1 << 0;
|
||||
const NSClosableWindowMask = 1 << 1;
|
||||
const NSMiniaturizableWindowMask = 1 << 2;
|
||||
const NSResizableWindowMask = 1 << 3;
|
||||
const NSTexturedBackgroundWindowMask = 1 << 8;
|
||||
const NSUnifiedTitleAndToolbarWindowMask = 1 << 12;
|
||||
const NSFullScreenWindowMask = 1 << 14;
|
||||
const NSFullSizeContentViewWindowMask = 1 << 15;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowStyleMask {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(usize)] // NSUInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSBackingStoreType {
|
||||
NSBackingStoreRetained = 0,
|
||||
NSBackingStoreNonretained = 1,
|
||||
NSBackingStoreBuffered = 2,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSBackingStoreType {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(usize)] // NSUInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowSharingType {
|
||||
NSWindowSharingNone = 0,
|
||||
NSWindowSharingReadOnly = 1,
|
||||
NSWindowSharingReadWrite = 2,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowSharingType {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)] // NSInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowOrderingMode {
|
||||
NSWindowAbove = 1,
|
||||
NSWindowBelow = -1,
|
||||
NSWindowOut = 0,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowOrderingMode {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)] // NSInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowTabbingMode {
|
||||
NSWindowTabbingModeAutomatic = 0,
|
||||
NSWindowTabbingModeDisallowed = 2,
|
||||
NSWindowTabbingModePreferred = 1,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowTabbingMode {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
228
src/platform_impl/macos/cursor.rs
Normal file
228
src/platform_impl/macos/cursor.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
use icrate::AppKit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
|
||||
use icrate::Foundation::{
|
||||
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
|
||||
NSString,
|
||||
};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{msg_send_id, sel, ClassType};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::ffi::c_uchar;
|
||||
use std::slice;
|
||||
|
||||
use super::EventLoopWindowTarget;
|
||||
use crate::cursor::CursorImage;
|
||||
use crate::cursor::OnlyCursorImageBuilder;
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct CustomCursor(pub(crate) Id<NSCursor>);
|
||||
|
||||
// SAFETY: NSCursor is immutable and thread-safe
|
||||
// TODO(madsmtm): Put this logic in icrate itself
|
||||
unsafe impl Send for CustomCursor {}
|
||||
unsafe impl Sync for CustomCursor {}
|
||||
|
||||
impl CustomCursor {
|
||||
pub(crate) fn build<T>(
|
||||
cursor: OnlyCursorImageBuilder,
|
||||
_: &EventLoopWindowTarget<T>,
|
||||
) -> CustomCursor {
|
||||
Self(cursor_from_image(&cursor.0))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Id<NSCursor> {
|
||||
let width = cursor.width;
|
||||
let height = cursor.height;
|
||||
|
||||
let bitmap = unsafe {
|
||||
NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel(
|
||||
NSBitmapImageRep::alloc(),
|
||||
std::ptr::null_mut::<*mut c_uchar>(),
|
||||
width as isize,
|
||||
height as isize,
|
||||
8,
|
||||
4,
|
||||
true,
|
||||
false,
|
||||
NSDeviceRGBColorSpace,
|
||||
width as isize * 4,
|
||||
32,
|
||||
).unwrap()
|
||||
};
|
||||
let bitmap_data = unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.rgba.len()) };
|
||||
bitmap_data.copy_from_slice(&cursor.rgba);
|
||||
|
||||
let image = unsafe {
|
||||
NSImage::initWithSize(NSImage::alloc(), NSSize::new(width.into(), height.into()))
|
||||
};
|
||||
unsafe { image.addRepresentation(&bitmap) };
|
||||
|
||||
let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64);
|
||||
|
||||
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
|
||||
}
|
||||
|
||||
pub(crate) fn default_cursor() -> Id<NSCursor> {
|
||||
NSCursor::arrowCursor()
|
||||
}
|
||||
|
||||
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Id<NSCursor>> {
|
||||
let cls = NSCursor::class();
|
||||
if cls.responds_to(sel) {
|
||||
let cursor: Id<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
|
||||
Some(cursor)
|
||||
} else {
|
||||
warn!("cursor `{sel}` appears to be invalid");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! def_undocumented_cursor {
|
||||
{$(
|
||||
$(#[$($m:meta)*])*
|
||||
fn $name:ident();
|
||||
)*} => {$(
|
||||
$(#[$($m)*])*
|
||||
#[allow(non_snake_case)]
|
||||
fn $name() -> Id<NSCursor> {
|
||||
unsafe { try_cursor_from_selector(sel!($name)).unwrap_or_else(|| default_cursor()) }
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
def_undocumented_cursor!(
|
||||
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
|
||||
fn _helpCursor();
|
||||
fn _zoomInCursor();
|
||||
fn _zoomOutCursor();
|
||||
fn _windowResizeNorthEastCursor();
|
||||
fn _windowResizeNorthWestCursor();
|
||||
fn _windowResizeSouthEastCursor();
|
||||
fn _windowResizeSouthWestCursor();
|
||||
fn _windowResizeNorthEastSouthWestCursor();
|
||||
fn _windowResizeNorthWestSouthEastCursor();
|
||||
|
||||
// While these two are available, the former just loads a white arrow,
|
||||
// and the latter loads an ugly deflated beachball!
|
||||
// pub fn _moveCursor();
|
||||
// pub fn _waitCursor();
|
||||
|
||||
// An even more undocumented cursor...
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
|
||||
fn busyButClickableCursor();
|
||||
);
|
||||
|
||||
// Note that loading `busybutclickable` with this code won't animate
|
||||
// the frames; instead you'll just get them all in a column.
|
||||
unsafe fn load_webkit_cursor(name: &NSString) -> Id<NSCursor> {
|
||||
// Snatch a cursor from WebKit; They fit the style of the native
|
||||
// cursors, and will seem completely standard to macOS users.
|
||||
//
|
||||
// https://stackoverflow.com/a/21786835/5435443
|
||||
let root = ns_string!("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors");
|
||||
let cursor_path = root.stringByAppendingPathComponent(name);
|
||||
|
||||
let pdf_path = cursor_path.stringByAppendingPathComponent(ns_string!("cursor.pdf"));
|
||||
let image = NSImage::initByReferencingFile(NSImage::alloc(), &pdf_path).unwrap();
|
||||
|
||||
// TODO: Handle PLists better
|
||||
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
|
||||
let info: Id<NSDictionary<NSObject, NSObject>> = unsafe {
|
||||
msg_send_id![
|
||||
<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()
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
let hotspot = NSPoint::new(x, y);
|
||||
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
|
||||
}
|
||||
|
||||
fn webkit_move() -> Id<NSCursor> {
|
||||
unsafe { load_webkit_cursor(ns_string!("move")) }
|
||||
}
|
||||
|
||||
fn webkit_cell() -> Id<NSCursor> {
|
||||
unsafe { load_webkit_cursor(ns_string!("cell")) }
|
||||
}
|
||||
|
||||
pub(crate) fn invisible_cursor() -> Id<NSCursor> {
|
||||
// 16x16 GIF data for invisible cursor
|
||||
// You can reproduce this via ImageMagick.
|
||||
// $ convert -size 16x16 xc:none cursor.gif
|
||||
static CURSOR_BYTES: &[u8] = &[
|
||||
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00,
|
||||
0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F,
|
||||
0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B,
|
||||
];
|
||||
|
||||
static CURSOR: Lazy<CustomCursor> = Lazy::new(|| {
|
||||
// TODO: Consider using `dataWithBytesNoCopy:`
|
||||
let data = NSData::with_bytes(CURSOR_BYTES);
|
||||
let image = NSImage::initWithData(NSImage::alloc(), &data).unwrap();
|
||||
let hotspot = NSPoint::new(0.0, 0.0);
|
||||
CustomCursor(NSCursor::initWithImage_hotSpot(
|
||||
NSCursor::alloc(),
|
||||
&image,
|
||||
hotspot,
|
||||
))
|
||||
});
|
||||
|
||||
CURSOR.0.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Id<NSCursor> {
|
||||
match icon {
|
||||
CursorIcon::Default => default_cursor(),
|
||||
CursorIcon::Pointer => NSCursor::pointingHandCursor(),
|
||||
CursorIcon::Grab => NSCursor::openHandCursor(),
|
||||
CursorIcon::Grabbing => NSCursor::closedHandCursor(),
|
||||
CursorIcon::Text => NSCursor::IBeamCursor(),
|
||||
CursorIcon::VerticalText => NSCursor::IBeamCursorForVerticalLayout(),
|
||||
CursorIcon::Copy => NSCursor::dragCopyCursor(),
|
||||
CursorIcon::Alias => NSCursor::dragLinkCursor(),
|
||||
CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(),
|
||||
CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(),
|
||||
CursorIcon::Crosshair => NSCursor::crosshairCursor(),
|
||||
CursorIcon::EResize => NSCursor::resizeRightCursor(),
|
||||
CursorIcon::NResize => NSCursor::resizeUpCursor(),
|
||||
CursorIcon::WResize => NSCursor::resizeLeftCursor(),
|
||||
CursorIcon::SResize => NSCursor::resizeDownCursor(),
|
||||
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
|
||||
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
|
||||
CursorIcon::Help => _helpCursor(),
|
||||
CursorIcon::ZoomIn => _zoomInCursor(),
|
||||
CursorIcon::ZoomOut => _zoomOutCursor(),
|
||||
CursorIcon::NeResize => _windowResizeNorthEastCursor(),
|
||||
CursorIcon::NwResize => _windowResizeNorthWestCursor(),
|
||||
CursorIcon::SeResize => _windowResizeSouthEastCursor(),
|
||||
CursorIcon::SwResize => _windowResizeSouthWestCursor(),
|
||||
CursorIcon::NeswResize => _windowResizeNorthEastSouthWestCursor(),
|
||||
CursorIcon::NwseResize => _windowResizeNorthWestSouthEastCursor(),
|
||||
// This is the wrong semantics for `Wait`, but it's the same as
|
||||
// what's used in Safari and Chrome.
|
||||
CursorIcon::Wait | CursorIcon::Progress => busyButClickableCursor(),
|
||||
CursorIcon::Move | CursorIcon::AllScroll => webkit_move(),
|
||||
CursorIcon::Cell => webkit_cell(),
|
||||
_ => default_cursor(),
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,15 @@ use core_foundation::{
|
||||
base::CFRelease,
|
||||
data::{CFDataGetBytePtr, CFDataRef},
|
||||
};
|
||||
use icrate::Foundation::MainThreadMarker;
|
||||
use icrate::AppKit::{
|
||||
NSEvent, NSEventModifierFlagCommand, NSEventModifierFlagControl, NSEventModifierFlagOption,
|
||||
NSEventModifierFlagShift, NSEventModifierFlags, NSEventSubtypeWindowExposed,
|
||||
NSEventTypeApplicationDefined,
|
||||
};
|
||||
use icrate::Foundation::{MainThreadMarker, NSPoint};
|
||||
use objc2::rc::Id;
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use super::appkit::{NSEvent, NSEventModifierFlags};
|
||||
use crate::{
|
||||
event::{ElementState, KeyEvent, Modifiers},
|
||||
keyboard::{
|
||||
@@ -88,7 +93,9 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
if result_len == 0 {
|
||||
log::error!("`UCKeyTranslate` was succesful but gave a string of 0 length.");
|
||||
// This is fine - not all keys have text representation.
|
||||
// For instance, users that have mapped the `Fn` key to toggle
|
||||
// keyboard layouts will hit this code path.
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
let chars = String::from_utf16_lossy(&string[0..result_len as usize]);
|
||||
@@ -96,8 +103,7 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
}
|
||||
|
||||
fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
|
||||
let string = ns_event
|
||||
.charactersIgnoringModifiers()
|
||||
let string = unsafe { ns_event.charactersIgnoringModifiers() }
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
if string.is_empty() {
|
||||
@@ -120,15 +126,14 @@ pub(crate) fn create_key_event(
|
||||
use ElementState::{Pressed, Released};
|
||||
let state = if is_press { Pressed } else { Released };
|
||||
|
||||
let scancode = ns_event.key_code();
|
||||
let scancode = unsafe { ns_event.keyCode() };
|
||||
let mut physical_key =
|
||||
key_override.unwrap_or_else(|| PhysicalKey::from_scancode(scancode as u32));
|
||||
|
||||
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
|
||||
None
|
||||
} else {
|
||||
let characters = ns_event
|
||||
.characters()
|
||||
let characters = unsafe { ns_event.characters() }
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
if characters.is_empty() {
|
||||
@@ -146,8 +151,8 @@ pub(crate) fn create_key_event(
|
||||
let (logical_key, key_without_modifiers) = if matches!(key_from_code, Key::Unidentified(_)) {
|
||||
let key_without_modifiers = get_modifierless_char(scancode);
|
||||
|
||||
let modifiers = NSEvent::modifierFlags(ns_event);
|
||||
let has_ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
let modifiers = unsafe { ns_event.modifierFlags() };
|
||||
let has_ctrl = flags_contains(modifiers, NSEventModifierFlagControl);
|
||||
|
||||
let logical_key = match text_with_all_modifiers.as_ref() {
|
||||
// Only checking for ctrl here, not checking for alt because we DO want to
|
||||
@@ -156,13 +161,11 @@ pub(crate) fn create_key_event(
|
||||
// Also not checking if this is a release event because then this issue would
|
||||
// still affect the key release.
|
||||
Some(text) if !has_ctrl => Key::Character(text.clone()),
|
||||
_ => {
|
||||
let modifierless_chars = match key_without_modifiers.as_ref() {
|
||||
Key::Character(ch) => ch,
|
||||
_ => "",
|
||||
};
|
||||
get_logical_key_char(ns_event, modifierless_chars)
|
||||
}
|
||||
_ => match key_without_modifiers.as_ref() {
|
||||
Key::Character(ch) => get_logical_key_char(ns_event, ch),
|
||||
// Don't try to get text for events which likely don't have it.
|
||||
_ => key_without_modifiers.clone(),
|
||||
},
|
||||
};
|
||||
|
||||
(logical_key, key_without_modifiers)
|
||||
@@ -311,42 +314,84 @@ pub fn extra_function_key_to_code(scancode: u16, string: &str) -> PhysicalKey {
|
||||
}
|
||||
}
|
||||
|
||||
// The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259
|
||||
const NX_DEVICELCTLKEYMASK: NSEventModifierFlags = 0x00000001;
|
||||
const NX_DEVICELSHIFTKEYMASK: NSEventModifierFlags = 0x00000002;
|
||||
const NX_DEVICERSHIFTKEYMASK: NSEventModifierFlags = 0x00000004;
|
||||
const NX_DEVICELCMDKEYMASK: NSEventModifierFlags = 0x00000008;
|
||||
const NX_DEVICERCMDKEYMASK: NSEventModifierFlags = 0x00000010;
|
||||
const NX_DEVICELALTKEYMASK: NSEventModifierFlags = 0x00000020;
|
||||
const NX_DEVICERALTKEYMASK: NSEventModifierFlags = 0x00000040;
|
||||
const NX_DEVICERCTLKEYMASK: NSEventModifierFlags = 0x00002000;
|
||||
|
||||
pub(super) fn flags_contains(flags: NSEventModifierFlags, value: NSEventModifierFlags) -> bool {
|
||||
flags & value == value
|
||||
}
|
||||
|
||||
pub(super) fn lalt_pressed(event: &NSEvent) -> bool {
|
||||
flags_contains(unsafe { event.modifierFlags() }, NX_DEVICELALTKEYMASK)
|
||||
}
|
||||
|
||||
pub(super) fn ralt_pressed(event: &NSEvent) -> bool {
|
||||
flags_contains(unsafe { event.modifierFlags() }, NX_DEVICERALTKEYMASK)
|
||||
}
|
||||
|
||||
pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
|
||||
let flags = event.modifierFlags();
|
||||
let flags = unsafe { event.modifierFlags() };
|
||||
let mut state = ModifiersState::empty();
|
||||
let mut pressed_mods = ModifiersKeys::empty();
|
||||
|
||||
state.set(
|
||||
ModifiersState::SHIFT,
|
||||
flags.contains(NSEventModifierFlags::NSShiftKeyMask),
|
||||
flags_contains(flags, NSEventModifierFlagShift),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::LSHIFT,
|
||||
flags_contains(flags, NX_DEVICELSHIFTKEYMASK),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::RSHIFT,
|
||||
flags_contains(flags, NX_DEVICERSHIFTKEYMASK),
|
||||
);
|
||||
|
||||
pressed_mods.set(ModifiersKeys::LSHIFT, event.lshift_pressed());
|
||||
pressed_mods.set(ModifiersKeys::RSHIFT, event.rshift_pressed());
|
||||
|
||||
state.set(
|
||||
ModifiersState::CONTROL,
|
||||
flags.contains(NSEventModifierFlags::NSControlKeyMask),
|
||||
flags_contains(flags, NSEventModifierFlagControl),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::LCONTROL,
|
||||
flags_contains(flags, NX_DEVICELCTLKEYMASK),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::RCONTROL,
|
||||
flags_contains(flags, NX_DEVICERCTLKEYMASK),
|
||||
);
|
||||
|
||||
pressed_mods.set(ModifiersKeys::LCONTROL, event.lctrl_pressed());
|
||||
pressed_mods.set(ModifiersKeys::RCONTROL, event.rctrl_pressed());
|
||||
|
||||
state.set(
|
||||
ModifiersState::ALT,
|
||||
flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
|
||||
flags_contains(flags, NSEventModifierFlagOption),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::LALT,
|
||||
flags_contains(flags, NX_DEVICELALTKEYMASK),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::RALT,
|
||||
flags_contains(flags, NX_DEVICERALTKEYMASK),
|
||||
);
|
||||
|
||||
pressed_mods.set(ModifiersKeys::LALT, event.lalt_pressed());
|
||||
pressed_mods.set(ModifiersKeys::RALT, event.ralt_pressed());
|
||||
|
||||
state.set(
|
||||
ModifiersState::SUPER,
|
||||
flags.contains(NSEventModifierFlags::NSCommandKeyMask),
|
||||
flags_contains(flags, NSEventModifierFlagCommand),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::LSUPER,
|
||||
flags_contains(flags, NX_DEVICELCMDKEYMASK),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::RSUPER,
|
||||
flags_contains(flags, NX_DEVICERCMDKEYMASK),
|
||||
);
|
||||
|
||||
pressed_mods.set(ModifiersKeys::LSUPER, event.lcmd_pressed());
|
||||
pressed_mods.set(ModifiersKeys::RSUPER, event.rcmd_pressed());
|
||||
|
||||
Modifiers {
|
||||
state,
|
||||
@@ -354,6 +399,22 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn dummy_event() -> Option<Id<NSEvent>> {
|
||||
unsafe {
|
||||
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
|
||||
NSEventTypeApplicationDefined,
|
||||
NSPoint::new(0.0, 0.0),
|
||||
0, // Empty NSEventModifierFlags
|
||||
0.0,
|
||||
0,
|
||||
None,
|
||||
NSEventSubtypeWindowExposed,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PhysicalKeyExtScancode for PhysicalKey {
|
||||
fn to_scancode(self) -> Option<u32> {
|
||||
let code = match self {
|
||||
|
||||
@@ -17,12 +17,18 @@ use core_foundation::runloop::{
|
||||
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
|
||||
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
use icrate::Foundation::MainThreadMarker;
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::runtime::NSObjectProtocol;
|
||||
use icrate::AppKit::{
|
||||
NSApplication, NSApplicationActivationPolicyAccessory, NSApplicationActivationPolicyProhibited,
|
||||
NSApplicationActivationPolicyRegular, NSWindow,
|
||||
};
|
||||
use icrate::Foundation::{MainThreadMarker, NSObjectProtocol};
|
||||
use objc2::{msg_send_id, ClassType};
|
||||
use objc2::{
|
||||
rc::{autoreleasepool, Id},
|
||||
runtime::ProtocolObject,
|
||||
};
|
||||
|
||||
use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent, NSWindow};
|
||||
use super::event::dummy_event;
|
||||
use crate::{
|
||||
error::EventLoopError,
|
||||
event::Event,
|
||||
@@ -123,19 +129,19 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
pub(crate) fn hide_application(&self) {
|
||||
NSApplication::shared(self.mtm).hide(None)
|
||||
NSApplication::sharedApplication(self.mtm).hide(None)
|
||||
}
|
||||
|
||||
pub(crate) fn hide_other_applications(&self) {
|
||||
NSApplication::shared(self.mtm).hideOtherApplications(None)
|
||||
NSApplication::sharedApplication(self.mtm).hideOtherApplications(None)
|
||||
}
|
||||
|
||||
pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
|
||||
NSWindow::setAllowsAutomaticWindowTabbing(enabled)
|
||||
NSWindow::setAllowsAutomaticWindowTabbing(enabled, self.mtm)
|
||||
}
|
||||
|
||||
pub(crate) fn allows_automatic_window_tabbing(&self) -> bool {
|
||||
NSWindow::allowsAutomaticWindowTabbing()
|
||||
NSWindow::allowsAutomaticWindowTabbing(self.mtm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,20 +202,20 @@ impl<T> EventLoop<T> {
|
||||
panic!("`winit` requires control over the principal class. You must create the event loop before other parts of your application initialize NSApplication");
|
||||
}
|
||||
|
||||
use NSApplicationActivationPolicy::*;
|
||||
let activation_policy = match attributes.activation_policy {
|
||||
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
|
||||
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
|
||||
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
|
||||
};
|
||||
let delegate = ApplicationDelegate::new(
|
||||
mtm,
|
||||
activation_policy,
|
||||
attributes.default_menu,
|
||||
attributes.activate_ignoring_other_apps,
|
||||
);
|
||||
|
||||
autoreleasepool(|_| {
|
||||
app.setDelegate(&delegate);
|
||||
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
||||
});
|
||||
|
||||
let panic_info: Rc<PanicInfo> = Default::default();
|
||||
@@ -307,7 +313,7 @@ impl<T> EventLoop<T> {
|
||||
|
||||
// While the app is running it's possible that we catch a panic
|
||||
// to avoid unwinding across an objective-c ffi boundary, which
|
||||
// will lead to us stopping the `NSApp` and saving the
|
||||
// will lead to us stopping the `NSApplication` and saving the
|
||||
// `PanicInfo` so that we can resume the unwind at a controlled,
|
||||
// safe point in time.
|
||||
if let Some(panic) = self.panic_info.take() {
|
||||
@@ -355,8 +361,6 @@ impl<T> EventLoop<T> {
|
||||
self._callback = Some(Rc::clone(&callback));
|
||||
|
||||
autoreleasepool(|_| {
|
||||
let app = NSApp();
|
||||
|
||||
// A bit of juggling with the callback references to make sure
|
||||
// that `self.callback` is the only owner of the callback.
|
||||
let weak_cb: Weak<_> = Rc::downgrade(&callback);
|
||||
@@ -377,25 +381,24 @@ impl<T> EventLoop<T> {
|
||||
// catch panics to make sure we can't unwind without clearing the set callback
|
||||
// (which would leave the global `AppState` in an undefined, unsafe state)
|
||||
let catch_result = catch_unwind(AssertUnwindSafe(|| {
|
||||
// As a special case, if the `NSApp` hasn't been launched yet then we at least run
|
||||
// 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 !AppState::is_launched() {
|
||||
debug_assert!(!AppState::is_running());
|
||||
|
||||
AppState::request_stop_on_launch();
|
||||
unsafe {
|
||||
app.run();
|
||||
self.app.run();
|
||||
}
|
||||
|
||||
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the `NSApp` has launched
|
||||
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application has launched
|
||||
} else if !AppState::is_running() {
|
||||
// Even though the NSApp may have been launched, it's possible we aren't 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.
|
||||
AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed`
|
||||
} else {
|
||||
// Only run the NSApp for as long as the given `Duration` allows so we
|
||||
// don't block the external loop.
|
||||
// Only run for as long as the given `Duration` allows so we don't block the external loop.
|
||||
match timeout {
|
||||
Some(Duration::ZERO) => {
|
||||
AppState::set_wait_timeout(None);
|
||||
@@ -415,13 +418,13 @@ impl<T> EventLoop<T> {
|
||||
}
|
||||
AppState::set_stop_app_on_redraw_requested(true);
|
||||
unsafe {
|
||||
app.run();
|
||||
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
|
||||
// will lead to us stopping the `NSApp` and saving the
|
||||
// will lead to us stopping the application and saving the
|
||||
// `PanicInfo` so that we can resume the unwind at a controlled,
|
||||
// safe point in time.
|
||||
if let Some(panic) = self.panic_info.take() {
|
||||
@@ -459,6 +462,7 @@ impl<T> EventLoop<T> {
|
||||
/// happens, stops the `sharedApplication`
|
||||
#[inline]
|
||||
pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
|
||||
mtm: MainThreadMarker,
|
||||
panic_info: Weak<PanicInfo>,
|
||||
f: F,
|
||||
) -> Option<R> {
|
||||
@@ -473,11 +477,11 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
|
||||
let panic_info = panic_info.upgrade().unwrap();
|
||||
panic_info.set_panic(e);
|
||||
}
|
||||
let app = NSApp();
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
app.stop(None);
|
||||
// Posting a dummy event to get `stop` to take effect immediately.
|
||||
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
|
||||
app.postEvent_atStart(&NSEvent::dummy(), true);
|
||||
app.postEvent_atStart(&dummy_event().unwrap(), true);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use core_graphics::{
|
||||
base::CGError,
|
||||
display::{CGDirectDisplayID, CGDisplayConfigRef},
|
||||
};
|
||||
use objc2::{ffi::NSInteger, runtime::AnyObject};
|
||||
|
||||
pub type CGDisplayFadeInterval = f32;
|
||||
pub type CGDisplayReservationInterval = f32;
|
||||
@@ -113,6 +114,14 @@ extern "C" {
|
||||
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(
|
||||
connection_id: *mut AnyObject,
|
||||
window_id: NSInteger,
|
||||
radius: i64,
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
mod core_video {
|
||||
@@ -201,3 +210,45 @@ 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,36 +1,49 @@
|
||||
use icrate::ns_string;
|
||||
use icrate::Foundation::{NSProcessInfo, NSString};
|
||||
use icrate::AppKit::{
|
||||
NSApplication, NSEventModifierFlagCommand, NSEventModifierFlagOption, NSEventModifierFlags,
|
||||
NSMenu, NSMenuItem,
|
||||
};
|
||||
use icrate::Foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::sel;
|
||||
|
||||
use super::appkit::{NSApp, NSEventModifierFlags, NSMenu, NSMenuItem};
|
||||
|
||||
struct KeyEquivalent<'a> {
|
||||
key: &'a NSString,
|
||||
masks: Option<NSEventModifierFlags>,
|
||||
}
|
||||
|
||||
pub fn initialize() {
|
||||
let menubar = NSMenu::new();
|
||||
let app_menu_item = NSMenuItem::new();
|
||||
pub fn initialize(app: &NSApplication) {
|
||||
let mtm = MainThreadMarker::from(app);
|
||||
let menubar = NSMenu::new(mtm);
|
||||
let app_menu_item = NSMenuItem::new(mtm);
|
||||
menubar.addItem(&app_menu_item);
|
||||
|
||||
let app_menu = NSMenu::new();
|
||||
let app_menu = NSMenu::new(mtm);
|
||||
let process_name = NSProcessInfo::processInfo().processName();
|
||||
|
||||
// About menu item
|
||||
let about_item_title = ns_string!("About ").stringByAppendingString(&process_name);
|
||||
let about_item = menu_item(&about_item_title, sel!(orderFrontStandardAboutPanel:), None);
|
||||
let about_item = menu_item(
|
||||
mtm,
|
||||
&about_item_title,
|
||||
Some(sel!(orderFrontStandardAboutPanel:)),
|
||||
None,
|
||||
);
|
||||
|
||||
// Services menu item
|
||||
let services_menu = NSMenu::new(mtm);
|
||||
let services_item = menu_item(mtm, ns_string!("Services"), None, None);
|
||||
services_item.setSubmenu(Some(&services_menu));
|
||||
|
||||
// Seperator menu item
|
||||
let sep_first = NSMenuItem::separatorItem();
|
||||
let sep_first = NSMenuItem::separatorItem(mtm);
|
||||
|
||||
// Hide application menu item
|
||||
let hide_item_title = ns_string!("Hide ").stringByAppendingString(&process_name);
|
||||
let hide_item = menu_item(
|
||||
mtm,
|
||||
&hide_item_title,
|
||||
sel!(hide:),
|
||||
Some(sel!(hide:)),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("h"),
|
||||
masks: None,
|
||||
@@ -40,28 +53,33 @@ pub fn initialize() {
|
||||
// Hide other applications menu item
|
||||
let hide_others_item_title = ns_string!("Hide Others");
|
||||
let hide_others_item = menu_item(
|
||||
mtm,
|
||||
hide_others_item_title,
|
||||
sel!(hideOtherApplications:),
|
||||
Some(sel!(hideOtherApplications:)),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("h"),
|
||||
masks: Some(
|
||||
NSEventModifierFlags::NSAlternateKeyMask | NSEventModifierFlags::NSCommandKeyMask,
|
||||
),
|
||||
masks: Some(NSEventModifierFlagOption | NSEventModifierFlagCommand),
|
||||
}),
|
||||
);
|
||||
|
||||
// Show applications menu item
|
||||
let show_all_item_title = ns_string!("Show All");
|
||||
let show_all_item = menu_item(show_all_item_title, sel!(unhideAllApplications:), None);
|
||||
let show_all_item = menu_item(
|
||||
mtm,
|
||||
show_all_item_title,
|
||||
Some(sel!(unhideAllApplications:)),
|
||||
None,
|
||||
);
|
||||
|
||||
// Seperator menu item
|
||||
let sep = NSMenuItem::separatorItem();
|
||||
let sep = NSMenuItem::separatorItem(mtm);
|
||||
|
||||
// Quit application menu item
|
||||
let quit_item_title = ns_string!("Quit ").stringByAppendingString(&process_name);
|
||||
let quit_item = menu_item(
|
||||
mtm,
|
||||
&quit_item_title,
|
||||
sel!(terminate:),
|
||||
Some(sel!(terminate:)),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("q"),
|
||||
masks: None,
|
||||
@@ -70,27 +88,31 @@ pub fn initialize() {
|
||||
|
||||
app_menu.addItem(&about_item);
|
||||
app_menu.addItem(&sep_first);
|
||||
app_menu.addItem(&services_item);
|
||||
app_menu.addItem(&hide_item);
|
||||
app_menu.addItem(&hide_others_item);
|
||||
app_menu.addItem(&show_all_item);
|
||||
app_menu.addItem(&sep);
|
||||
app_menu.addItem(&quit_item);
|
||||
app_menu_item.setSubmenu(&app_menu);
|
||||
app_menu_item.setSubmenu(Some(&app_menu));
|
||||
|
||||
let app = NSApp();
|
||||
app.setMainMenu(&menubar);
|
||||
unsafe { app.setServicesMenu(Some(&services_menu)) };
|
||||
app.setMainMenu(Some(&menubar));
|
||||
}
|
||||
|
||||
fn menu_item(
|
||||
mtm: MainThreadMarker,
|
||||
title: &NSString,
|
||||
selector: Sel,
|
||||
selector: Option<Sel>,
|
||||
key_equivalent: Option<KeyEquivalent<'_>>,
|
||||
) -> Id<NSMenuItem> {
|
||||
let (key, masks) = match key_equivalent {
|
||||
Some(ke) => (ke.key, ke.masks),
|
||||
None => (ns_string!(""), None),
|
||||
};
|
||||
let item = NSMenuItem::newWithTitle(title, selector, key);
|
||||
let item = unsafe {
|
||||
NSMenuItem::initWithTitle_action_keyEquivalent(mtm.alloc(), title, selector, key)
|
||||
};
|
||||
if let Some(masks) = masks {
|
||||
item.setKeyEquivalentModifierMask(masks)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ mod util;
|
||||
mod app;
|
||||
mod app_delegate;
|
||||
mod app_state;
|
||||
mod appkit;
|
||||
mod cursor;
|
||||
mod event;
|
||||
mod event_loop;
|
||||
mod ffi;
|
||||
@@ -27,7 +27,9 @@ pub(crate) use self::{
|
||||
};
|
||||
use crate::event::DeviceId as RootDeviceId;
|
||||
|
||||
pub(crate) use self::window::Window;
|
||||
pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use self::window::{OwnedWindowHandle, Window};
|
||||
pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
|
||||
@@ -7,10 +7,13 @@ use core_foundation::{
|
||||
base::{CFRelease, TCFType},
|
||||
string::CFString,
|
||||
};
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
|
||||
use objc2::rc::Id;
|
||||
use core_graphics::display::{
|
||||
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
|
||||
};
|
||||
use icrate::AppKit::NSScreen;
|
||||
use icrate::Foundation::{ns_string, MainThreadMarker, NSNumber};
|
||||
use objc2::{rc::Id, runtime::AnyObject};
|
||||
|
||||
use super::appkit::NSScreen;
|
||||
use super::ffi;
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
|
||||
@@ -208,14 +211,22 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
match self.ns_screen() {
|
||||
Some(screen) => screen.backingScaleFactor() as f64,
|
||||
None => 1.0, // default to 1.0 when we can't find the screen
|
||||
}
|
||||
MainThreadMarker::run_on_main(|mtm| {
|
||||
match self.ns_screen(mtm) {
|
||||
Some(screen) => screen.backingScaleFactor() as f64,
|
||||
None => 1.0, // default to 1.0 when we can't find the screen
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
unsafe {
|
||||
let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _);
|
||||
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0);
|
||||
if refresh_rate > 0.0 {
|
||||
return Some((refresh_rate * 1000.0).round() as u32);
|
||||
}
|
||||
|
||||
let mut display_link = std::ptr::null_mut();
|
||||
if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link)
|
||||
!= ffi::kCVReturnSuccess
|
||||
@@ -295,10 +306,10 @@ impl MonitorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ns_screen(&self) -> Option<Id<NSScreen>> {
|
||||
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Id<NSScreen>> {
|
||||
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
|
||||
NSScreen::screens().into_iter().find(|screen| {
|
||||
let other_native_id = screen.display_id();
|
||||
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)
|
||||
};
|
||||
@@ -306,3 +317,25 @@ impl MonitorHandle {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
|
||||
let key = ns_string!("NSScreenNumber");
|
||||
|
||||
objc2::rc::autoreleasepool(|_| {
|
||||
let device_description = screen.deviceDescription();
|
||||
|
||||
// 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:
|
||||
// <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 };
|
||||
|
||||
obj.as_u32()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ use core_foundation::runloop::{
|
||||
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
|
||||
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
|
||||
};
|
||||
use icrate::Foundation::MainThreadMarker;
|
||||
|
||||
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
|
||||
where
|
||||
@@ -36,7 +37,8 @@ where
|
||||
// However we want to keep that weak reference around after the function.
|
||||
std::mem::forget(info_from_raw);
|
||||
|
||||
stop_app_on_panic(Weak::clone(&panic_info), move || {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
stop_app_on_panic(mtm, Weak::clone(&panic_info), move || {
|
||||
let _ = &panic_info;
|
||||
f(panic_info.0)
|
||||
});
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::boxed::Box;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::ptr::NonNull;
|
||||
use std::ptr;
|
||||
|
||||
use icrate::Foundation::{
|
||||
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
|
||||
NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
|
||||
use icrate::AppKit::{
|
||||
NSApplication, NSCursor, NSEvent, NSEventPhaseBegan, NSEventPhaseCancelled,
|
||||
NSEventPhaseChanged, NSEventPhaseEnded, NSEventPhaseMayBegin, NSResponder, NSTextInputClient,
|
||||
NSTrackingRectTag, NSView,
|
||||
};
|
||||
use icrate::Foundation::{
|
||||
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
|
||||
NSMutableAttributedString, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize,
|
||||
NSString, NSUInteger,
|
||||
};
|
||||
use objc2::declare::{Ivar, IvarDrop};
|
||||
use objc2::rc::{Id, WeakId};
|
||||
use objc2::runtime::{AnyObject, Sel};
|
||||
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType};
|
||||
|
||||
use super::{
|
||||
appkit::{
|
||||
NSApp, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingRectTag,
|
||||
NSView,
|
||||
},
|
||||
event::{code_to_key, code_to_location},
|
||||
use objc2::{
|
||||
class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
|
||||
};
|
||||
|
||||
use super::cursor::{default_cursor, invisible_cursor};
|
||||
use super::event::{code_to_key, code_to_location};
|
||||
use super::event::{lalt_pressed, ralt_pressed};
|
||||
use crate::{
|
||||
dpi::{LogicalPosition, LogicalSize},
|
||||
event::{
|
||||
@@ -49,7 +51,7 @@ impl Default for CursorState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
visible: true,
|
||||
cursor: Default::default(),
|
||||
cursor: default_cursor(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,8 +76,8 @@ enum ImeState {
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
struct ModLocationMask: u8 {
|
||||
const LEFT = 1;
|
||||
const RIGHT = 2;
|
||||
const LEFT = 0b0001;
|
||||
const RIGHT = 0b0010;
|
||||
}
|
||||
}
|
||||
impl ModLocationMask {
|
||||
@@ -88,13 +90,13 @@ impl ModLocationMask {
|
||||
}
|
||||
}
|
||||
|
||||
fn key_to_modifier(key: &Key) -> ModifiersState {
|
||||
fn key_to_modifier(key: &Key) -> Option<ModifiersState> {
|
||||
match key {
|
||||
Key::Named(NamedKey::Alt) => ModifiersState::ALT,
|
||||
Key::Named(NamedKey::Control) => ModifiersState::CONTROL,
|
||||
Key::Named(NamedKey::Super) => ModifiersState::SUPER,
|
||||
Key::Named(NamedKey::Shift) => ModifiersState::SHIFT,
|
||||
_ => unreachable!(),
|
||||
Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT),
|
||||
Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL),
|
||||
Key::Named(NamedKey::Super) => Some(ModifiersState::SUPER),
|
||||
Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,93 +142,50 @@ pub struct ViewState {
|
||||
|
||||
marked_text: RefCell<Id<NSMutableAttributedString>>,
|
||||
accepts_first_mouse: bool,
|
||||
|
||||
// Weak reference because the window keeps a strong reference to the view
|
||||
_ns_window: WeakId<WinitWindow>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
pub(super) struct WinitView {
|
||||
// Weak reference because the window keeps a strong reference to the view
|
||||
_ns_window: IvarDrop<Box<WeakId<WinitWindow>>, "__ns_window">,
|
||||
state: IvarDrop<Box<ViewState>, "_state">,
|
||||
}
|
||||
|
||||
mod ivars;
|
||||
pub(super) struct WinitView;
|
||||
|
||||
unsafe impl ClassType for WinitView {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSView;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitView";
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(initWithId:acceptsFirstMouse:)]
|
||||
unsafe fn init_with_id(
|
||||
this: *mut Self,
|
||||
window: &WinitWindow,
|
||||
accepts_first_mouse: bool,
|
||||
) -> Option<NonNull<Self>> {
|
||||
let this: Option<&mut Self> = unsafe { msg_send![super(this), init] };
|
||||
this.map(|this| {
|
||||
let state = ViewState {
|
||||
accepts_first_mouse,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ivar::write(
|
||||
&mut this._ns_window,
|
||||
Box::new(WeakId::new(&window.retain())),
|
||||
);
|
||||
Ivar::write(&mut this.state, Box::new(state));
|
||||
|
||||
this.setPostsFrameChangedNotifications(true);
|
||||
|
||||
let notification_center: &AnyObject =
|
||||
unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] };
|
||||
// About frame change
|
||||
let frame_did_change_notification_name =
|
||||
NSString::from_str("NSViewFrameDidChangeNotification");
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
notification_center,
|
||||
addObserver: &*this,
|
||||
selector: sel!(frameDidChange:),
|
||||
name: &*frame_did_change_notification_name,
|
||||
object: &*this,
|
||||
];
|
||||
}
|
||||
|
||||
*this.state.input_source.borrow_mut() = this.current_input_source();
|
||||
NonNull::from(this)
|
||||
})
|
||||
}
|
||||
impl DeclaredClass for WinitView {
|
||||
type Ivars = ViewState;
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(viewDidMoveToWindow)]
|
||||
fn view_did_move_to_window(&self) {
|
||||
trace_scope!("viewDidMoveToWindow");
|
||||
if let Some(tracking_rect) = self.state.tracking_rect.take() {
|
||||
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
|
||||
self.removeTrackingRect(tracking_rect);
|
||||
}
|
||||
|
||||
let rect = self.frame();
|
||||
let tracking_rect = self.add_tracking_rect(rect, false);
|
||||
self.state.tracking_rect.set(Some(tracking_rect));
|
||||
let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) };
|
||||
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
|
||||
self.ivars().tracking_rect.set(Some(tracking_rect));
|
||||
}
|
||||
|
||||
#[method(frameDidChange:)]
|
||||
fn frame_did_change(&self, _event: &NSEvent) {
|
||||
trace_scope!("frameDidChange:");
|
||||
if let Some(tracking_rect) = self.state.tracking_rect.take() {
|
||||
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
|
||||
self.removeTrackingRect(tracking_rect);
|
||||
}
|
||||
|
||||
let rect = self.frame();
|
||||
let tracking_rect = self.add_tracking_rect(rect, false);
|
||||
self.state.tracking_rect.set(Some(tracking_rect));
|
||||
let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) };
|
||||
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
|
||||
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.
|
||||
@@ -241,7 +200,7 @@ declare_class!(
|
||||
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._ns_window.load() {
|
||||
if let Some(window) = self.ivars()._ns_window.load() {
|
||||
AppState::handle_redraw(WindowId(window.id()));
|
||||
}
|
||||
|
||||
@@ -270,12 +229,12 @@ declare_class!(
|
||||
fn reset_cursor_rects(&self) {
|
||||
trace_scope!("resetCursorRects");
|
||||
let bounds = self.bounds();
|
||||
let cursor_state = self.state.cursor_state.borrow();
|
||||
let cursor_state = self.ivars().cursor_state.borrow();
|
||||
// We correctly invoke `addCursorRect` only from inside `resetCursorRects`
|
||||
if cursor_state.visible {
|
||||
self.addCursorRect(bounds, &cursor_state.cursor);
|
||||
self.addCursorRect_cursor(bounds, &cursor_state.cursor);
|
||||
} else {
|
||||
self.addCursorRect(bounds, &NSCursor::invisible());
|
||||
self.addCursorRect_cursor(bounds, &invisible_cursor());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,13 +243,13 @@ declare_class!(
|
||||
#[method(hasMarkedText)]
|
||||
fn has_marked_text(&self) -> bool {
|
||||
trace_scope!("hasMarkedText");
|
||||
self.state.marked_text.borrow().length() > 0
|
||||
self.ivars().marked_text.borrow().length() > 0
|
||||
}
|
||||
|
||||
#[method(markedRange)]
|
||||
fn marked_range(&self) -> NSRange {
|
||||
trace_scope!("markedRange");
|
||||
let length = self.state.marked_text.borrow().length();
|
||||
let length = self.ivars().marked_text.borrow().length();
|
||||
if length > 0 {
|
||||
NSRange::new(0, length)
|
||||
} else {
|
||||
@@ -333,19 +292,19 @@ declare_class!(
|
||||
};
|
||||
|
||||
// Update marked text.
|
||||
*self.state.marked_text.borrow_mut() = marked_text;
|
||||
*self.ivars().marked_text.borrow_mut() = marked_text;
|
||||
|
||||
// Notify IME is active if application still doesn't know it.
|
||||
if self.state.ime_state.get() == ImeState::Disabled {
|
||||
*self.state.input_source.borrow_mut() = self.current_input_source();
|
||||
if self.ivars().ime_state.get() == ImeState::Disabled {
|
||||
*self.ivars().input_source.borrow_mut() = self.current_input_source();
|
||||
self.queue_event(WindowEvent::Ime(Ime::Enabled));
|
||||
}
|
||||
|
||||
if self.hasMarkedText() {
|
||||
self.state.ime_state.set(ImeState::Preedit);
|
||||
if unsafe { self.hasMarkedText() } {
|
||||
self.ivars().ime_state.set(ImeState::Preedit);
|
||||
} else {
|
||||
// In case the preedit was cleared, set IME into the Ground state.
|
||||
self.state.ime_state.set(ImeState::Ground);
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
}
|
||||
|
||||
// Empty string basically means that there's no preedit, so indicate that by sending
|
||||
@@ -363,15 +322,15 @@ declare_class!(
|
||||
#[method(unmarkText)]
|
||||
fn unmark_text(&self) {
|
||||
trace_scope!("unmarkText");
|
||||
*self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
|
||||
let input_context = self.inputContext().expect("input context");
|
||||
input_context.discardMarkedText();
|
||||
|
||||
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
|
||||
if self.is_ime_enabled() {
|
||||
// Leave the Preedit self.state
|
||||
self.state.ime_state.set(ImeState::Ground);
|
||||
// Leave the Preedit self.ivars()
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
} else {
|
||||
warn!("Expected to have IME enabled when receiving unmarkText");
|
||||
}
|
||||
@@ -410,9 +369,9 @@ declare_class!(
|
||||
let content_rect = window.contentRectForFrameRect(window.frame());
|
||||
let base_x = content_rect.origin.x as f64;
|
||||
let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
|
||||
let x = base_x + self.state.ime_position.get().x;
|
||||
let y = base_y - self.state.ime_position.get().y;
|
||||
let LogicalSize { width, height } = self.state.ime_size.get();
|
||||
let x = base_x + self.ivars().ime_position.get().x;
|
||||
let y = base_y - self.ivars().ime_position.get().y;
|
||||
let LogicalSize { width, height } = self.ivars().ime_size.get();
|
||||
NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height))
|
||||
}
|
||||
|
||||
@@ -434,10 +393,10 @@ declare_class!(
|
||||
let is_control = string.chars().next().map_or(false, |c| c.is_control());
|
||||
|
||||
// Commit only if we have marked text.
|
||||
if self.hasMarkedText() && self.is_ime_enabled() && !is_control {
|
||||
if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control {
|
||||
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
|
||||
self.queue_event(WindowEvent::Ime(Ime::Commit(string)));
|
||||
self.state.ime_state.set(ImeState::Commited);
|
||||
self.ivars().ime_state.set(ImeState::Commited);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,15 +408,15 @@ declare_class!(
|
||||
// We shouldn't forward any character from just commited 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.state.ime_state.get() == ImeState::Commited {
|
||||
if self.ivars().ime_state.get() == ImeState::Commited {
|
||||
return;
|
||||
}
|
||||
|
||||
self.state.forward_key_to_app.set(true);
|
||||
self.ivars().forward_key_to_app.set(true);
|
||||
|
||||
if self.hasMarkedText() && self.state.ime_state.get() == ImeState::Preedit {
|
||||
if unsafe { self.hasMarkedText() } && self.ivars().ime_state.get() == ImeState::Preedit {
|
||||
// Leave preedit so that we also report the key-up for this key.
|
||||
self.state.ime_state.set(ImeState::Ground);
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -467,19 +426,19 @@ declare_class!(
|
||||
fn key_down(&self, event: &NSEvent) {
|
||||
trace_scope!("keyDown:");
|
||||
{
|
||||
let mut prev_input_source = self.state.input_source.borrow_mut();
|
||||
let mut prev_input_source = self.ivars().input_source.borrow_mut();
|
||||
let current_input_source = self.current_input_source();
|
||||
if *prev_input_source != current_input_source && self.is_ime_enabled() {
|
||||
*prev_input_source = current_input_source;
|
||||
drop(prev_input_source);
|
||||
self.state.ime_state.set(ImeState::Disabled);
|
||||
self.ivars().ime_state.set(ImeState::Disabled);
|
||||
self.queue_event(WindowEvent::Ime(Ime::Disabled));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the characters from the event.
|
||||
let old_ime_state = self.state.ime_state.get();
|
||||
self.state.forward_key_to_app.set(false);
|
||||
let old_ime_state = self.ivars().ime_state.get();
|
||||
self.ivars().forward_key_to_app.set(false);
|
||||
let event = replace_event(event, self.window().option_as_alt());
|
||||
|
||||
// The `interpretKeyEvents` function might call
|
||||
@@ -488,32 +447,32 @@ declare_class!(
|
||||
// we must send the `KeyboardInput` event during IME if it triggered
|
||||
// `doCommandBySelector`. (doCommandBySelector means that the keyboard input
|
||||
// is not handled by IME and should be handled by the application)
|
||||
if self.state.ime_allowed.get() {
|
||||
if self.ivars().ime_allowed.get() {
|
||||
let events_for_nsview = NSArray::from_slice(&[&*event]);
|
||||
unsafe { self.interpretKeyEvents(&events_for_nsview) };
|
||||
|
||||
// If the text was commited we must treat the next keyboard event as IME related.
|
||||
if self.state.ime_state.get() == ImeState::Commited {
|
||||
if self.ivars().ime_state.get() == ImeState::Commited {
|
||||
// Remove any marked text, so normal input can continue.
|
||||
*self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
}
|
||||
}
|
||||
|
||||
self.update_modifiers(&event, false);
|
||||
|
||||
let had_ime_input = match self.state.ime_state.get() {
|
||||
let had_ime_input = match self.ivars().ime_state.get() {
|
||||
ImeState::Commited => {
|
||||
// Allow normal input after the commit.
|
||||
self.state.ime_state.set(ImeState::Ground);
|
||||
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.state.ime_state.get(),
|
||||
_ => old_ime_state != self.ivars().ime_state.get(),
|
||||
};
|
||||
|
||||
if !had_ime_input || self.state.forward_key_to_app.get() {
|
||||
let key_event = create_key_event(&event, true, event.is_a_repeat(), None);
|
||||
if !had_ime_input || self.ivars().forward_key_to_app.get() {
|
||||
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event: key_event,
|
||||
@@ -531,7 +490,7 @@ declare_class!(
|
||||
|
||||
// We want to send keyboard input when we are currently in the ground state.
|
||||
if matches!(
|
||||
self.state.ime_state.get(),
|
||||
self.ivars().ime_state.get(),
|
||||
ImeState::Ground | ImeState::Disabled
|
||||
) {
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
@@ -575,14 +534,15 @@ declare_class!(
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
|
||||
#[method(cancelOperation:)]
|
||||
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
trace_scope!("cancelOperation:");
|
||||
|
||||
let event = NSApp()
|
||||
let event = NSApplication::sharedApplication(mtm)
|
||||
.currentEvent()
|
||||
.expect("could not find current event");
|
||||
|
||||
self.update_modifiers(&event, false);
|
||||
let event = create_key_event(&event, true, event.is_a_repeat(), None);
|
||||
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
|
||||
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
@@ -679,8 +639,8 @@ declare_class!(
|
||||
self.mouse_motion(event);
|
||||
|
||||
let delta = {
|
||||
let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY());
|
||||
if event.hasPreciseScrollingDeltas() {
|
||||
let (x, y) = unsafe { (event.scrollingDeltaX(), event.scrollingDeltaY()) };
|
||||
if unsafe { event.hasPreciseScrollingDeltas() } {
|
||||
let delta = LogicalPosition::new(x, y).to_physical(self.scale_factor());
|
||||
MouseScrollDelta::PixelDelta(delta)
|
||||
} else {
|
||||
@@ -692,18 +652,19 @@ declare_class!(
|
||||
// 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.
|
||||
let phase = match event.momentumPhase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
|
||||
#[allow(non_upper_case_globals)]
|
||||
let phase = match unsafe { event.momentumPhase() } {
|
||||
NSEventPhaseMayBegin | NSEventPhaseBegan => {
|
||||
TouchPhase::Started
|
||||
}
|
||||
NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => {
|
||||
NSEventPhaseEnded | NSEventPhaseCancelled => {
|
||||
TouchPhase::Ended
|
||||
}
|
||||
_ => match event.phase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
|
||||
_ => match unsafe { event.phase() } {
|
||||
NSEventPhaseMayBegin | NSEventPhaseBegan => {
|
||||
TouchPhase::Started
|
||||
}
|
||||
NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => {
|
||||
NSEventPhaseEnded | NSEventPhaseCancelled => {
|
||||
TouchPhase::Ended
|
||||
}
|
||||
_ => TouchPhase::Moved,
|
||||
@@ -724,17 +685,18 @@ declare_class!(
|
||||
fn magnify_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("magnifyWithEvent:");
|
||||
|
||||
let phase = match event.phase() {
|
||||
NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
|
||||
NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved,
|
||||
NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled,
|
||||
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
|
||||
#[allow(non_upper_case_globals)]
|
||||
let phase = match unsafe { event.phase() } {
|
||||
NSEventPhaseBegan => TouchPhase::Started,
|
||||
NSEventPhaseChanged => TouchPhase::Moved,
|
||||
NSEventPhaseCancelled => TouchPhase::Cancelled,
|
||||
NSEventPhaseEnded => TouchPhase::Ended,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
self.queue_event(WindowEvent::TouchpadMagnify {
|
||||
device_id: DEVICE_ID,
|
||||
delta: event.magnification(),
|
||||
delta: unsafe { event.magnification() },
|
||||
phase,
|
||||
});
|
||||
}
|
||||
@@ -752,17 +714,18 @@ declare_class!(
|
||||
fn rotate_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("rotateWithEvent:");
|
||||
|
||||
let phase = match event.phase() {
|
||||
NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
|
||||
NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved,
|
||||
NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled,
|
||||
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
|
||||
#[allow(non_upper_case_globals)]
|
||||
let phase = match unsafe { event.phase() } {
|
||||
NSEventPhaseBegan => TouchPhase::Started,
|
||||
NSEventPhaseChanged => TouchPhase::Moved,
|
||||
NSEventPhaseCancelled => TouchPhase::Cancelled,
|
||||
NSEventPhaseEnded => TouchPhase::Ended,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
self.queue_event(WindowEvent::TouchpadRotate {
|
||||
device_id: DEVICE_ID,
|
||||
delta: event.rotation(),
|
||||
delta: unsafe { event.rotation() },
|
||||
phase,
|
||||
});
|
||||
}
|
||||
@@ -775,8 +738,8 @@ declare_class!(
|
||||
|
||||
self.queue_event(WindowEvent::TouchpadPressure {
|
||||
device_id: DEVICE_ID,
|
||||
pressure: event.pressure(),
|
||||
stage: event.stage() as i64,
|
||||
pressure: unsafe { event.pressure() },
|
||||
stage: unsafe { event.stage() } as i64,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -792,20 +755,41 @@ declare_class!(
|
||||
#[method(acceptsFirstMouse:)]
|
||||
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
|
||||
trace_scope!("acceptsFirstMouse:");
|
||||
self.state.accepts_first_mouse
|
||||
self.ivars().accepts_first_mouse
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitView {
|
||||
pub(super) fn new(window: &WinitWindow, accepts_first_mouse: bool) -> Id<Self> {
|
||||
let mtm = MainThreadMarker::from(window);
|
||||
let this = mtm.alloc().set_ivars(ViewState {
|
||||
accepts_first_mouse,
|
||||
_ns_window: WeakId::new(&window.retain()),
|
||||
..Default::default()
|
||||
});
|
||||
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
|
||||
this.setPostsFrameChangedNotifications(true);
|
||||
let notification_center: &AnyObject =
|
||||
unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] };
|
||||
// About frame change
|
||||
let frame_did_change_notification_name =
|
||||
NSString::from_str("NSViewFrameDidChangeNotification");
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
Self::alloc(),
|
||||
initWithId: window,
|
||||
acceptsFirstMouse: accepts_first_mouse,
|
||||
]
|
||||
let _: () = msg_send![
|
||||
notification_center,
|
||||
addObserver: &*this,
|
||||
selector: sel!(frameDidChange:),
|
||||
name: &*frame_did_change_notification_name,
|
||||
object: &*this,
|
||||
];
|
||||
}
|
||||
|
||||
*this.ivars().input_source.borrow_mut() = this.current_input_source();
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn window(&self) -> Id<WinitWindow> {
|
||||
@@ -814,7 +798,10 @@ impl WinitView {
|
||||
// (which is incompatible with `frameDidChange:`)
|
||||
//
|
||||
// unsafe { msg_send_id![self, window] }
|
||||
self._ns_window.load().expect("view to have a window")
|
||||
self.ivars()
|
||||
._ns_window
|
||||
.load()
|
||||
.expect("view to have a window")
|
||||
}
|
||||
|
||||
fn window_id(&self) -> WindowId {
|
||||
@@ -842,7 +829,7 @@ impl WinitView {
|
||||
}
|
||||
|
||||
fn is_ime_enabled(&self) -> bool {
|
||||
!matches!(self.state.ime_state.get(), ImeState::Disabled)
|
||||
!matches!(self.ivars().ime_state.get(), ImeState::Disabled)
|
||||
}
|
||||
|
||||
fn current_input_source(&self) -> String {
|
||||
@@ -854,7 +841,7 @@ impl WinitView {
|
||||
}
|
||||
|
||||
pub(super) fn set_cursor_icon(&self, icon: Id<NSCursor>) {
|
||||
let mut cursor_state = self.state.cursor_state.borrow_mut();
|
||||
let mut cursor_state = self.ivars().cursor_state.borrow_mut();
|
||||
cursor_state.cursor = icon;
|
||||
}
|
||||
|
||||
@@ -862,7 +849,7 @@ impl WinitView {
|
||||
///
|
||||
/// Returns whether the state changed.
|
||||
pub(super) fn set_cursor_visible(&self, visible: bool) -> bool {
|
||||
let mut cursor_state = self.state.cursor_state.borrow_mut();
|
||||
let mut cursor_state = self.ivars().cursor_state.borrow_mut();
|
||||
if visible != cursor_state.visible {
|
||||
cursor_state.visible = visible;
|
||||
true
|
||||
@@ -872,19 +859,19 @@ impl WinitView {
|
||||
}
|
||||
|
||||
pub(super) fn set_ime_allowed(&self, ime_allowed: bool) {
|
||||
if self.state.ime_allowed.get() == ime_allowed {
|
||||
if self.ivars().ime_allowed.get() == ime_allowed {
|
||||
return;
|
||||
}
|
||||
self.state.ime_allowed.set(ime_allowed);
|
||||
if self.state.ime_allowed.get() {
|
||||
self.ivars().ime_allowed.set(ime_allowed);
|
||||
if self.ivars().ime_allowed.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear markedText
|
||||
*self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
|
||||
if self.state.ime_state.get() != ImeState::Disabled {
|
||||
self.state.ime_state.set(ImeState::Disabled);
|
||||
if self.ivars().ime_state.get() != ImeState::Disabled {
|
||||
self.ivars().ime_state.set(ImeState::Disabled);
|
||||
self.queue_event(WindowEvent::Ime(Ime::Disabled));
|
||||
}
|
||||
}
|
||||
@@ -894,17 +881,17 @@ impl WinitView {
|
||||
position: LogicalPosition<f64>,
|
||||
size: LogicalSize<f64>,
|
||||
) {
|
||||
self.state.ime_position.set(position);
|
||||
self.state.ime_size.set(size);
|
||||
self.ivars().ime_position.set(position);
|
||||
self.ivars().ime_size.set(size);
|
||||
let input_context = self.inputContext().expect("input context");
|
||||
input_context.invalidateCharacterCoordinates();
|
||||
}
|
||||
|
||||
/// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary.
|
||||
pub(super) fn reset_modifiers(&self) {
|
||||
if !self.state.modifiers.get().state().is_empty() {
|
||||
self.state.modifiers.set(Modifiers::default());
|
||||
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get()));
|
||||
if !self.ivars().modifiers.get().state().is_empty() {
|
||||
self.ivars().modifiers.set(Modifiers::default());
|
||||
self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,8 +900,8 @@ impl WinitView {
|
||||
use ElementState::{Pressed, Released};
|
||||
|
||||
let current_modifiers = event_mods(ns_event);
|
||||
let prev_modifiers = self.state.modifiers.get();
|
||||
self.state.modifiers.set(current_modifiers);
|
||||
let prev_modifiers = self.ivars().modifiers.get();
|
||||
self.ivars().modifiers.set(current_modifiers);
|
||||
|
||||
// This function was called form the flagsChanged event, which is triggered
|
||||
// when the user presses/releases a modifier even if the same kind of modifier
|
||||
@@ -924,91 +911,96 @@ impl WinitView {
|
||||
// event, thus we can't generate regular presses based on that. The `ModifiersChanged`
|
||||
// later will work though, since the flags are attached to the event and contain valid
|
||||
// information.
|
||||
if is_flags_changed_event && ns_event.key_code() != 0 {
|
||||
let scancode = ns_event.key_code();
|
||||
let physical_key = PhysicalKey::from_scancode(scancode as u32);
|
||||
'send_event: {
|
||||
if is_flags_changed_event && unsafe { ns_event.keyCode() } != 0 {
|
||||
let scancode = unsafe { ns_event.keyCode() };
|
||||
let physical_key = PhysicalKey::from_scancode(scancode as u32);
|
||||
|
||||
// We'll correct the `is_press` later.
|
||||
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
|
||||
// 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 event_modifier = key_to_modifier(&key);
|
||||
event.physical_key = physical_key;
|
||||
event.logical_key = key.clone();
|
||||
event.location = code_to_location(physical_key);
|
||||
let location_mask = ModLocationMask::from_location(event.location);
|
||||
let key = code_to_key(physical_key, scancode);
|
||||
// Ignore processing of unkown modifiers because we can't determine whether
|
||||
// it was pressed or release reliably.
|
||||
let Some(event_modifier) = key_to_modifier(&key) else {
|
||||
break 'send_event;
|
||||
};
|
||||
event.physical_key = physical_key;
|
||||
event.logical_key = key.clone();
|
||||
event.location = code_to_location(physical_key);
|
||||
let location_mask = ModLocationMask::from_location(event.location);
|
||||
|
||||
let mut phys_mod_state = self.state.phys_modifiers.borrow_mut();
|
||||
let phys_mod = phys_mod_state
|
||||
.entry(key)
|
||||
.or_insert(ModLocationMask::empty());
|
||||
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
|
||||
let phys_mod = phys_mod_state
|
||||
.entry(key)
|
||||
.or_insert(ModLocationMask::empty());
|
||||
|
||||
let is_active = current_modifiers.state().contains(event_modifier);
|
||||
let mut events = VecDeque::with_capacity(2);
|
||||
let is_active = current_modifiers.state().contains(event_modifier);
|
||||
let mut events = VecDeque::with_capacity(2);
|
||||
|
||||
// There is no API for getting whether the button was pressed or released
|
||||
// during this event. For this reason we have to do a bit of magic below
|
||||
// to come up with a good guess whether this key was pressed or released.
|
||||
// (This is not trivial because there are multiple buttons that may affect
|
||||
// the same modifier)
|
||||
if !is_active {
|
||||
event.state = Released;
|
||||
if phys_mod.contains(ModLocationMask::LEFT) {
|
||||
let mut event = event.clone();
|
||||
event.location = KeyLocation::Left;
|
||||
event.physical_key = get_left_modifier_code(&event.logical_key).into();
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
if phys_mod.contains(ModLocationMask::RIGHT) {
|
||||
event.location = KeyLocation::Right;
|
||||
event.physical_key = get_right_modifier_code(&event.logical_key).into();
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
*phys_mod = ModLocationMask::empty();
|
||||
} else {
|
||||
// is_active
|
||||
if *phys_mod == location_mask {
|
||||
// Here we hit a contradiction:
|
||||
// The modifier state was "changed" to active,
|
||||
// yet the only pressed modifier key was the one that we
|
||||
// just got a change event for.
|
||||
// This seemingly means that the only pressed modifier is now released,
|
||||
// but at the same time the modifier became active.
|
||||
//
|
||||
// But this scenario is possible if we released modifiers
|
||||
// while the application was not in focus. (Because we don't
|
||||
// get informed of modifier key events while the application
|
||||
// is not focused)
|
||||
|
||||
// In this case we prioritize the information
|
||||
// about the current modifier state which means
|
||||
// that the button was pressed.
|
||||
event.state = Pressed;
|
||||
// There is no API for getting whether the button was pressed or released
|
||||
// during this event. For this reason we have to do a bit of magic below
|
||||
// to come up with a good guess whether this key was pressed or released.
|
||||
// (This is not trivial because there are multiple buttons that may affect
|
||||
// the same modifier)
|
||||
if !is_active {
|
||||
event.state = Released;
|
||||
if phys_mod.contains(ModLocationMask::LEFT) {
|
||||
let mut event = event.clone();
|
||||
event.location = KeyLocation::Left;
|
||||
event.physical_key = get_left_modifier_code(&event.logical_key).into();
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
if phys_mod.contains(ModLocationMask::RIGHT) {
|
||||
event.location = KeyLocation::Right;
|
||||
event.physical_key = get_right_modifier_code(&event.logical_key).into();
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
*phys_mod = ModLocationMask::empty();
|
||||
} else {
|
||||
phys_mod.toggle(location_mask);
|
||||
let is_pressed = phys_mod.contains(location_mask);
|
||||
event.state = if is_pressed { Pressed } else { Released };
|
||||
if *phys_mod == location_mask {
|
||||
// Here we hit a contradiction:
|
||||
// The modifier state was "changed" to active,
|
||||
// yet the only pressed modifier key was the one that we
|
||||
// just got a change event for.
|
||||
// This seemingly means that the only pressed modifier is now released,
|
||||
// but at the same time the modifier became active.
|
||||
//
|
||||
// But this scenario is possible if we released modifiers
|
||||
// while the application was not in focus. (Because we don't
|
||||
// get informed of modifier key events while the application
|
||||
// is not focused)
|
||||
|
||||
// In this case we prioritize the information
|
||||
// about the current modifier state which means
|
||||
// that the button was pressed.
|
||||
event.state = Pressed;
|
||||
} else {
|
||||
phys_mod.toggle(location_mask);
|
||||
let is_pressed = phys_mod.contains(location_mask);
|
||||
event.state = if is_pressed { Pressed } else { Released };
|
||||
}
|
||||
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
drop(phys_mod_state);
|
||||
|
||||
drop(phys_mod_state);
|
||||
|
||||
for event in events {
|
||||
self.queue_event(event);
|
||||
for event in events {
|
||||
self.queue_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1016,7 +1008,7 @@ impl WinitView {
|
||||
return;
|
||||
}
|
||||
|
||||
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get()));
|
||||
self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get()));
|
||||
}
|
||||
|
||||
fn mouse_click(&self, event: &NSEvent, button_state: ElementState) {
|
||||
@@ -1032,7 +1024,7 @@ impl WinitView {
|
||||
}
|
||||
|
||||
fn mouse_motion(&self, event: &NSEvent) {
|
||||
let window_point = event.locationInWindow();
|
||||
let window_point = unsafe { event.locationInWindow() };
|
||||
let view_point = self.convertPoint_fromView(window_point, None);
|
||||
let view_rect = self.frame();
|
||||
|
||||
@@ -1041,7 +1033,7 @@ impl WinitView {
|
||||
|| view_point.x > view_rect.size.width
|
||||
|| view_point.y > view_rect.size.height
|
||||
{
|
||||
let mouse_buttons_down = NSEvent::pressedMouseButtons();
|
||||
let mouse_buttons_down = unsafe { NSEvent::pressedMouseButtons() };
|
||||
if mouse_buttons_down == 0 {
|
||||
// Point is outside of the client area (view) and no buttons are pressed
|
||||
return;
|
||||
@@ -1068,7 +1060,7 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
|
||||
// For the other events, it's always set to 0.
|
||||
// MacOS only defines the left, right and middle buttons, 3..=31 are left as generic buttons,
|
||||
// but 3 and 4 are very commonly used as Back and Forward by hardware vendors and applications.
|
||||
match event.buttonNumber() {
|
||||
match unsafe { event.buttonNumber() } {
|
||||
0 => MouseButton::Left,
|
||||
1 => MouseButton::Right,
|
||||
2 => MouseButton::Middle,
|
||||
@@ -1084,30 +1076,35 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
|
||||
fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Id<NSEvent> {
|
||||
let ev_mods = event_mods(event).state;
|
||||
let ignore_alt_characters = match option_as_alt {
|
||||
OptionAsAlt::OnlyLeft if event.lalt_pressed() => true,
|
||||
OptionAsAlt::OnlyRight if event.ralt_pressed() => true,
|
||||
OptionAsAlt::OnlyLeft if lalt_pressed(event) => true,
|
||||
OptionAsAlt::OnlyRight if ralt_pressed(event) => true,
|
||||
OptionAsAlt::Both if ev_mods.alt_key() => true,
|
||||
_ => false,
|
||||
} && !ev_mods.control_key()
|
||||
&& !ev_mods.super_key();
|
||||
|
||||
if ignore_alt_characters {
|
||||
let ns_chars = event
|
||||
.charactersIgnoringModifiers()
|
||||
.expect("expected characters to be non-null");
|
||||
let ns_chars = unsafe {
|
||||
event
|
||||
.charactersIgnoringModifiers()
|
||||
.expect("expected characters to be non-null")
|
||||
};
|
||||
|
||||
NSEvent::keyEventWithType(
|
||||
event.type_(),
|
||||
event.locationInWindow(),
|
||||
event.modifierFlags(),
|
||||
event.timestamp(),
|
||||
event.window_number(),
|
||||
None,
|
||||
&ns_chars,
|
||||
&ns_chars,
|
||||
event.is_a_repeat(),
|
||||
event.key_code(),
|
||||
)
|
||||
unsafe {
|
||||
NSEvent::keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode(
|
||||
event.r#type(),
|
||||
event.locationInWindow(),
|
||||
event.modifierFlags(),
|
||||
event.timestamp(),
|
||||
event.windowNumber(),
|
||||
None,
|
||||
&ns_chars,
|
||||
&ns_chars,
|
||||
event.isARepeat(),
|
||||
event.keyCode(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
} else {
|
||||
event.copy()
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::f64;
|
||||
use std::ops;
|
||||
use std::os::raw::c_void;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
use crate::{
|
||||
@@ -17,14 +15,13 @@ use crate::{
|
||||
platform::macos::{OptionAsAlt, WindowExtMacOS},
|
||||
platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
appkit::NSWindowOrderingMode,
|
||||
event_loop::EventLoopWindowTarget,
|
||||
ffi,
|
||||
monitor::{self, MonitorHandle, VideoMode},
|
||||
util,
|
||||
view::WinitView,
|
||||
window_delegate::WinitWindowDelegate,
|
||||
Fullscreen, OsError,
|
||||
Fullscreen, OsError, PlatformCustomCursor,
|
||||
},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
@@ -32,20 +29,30 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use core_graphics::display::{CGDisplay, CGPoint};
|
||||
use icrate::AppKit::{
|
||||
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSApplication,
|
||||
NSApplicationPresentationAutoHideDock, NSApplicationPresentationAutoHideMenuBar,
|
||||
NSApplicationPresentationFullScreen, NSApplicationPresentationHideDock,
|
||||
NSApplicationPresentationHideMenuBar, NSApplicationPresentationOptions, NSBackingStoreBuffered,
|
||||
NSColor, NSCriticalRequest, NSFilenamesPboardType, NSInformationalRequest, NSResponder,
|
||||
NSScreen, NSView, NSWindow, NSWindowAbove, NSWindowCloseButton, NSWindowFullScreenButton,
|
||||
NSWindowLevel, NSWindowMiniaturizeButton, NSWindowSharingNone, NSWindowSharingReadOnly,
|
||||
NSWindowStyleMask, NSWindowStyleMaskBorderless, NSWindowStyleMaskClosable,
|
||||
NSWindowStyleMaskFullSizeContentView, NSWindowStyleMaskMiniaturizable,
|
||||
NSWindowStyleMaskResizable, NSWindowStyleMaskTitled, NSWindowTabbingModePreferred,
|
||||
NSWindowTitleHidden, NSWindowZoomButton,
|
||||
};
|
||||
use icrate::Foundation::{
|
||||
CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSInteger, NSObject, NSPoint,
|
||||
NSRect, NSSize, NSString,
|
||||
CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSObject, NSPoint, NSRect,
|
||||
NSSize, NSString,
|
||||
};
|
||||
use objc2::declare::{Ivar, IvarDrop};
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType};
|
||||
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
|
||||
|
||||
use super::appkit::{
|
||||
NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType,
|
||||
NSColor, NSCursor, NSFilenamesPboardType, NSRequestUserAttentionType, NSResponder, NSScreen,
|
||||
NSView, NSWindow, NSWindowButton, NSWindowLevel, NSWindowSharingType, NSWindowStyleMask,
|
||||
NSWindowTabbingMode, NSWindowTitleVisibility,
|
||||
};
|
||||
use super::cursor::cursor_from_icon;
|
||||
use super::ffi::kCGFloatingWindowLevel;
|
||||
use super::ffi::{kCGNormalWindowLevel, CGSMainConnectionID, CGSSetWindowBackgroundBlurRadius};
|
||||
use super::monitor::get_display_id;
|
||||
|
||||
pub(crate) struct Window {
|
||||
window: MainThreadBound<Id<WinitWindow>>,
|
||||
@@ -56,7 +63,7 @@ pub(crate) struct Window {
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
self.window
|
||||
.get_on_main(|window, _| autoreleasepool(|_| window.close()))
|
||||
.get_on_main(|window| autoreleasepool(|_| window.close()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +75,8 @@ impl Window {
|
||||
) -> Result<Self, RootOsError> {
|
||||
let mtm = MainThreadMarker::new()
|
||||
.expect("windows can only be created on the main thread on macOS");
|
||||
let (window, _delegate) = autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs))?;
|
||||
let (window, _delegate) =
|
||||
autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs, mtm))?;
|
||||
Ok(Window {
|
||||
window: MainThreadBound::new(window, mtm),
|
||||
_delegate: MainThreadBound::new(_delegate, mtm),
|
||||
@@ -84,7 +92,19 @@ impl Window {
|
||||
&self,
|
||||
f: impl FnOnce(&WinitWindow) -> R + Send,
|
||||
) -> R {
|
||||
self.window.get_on_main(|window, _mtm| f(window))
|
||||
self.window.get_on_main(|window| f(window))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub(crate) fn raw_window_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
if let Some(mtm) = MainThreadMarker::new() {
|
||||
Ok(self.window.get(mtm).raw_window_handle_rwh_06())
|
||||
} else {
|
||||
Err(rwh_06::HandleError::Unavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +129,54 @@ impl From<u64> for WindowId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OwnedWindowHandle {
|
||||
ns_view: MainThreadBound<Id<NSView>>,
|
||||
}
|
||||
|
||||
impl Clone for OwnedWindowHandle {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
ns_view: MainThreadMarker::run_on_main(|mtm| {
|
||||
MainThreadBound::new(self.ns_view.get(mtm).clone(), mtm)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
let mtm =
|
||||
MainThreadMarker::new().expect("can only have handles on macOS on the main thread");
|
||||
let ns_view = match handle.as_raw() {
|
||||
rwh_06::RawWindowHandle::AppKit(handle) => {
|
||||
// SAFETY: Taking `WindowHandle<'_>` ensures that the pointer is valid.
|
||||
// Unwrap is fine, since the pointer comes from `NonNull`.
|
||||
unsafe { Id::retain(handle.ns_view.as_ptr().cast()) }.unwrap()
|
||||
}
|
||||
handle => panic!("invalid window handle {handle:?} on macOS"),
|
||||
};
|
||||
Self {
|
||||
ns_view: MainThreadBound::new(ns_view, mtm),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn raw_window_handle(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
if let Some(mtm) = MainThreadMarker::new() {
|
||||
let window_handle = rwh_06::AppKitWindowHandle::new({
|
||||
let ptr = Id::as_ptr(self.ns_view.get(mtm)) as *mut _;
|
||||
std::ptr::NonNull::new(ptr).expect("Id<T> should never be null")
|
||||
});
|
||||
let handle = rwh_06::RawWindowHandle::AppKit(window_handle);
|
||||
Ok(handle)
|
||||
} else {
|
||||
Err(rwh_06::HandleError::Unavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub movable_by_window_background: bool,
|
||||
@@ -145,52 +213,17 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
pub struct WinitWindow {
|
||||
// TODO: Fix unnecessary boxing here
|
||||
shared_state: IvarDrop<Box<Mutex<SharedState>>, "_shared_state">,
|
||||
}
|
||||
|
||||
mod ivars;
|
||||
pub struct WinitWindow;
|
||||
|
||||
unsafe impl ClassType for WinitWindow {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSWindow;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitWindow";
|
||||
}
|
||||
|
||||
unsafe impl WinitWindow {
|
||||
#[method(initWithContentRect:styleMask:state:)]
|
||||
unsafe fn init(
|
||||
this: *mut Self,
|
||||
frame: NSRect,
|
||||
mask: NSWindowStyleMask,
|
||||
state: *mut c_void,
|
||||
) -> Option<NonNull<Self>> {
|
||||
let this: Option<&mut Self> = unsafe {
|
||||
msg_send![
|
||||
super(this),
|
||||
initWithContentRect: frame,
|
||||
styleMask: mask,
|
||||
backing: NSBackingStoreType::NSBackingStoreBuffered,
|
||||
defer: false,
|
||||
]
|
||||
};
|
||||
|
||||
this.map(|this| {
|
||||
// SAFETY: The pointer originally came from `Box::into_raw`.
|
||||
Ivar::write(&mut this.shared_state, unsafe {
|
||||
Box::from_raw(state as *mut Mutex<SharedState>)
|
||||
});
|
||||
|
||||
// It is imperative to correct memory management that we
|
||||
// disable the extra release that would otherwise happen when
|
||||
// calling `clone` on the window.
|
||||
this.setReleasedWhenClosed(false);
|
||||
|
||||
NonNull::from(this)
|
||||
})
|
||||
}
|
||||
impl DeclaredClass for WinitWindow {
|
||||
type Ivars = Mutex<SharedState>;
|
||||
}
|
||||
|
||||
unsafe impl WinitWindow {
|
||||
@@ -291,6 +324,7 @@ impl WinitWindow {
|
||||
fn new(
|
||||
attrs: WindowAttributes,
|
||||
pl_attrs: PlatformSpecificWindowBuilderAttributes,
|
||||
mtm: MainThreadMarker,
|
||||
) -> Result<(Id<Self>, Id<WinitWindowDelegate>), RootOsError> {
|
||||
trace_scope!("WinitWindow::new");
|
||||
|
||||
@@ -298,15 +332,15 @@ impl WinitWindow {
|
||||
let screen = match attrs.fullscreen.0.clone().map(Into::into) {
|
||||
Some(Fullscreen::Borderless(Some(monitor)))
|
||||
| Some(Fullscreen::Exclusive(VideoMode { monitor, .. })) => {
|
||||
monitor.ns_screen().or_else(NSScreen::main)
|
||||
monitor.ns_screen(mtm).or_else(|| NSScreen::mainScreen(mtm))
|
||||
}
|
||||
Some(Fullscreen::Borderless(None)) => NSScreen::main(),
|
||||
Some(Fullscreen::Borderless(None)) => NSScreen::mainScreen(mtm),
|
||||
None => None,
|
||||
};
|
||||
let frame = match &screen {
|
||||
Some(screen) => screen.frame(),
|
||||
None => {
|
||||
let scale_factor = NSScreen::main()
|
||||
let scale_factor = NSScreen::mainScreen(mtm)
|
||||
.map(|screen| screen.backingScaleFactor() as f64)
|
||||
.unwrap_or(1.0);
|
||||
let (width, height) = match attrs.inner_size {
|
||||
@@ -336,31 +370,31 @@ impl WinitWindow {
|
||||
// if decorations is set to false, ignore pl_attrs
|
||||
//
|
||||
// if the titlebar is hidden, ignore other pl_attrs
|
||||
NSWindowStyleMask::NSBorderlessWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask
|
||||
NSWindowStyleMaskBorderless
|
||||
| NSWindowStyleMaskResizable
|
||||
| NSWindowStyleMaskMiniaturizable
|
||||
} else {
|
||||
// default case, resizable window with titlebar and titlebar buttons
|
||||
NSWindowStyleMask::NSClosableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask
|
||||
| NSWindowStyleMask::NSTitledWindowMask
|
||||
NSWindowStyleMaskClosable
|
||||
| NSWindowStyleMaskMiniaturizable
|
||||
| NSWindowStyleMaskResizable
|
||||
| NSWindowStyleMaskTitled
|
||||
};
|
||||
|
||||
if !attrs.resizable {
|
||||
masks &= !NSWindowStyleMask::NSResizableWindowMask;
|
||||
masks &= !NSWindowStyleMaskResizable;
|
||||
}
|
||||
|
||||
if !attrs.enabled_buttons.contains(WindowButtons::MINIMIZE) {
|
||||
masks &= !NSWindowStyleMask::NSMiniaturizableWindowMask;
|
||||
masks &= !NSWindowStyleMaskMiniaturizable;
|
||||
}
|
||||
|
||||
if !attrs.enabled_buttons.contains(WindowButtons::CLOSE) {
|
||||
masks &= !NSWindowStyleMask::NSClosableWindowMask;
|
||||
masks &= !NSWindowStyleMaskClosable;
|
||||
}
|
||||
|
||||
if pl_attrs.fullsize_content_view {
|
||||
masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
|
||||
masks |= NSWindowStyleMaskFullSizeContentView;
|
||||
}
|
||||
|
||||
let state = SharedState {
|
||||
@@ -370,18 +404,23 @@ impl WinitWindow {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Pass the state through FFI to the method declared on the class
|
||||
let state_ptr: *mut c_void = Box::into_raw(Box::new(Mutex::new(state))).cast();
|
||||
let this = mtm.alloc().set_ivars(Mutex::new(state));
|
||||
let this: Option<Id<Self>> = unsafe {
|
||||
msg_send_id![
|
||||
WinitWindow::alloc(),
|
||||
super(this),
|
||||
initWithContentRect: frame,
|
||||
styleMask: masks,
|
||||
state: state_ptr,
|
||||
backing: NSBackingStoreBuffered,
|
||||
defer: false,
|
||||
]
|
||||
};
|
||||
let this = this?;
|
||||
|
||||
// It is very important for correct memory management that we
|
||||
// disable the extra release that would otherwise happen when
|
||||
// calling `close` on the window.
|
||||
unsafe { this.setReleasedWhenClosed(false) };
|
||||
|
||||
let resize_increments = match attrs
|
||||
.resize_increments
|
||||
.map(|i| i.to_logical::<f64>(this.scale_factor()))
|
||||
@@ -399,26 +438,26 @@ impl WinitWindow {
|
||||
|
||||
if let Some(identifier) = pl_attrs.tabbing_identifier {
|
||||
this.setTabbingIdentifier(&NSString::from_str(&identifier));
|
||||
this.setTabbingMode(NSWindowTabbingMode::NSWindowTabbingModePreferred);
|
||||
this.setTabbingMode(NSWindowTabbingModePreferred);
|
||||
}
|
||||
|
||||
if attrs.content_protected {
|
||||
this.setSharingType(NSWindowSharingType::NSWindowSharingNone);
|
||||
this.setSharingType(NSWindowSharingNone);
|
||||
}
|
||||
|
||||
if pl_attrs.titlebar_transparent {
|
||||
this.setTitlebarAppearsTransparent(true);
|
||||
}
|
||||
if pl_attrs.title_hidden {
|
||||
this.setTitleVisibility(NSWindowTitleVisibility::Hidden);
|
||||
this.setTitleVisibility(NSWindowTitleHidden);
|
||||
}
|
||||
if pl_attrs.titlebar_buttons_hidden {
|
||||
for titlebar_button in &[
|
||||
#[allow(deprecated)]
|
||||
NSWindowButton::FullScreen,
|
||||
NSWindowButton::Miniaturize,
|
||||
NSWindowButton::Close,
|
||||
NSWindowButton::Zoom,
|
||||
NSWindowFullScreenButton,
|
||||
NSWindowMiniaturizeButton,
|
||||
NSWindowCloseButton,
|
||||
NSWindowZoomButton,
|
||||
] {
|
||||
if let Some(button) = this.standardWindowButton(*titlebar_button) {
|
||||
button.setHidden(true);
|
||||
@@ -430,7 +469,7 @@ impl WinitWindow {
|
||||
}
|
||||
|
||||
if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) {
|
||||
if let Some(button) = this.standardWindowButton(NSWindowButton::Zoom) {
|
||||
if let Some(button) = this.standardWindowButton(NSWindowZoomButton) {
|
||||
button.setEnabled(false);
|
||||
}
|
||||
}
|
||||
@@ -448,25 +487,16 @@ impl WinitWindow {
|
||||
})
|
||||
.ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?;
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
match attrs.parent_window.0 {
|
||||
Some(rwh_06::RawWindowHandle::AppKit(handle)) => {
|
||||
// SAFETY: Caller ensures the pointer is valid or NULL
|
||||
// Unwrap is fine, since the pointer comes from `NonNull`.
|
||||
let parent_view: Id<NSView> =
|
||||
unsafe { Id::retain(handle.ns_view.as_ptr().cast()) }.unwrap();
|
||||
let parent = parent_view.window().ok_or_else(|| {
|
||||
os_error!(OsError::CreationError(
|
||||
"parent view should be installed in a window"
|
||||
))
|
||||
})?;
|
||||
if let Some(parent_window) = attrs.parent_window {
|
||||
let parent = parent_window.ns_view.get(mtm).window().ok_or_else(|| {
|
||||
os_error!(OsError::CreationError(
|
||||
"parent view should be installed in a window"
|
||||
))
|
||||
})?;
|
||||
|
||||
// SAFETY: We know that there are no parent -> child -> parent cycles since the only place in `winit`
|
||||
// where we allow making a window a child window is right here, just after it's been created.
|
||||
unsafe { parent.addChildWindow(&this, NSWindowOrderingMode::NSWindowAbove) };
|
||||
}
|
||||
Some(raw) => panic!("Invalid raw window handle {raw:?} on macOS"),
|
||||
None => (),
|
||||
// SAFETY: We know that there are no parent -> child -> parent cycles since the only place in `winit`
|
||||
// where we allow making a window a child window is right here, just after it's been created.
|
||||
unsafe { parent.addChildWindow_ordered(&this, NSWindowAbove) };
|
||||
}
|
||||
|
||||
let view = WinitView::new(&this, pl_attrs.accepts_first_mouse);
|
||||
@@ -474,6 +504,7 @@ impl WinitWindow {
|
||||
// The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until
|
||||
// macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid
|
||||
// always the default system value in favour of the user's code
|
||||
#[allow(deprecated)]
|
||||
view.setWantsBestResolutionOpenGLSurface(!pl_attrs.disallow_hidpi);
|
||||
|
||||
// On Mojave, views automatically become layer-backed shortly after being added to
|
||||
@@ -481,17 +512,21 @@ impl WinitWindow {
|
||||
// the view and its associated OpenGL context. To work around this, on Mojave we
|
||||
// explicitly make the view layer-backed up front so that AppKit doesn't do it
|
||||
// itself and break the association with its context.
|
||||
if NSAppKitVersion::current().floor() > NSAppKitVersion::NSAppKitVersionNumber10_12 {
|
||||
if unsafe { NSAppKitVersionNumber }.floor() > NSAppKitVersionNumber10_12 {
|
||||
view.setWantsLayer(true);
|
||||
}
|
||||
|
||||
// Configure the new view as the "key view" for the window
|
||||
this.setContentView(&view);
|
||||
this.setInitialFirstResponder(&view);
|
||||
this.setContentView(Some(&view));
|
||||
this.setInitialFirstResponder(Some(&view));
|
||||
|
||||
if attrs.transparent {
|
||||
this.setOpaque(false);
|
||||
this.setBackgroundColor(&NSColor::clear());
|
||||
this.setBackgroundColor(Some(unsafe { &NSColor::clearColor() }));
|
||||
}
|
||||
|
||||
if attrs.blur {
|
||||
this.set_blur(attrs.blur);
|
||||
}
|
||||
|
||||
if let Some(dim) = attrs.min_inner_size {
|
||||
@@ -510,13 +545,13 @@ impl WinitWindow {
|
||||
|
||||
match attrs.preferred_theme {
|
||||
Some(theme) => {
|
||||
set_ns_theme(Some(theme));
|
||||
set_ns_theme(Some(theme), mtm);
|
||||
let mut state = this.lock_shared_state("WinitWindow::new");
|
||||
state.current_theme = Some(theme);
|
||||
}
|
||||
None => {
|
||||
let mut state = this.lock_shared_state("WinitWindow::new");
|
||||
state.current_theme = Some(get_ns_theme());
|
||||
state.current_theme = Some(get_ns_theme(mtm));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,9 +583,10 @@ impl WinitWindow {
|
||||
Ok((this, delegate))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(super) fn view(&self) -> Id<WinitView> {
|
||||
// SAFETY: The view inside WinitWindow is always `WinitView`
|
||||
unsafe { Id::cast(self.contentView()) }
|
||||
unsafe { Id::cast(self.contentView().unwrap()) }
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@@ -558,14 +594,14 @@ impl WinitWindow {
|
||||
&self,
|
||||
called_from_fn: &'static str,
|
||||
) -> SharedStateMutexGuard<'_> {
|
||||
SharedStateMutexGuard::new(self.shared_state.lock().unwrap(), called_from_fn)
|
||||
SharedStateMutexGuard::new(self.ivars().lock().unwrap(), called_from_fn)
|
||||
}
|
||||
|
||||
fn set_style_mask(&self, mask: NSWindowStyleMask) {
|
||||
self.setStyleMask(mask);
|
||||
// If we don't do this, key handling will break
|
||||
// (at least until the window is clicked again/etc.)
|
||||
let _ = self.makeFirstResponder(Some(&self.contentView()));
|
||||
let _ = self.makeFirstResponder(Some(&self.contentView().unwrap()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,7 +618,15 @@ impl WinitWindow {
|
||||
self.setOpaque(!transparent)
|
||||
}
|
||||
|
||||
pub fn set_blur(&self, _blur: bool) {}
|
||||
pub fn set_blur(&self, blur: bool) {
|
||||
// NOTE: in general we want to specify the blur radius, but the choice of 80
|
||||
// should be a reasonable default.
|
||||
let radius = if blur { 80 } else { 0 };
|
||||
let window_number = unsafe { self.windowNumber() };
|
||||
unsafe {
|
||||
CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, radius);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
match visible {
|
||||
@@ -631,7 +675,7 @@ impl WinitWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
let frame = self.contentView().frame();
|
||||
let frame = self.contentView().unwrap().frame();
|
||||
let logical: LogicalSize<f64> = (frame.size.width as f64, frame.size.height as f64).into();
|
||||
let scale_factor = self.scale_factor();
|
||||
logical.to_physical(scale_factor)
|
||||
@@ -753,9 +797,9 @@ impl WinitWindow {
|
||||
if !fullscreen {
|
||||
let mut mask = self.styleMask();
|
||||
if resizable {
|
||||
mask |= NSWindowStyleMask::NSResizableWindowMask;
|
||||
mask |= NSWindowStyleMaskResizable;
|
||||
} else {
|
||||
mask &= !NSWindowStyleMask::NSResizableWindowMask;
|
||||
mask &= !NSWindowStyleMaskResizable;
|
||||
}
|
||||
self.set_style_mask(mask);
|
||||
}
|
||||
@@ -772,15 +816,15 @@ impl WinitWindow {
|
||||
let mut mask = self.styleMask();
|
||||
|
||||
if buttons.contains(WindowButtons::CLOSE) {
|
||||
mask |= NSWindowStyleMask::NSClosableWindowMask;
|
||||
mask |= NSWindowStyleMaskClosable;
|
||||
} else {
|
||||
mask &= !NSWindowStyleMask::NSClosableWindowMask;
|
||||
mask &= !NSWindowStyleMaskClosable;
|
||||
}
|
||||
|
||||
if buttons.contains(WindowButtons::MINIMIZE) {
|
||||
mask |= NSWindowStyleMask::NSMiniaturizableWindowMask;
|
||||
mask |= NSWindowStyleMaskMiniaturizable;
|
||||
} else {
|
||||
mask &= !NSWindowStyleMask::NSMiniaturizableWindowMask;
|
||||
mask &= !NSWindowStyleMaskMiniaturizable;
|
||||
}
|
||||
|
||||
// This must happen before the button's "enabled" status has been set,
|
||||
@@ -790,7 +834,7 @@ impl WinitWindow {
|
||||
// We edit the button directly instead of using `NSResizableWindowMask`,
|
||||
// since that mask also affect the resizability of the window (which is
|
||||
// controllable by other means in `winit`).
|
||||
if let Some(button) = self.standardWindowButton(NSWindowButton::Zoom) {
|
||||
if let Some(button) = self.standardWindowButton(NSWindowZoomButton) {
|
||||
button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE));
|
||||
}
|
||||
}
|
||||
@@ -802,7 +846,7 @@ impl WinitWindow {
|
||||
buttons |= WindowButtons::MINIMIZE;
|
||||
}
|
||||
if self
|
||||
.standardWindowButton(NSWindowButton::Zoom)
|
||||
.standardWindowButton(NSWindowZoomButton)
|
||||
.map(|b| b.isEnabled())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
@@ -816,7 +860,14 @@ impl WinitWindow {
|
||||
|
||||
pub fn set_cursor_icon(&self, icon: CursorIcon) {
|
||||
let view = self.view();
|
||||
view.set_cursor_icon(NSCursor::from_icon(icon));
|
||||
view.set_cursor_icon(cursor_from_icon(icon));
|
||||
self.invalidateCursorRectsForView(&view);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
|
||||
let view = self.view();
|
||||
view.set_cursor_icon(cursor.0.clone());
|
||||
self.invalidateCursorRectsForView(&view);
|
||||
}
|
||||
|
||||
@@ -869,8 +920,11 @@ impl WinitWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
let event = NSApp().currentEvent();
|
||||
self.performWindowDragWithEvent(event.as_deref());
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let event = NSApplication::sharedApplication(mtm)
|
||||
.currentEvent()
|
||||
.unwrap();
|
||||
self.performWindowDragWithEvent(&event);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -893,9 +947,8 @@ impl WinitWindow {
|
||||
// we make it resizable temporalily.
|
||||
let curr_mask = self.styleMask();
|
||||
|
||||
let required =
|
||||
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
|
||||
let needs_temp_mask = !curr_mask.contains(required);
|
||||
let required = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
|
||||
let needs_temp_mask = !mask_contains(curr_mask, required);
|
||||
if needs_temp_mask {
|
||||
self.set_style_mask(required);
|
||||
}
|
||||
@@ -916,9 +969,9 @@ impl WinitWindow {
|
||||
.take()
|
||||
.unwrap_or_else(|| self.styleMask());
|
||||
if shared_state.resizable {
|
||||
base_mask | NSWindowStyleMask::NSResizableWindowMask
|
||||
base_mask | NSWindowStyleMaskResizable
|
||||
} else {
|
||||
base_mask & !NSWindowStyleMask::NSResizableWindowMask
|
||||
base_mask & !NSWindowStyleMaskResizable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -949,7 +1002,7 @@ impl WinitWindow {
|
||||
if minimized {
|
||||
self.miniaturize(Some(self));
|
||||
} else {
|
||||
self.deminiaturize(Some(self));
|
||||
unsafe { self.deminiaturize(Some(self)) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,6 +1013,7 @@ impl WinitWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let is_zoomed = self.is_zoomed();
|
||||
if is_zoomed == maximized {
|
||||
return;
|
||||
@@ -978,17 +1032,14 @@ impl WinitWindow {
|
||||
return;
|
||||
}
|
||||
|
||||
if self
|
||||
.styleMask()
|
||||
.contains(NSWindowStyleMask::NSResizableWindowMask)
|
||||
{
|
||||
if mask_contains(self.styleMask(), NSWindowStyleMaskResizable) {
|
||||
drop(shared_state);
|
||||
// Just use the native zoom if resizable
|
||||
self.zoom(None);
|
||||
} else {
|
||||
// if it's not resizable, we set the frame directly
|
||||
let new_rect = if maximized {
|
||||
let screen = NSScreen::main().expect("no screen found");
|
||||
let screen = NSScreen::mainScreen(mtm).expect("no screen found");
|
||||
screen.visibleFrame()
|
||||
} else {
|
||||
shared_state.saved_standard_frame()
|
||||
@@ -1011,6 +1062,9 @@ impl WinitWindow {
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
|
||||
let mut shared_state_lock = self.lock_shared_state("set_fullscreen");
|
||||
if shared_state_lock.is_simple_fullscreen {
|
||||
return;
|
||||
@@ -1042,7 +1096,7 @@ impl WinitWindow {
|
||||
}
|
||||
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
|
||||
}
|
||||
.ns_screen()
|
||||
.ns_screen(mtm)
|
||||
.unwrap();
|
||||
|
||||
let old_screen = self.screen().unwrap();
|
||||
@@ -1073,7 +1127,6 @@ impl WinitWindow {
|
||||
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
|
||||
|
||||
if matches!(old_fullscreen, Some(Fullscreen::Borderless(_))) {
|
||||
let app = NSApp();
|
||||
let mut shared_state_lock = self.lock_shared_state("set_fullscreen");
|
||||
shared_state_lock.save_presentation_opts = Some(app.presentationOptions());
|
||||
}
|
||||
@@ -1131,7 +1184,7 @@ impl WinitWindow {
|
||||
// Window level must be restored from `CGShieldingWindowLevel()
|
||||
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||
// anything
|
||||
window.setLevel(NSWindowLevel::Normal);
|
||||
window.setLevel(kCGNormalWindowLevel as NSWindowLevel);
|
||||
window.toggleFullScreen(None);
|
||||
}
|
||||
|
||||
@@ -1141,9 +1194,8 @@ impl WinitWindow {
|
||||
// set a normal style temporarily. The previous state will be
|
||||
// restored in `WindowDelegate::window_did_exit_fullscreen`.
|
||||
let curr_mask = self.styleMask();
|
||||
let required = NSWindowStyleMask::NSTitledWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask;
|
||||
if !curr_mask.contains(required) {
|
||||
let required = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
|
||||
if !mask_contains(curr_mask, required) {
|
||||
self.set_style_mask(required);
|
||||
self.lock_shared_state("set_fullscreen").saved_style = Some(curr_mask);
|
||||
}
|
||||
@@ -1172,30 +1224,27 @@ impl WinitWindow {
|
||||
// of the menu bar, and this looks broken, so we must make sure
|
||||
// that the menu bar is disabled. This is done in the window
|
||||
// delegate in `window:willUseFullScreenPresentationOptions:`.
|
||||
let app = NSApp();
|
||||
self.lock_shared_state("set_fullscreen")
|
||||
.save_presentation_opts = Some(app.presentationOptions());
|
||||
|
||||
let presentation_options =
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
let presentation_options = NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationHideMenuBar;
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
let window_level =
|
||||
NSWindowLevel(unsafe { ffi::CGShieldingWindowLevel() } as NSInteger + 1);
|
||||
let window_level = unsafe { ffi::CGShieldingWindowLevel() } as NSWindowLevel + 1;
|
||||
self.setLevel(window_level);
|
||||
}
|
||||
(Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => {
|
||||
let presentation_options = self
|
||||
.lock_shared_state("set_fullscreen")
|
||||
.save_presentation_opts
|
||||
.unwrap_or_else(|| {
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
|
||||
});
|
||||
NSApp().setPresentationOptions(presentation_options);
|
||||
.unwrap_or(
|
||||
NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationAutoHideMenuBar,
|
||||
);
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
unsafe {
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
@@ -1207,7 +1256,7 @@ impl WinitWindow {
|
||||
|
||||
// Restore the normal window level following the Borderless fullscreen
|
||||
// `CGShieldingWindowLevel() + 1` hack.
|
||||
self.setLevel(NSWindowLevel::Normal);
|
||||
self.setLevel(kCGNormalWindowLevel as NSWindowLevel);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
@@ -1235,15 +1284,15 @@ impl WinitWindow {
|
||||
|
||||
let new_mask = {
|
||||
let mut new_mask = if decorations {
|
||||
NSWindowStyleMask::NSClosableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask
|
||||
| NSWindowStyleMask::NSTitledWindowMask
|
||||
NSWindowStyleMaskClosable
|
||||
| NSWindowStyleMaskMiniaturizable
|
||||
| NSWindowStyleMaskResizable
|
||||
| NSWindowStyleMaskTitled
|
||||
} else {
|
||||
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
|
||||
NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable
|
||||
};
|
||||
if !resizable {
|
||||
new_mask &= !NSWindowStyleMask::NSResizableWindowMask;
|
||||
new_mask &= !NSWindowStyleMaskResizable;
|
||||
}
|
||||
new_mask
|
||||
};
|
||||
@@ -1258,9 +1307,9 @@ impl WinitWindow {
|
||||
#[inline]
|
||||
pub fn set_window_level(&self, level: WindowLevel) {
|
||||
let level = match level {
|
||||
WindowLevel::AlwaysOnTop => NSWindowLevel::Floating,
|
||||
WindowLevel::AlwaysOnBottom => NSWindowLevel::BELOW_NORMAL,
|
||||
WindowLevel::Normal => NSWindowLevel::Normal,
|
||||
WindowLevel::AlwaysOnTop => kCGFloatingWindowLevel as NSWindowLevel,
|
||||
WindowLevel::AlwaysOnBottom => (kCGNormalWindowLevel - 1) as NSWindowLevel,
|
||||
WindowLevel::Normal => kCGNormalWindowLevel as NSWindowLevel,
|
||||
};
|
||||
self.setLevel(level);
|
||||
}
|
||||
@@ -1295,30 +1344,33 @@ impl WinitWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn focus_window(&self) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let is_minimized = self.isMiniaturized();
|
||||
let is_visible = self.isVisible();
|
||||
|
||||
if !is_minimized && is_visible {
|
||||
NSApp().activateIgnoringOtherApps(true);
|
||||
#[allow(deprecated)]
|
||||
NSApplication::sharedApplication(mtm).activateIgnoringOtherApps(true);
|
||||
self.makeKeyAndOrderFront(None);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let ns_request_type = request_type.map(|ty| match ty {
|
||||
UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest,
|
||||
UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest,
|
||||
UserAttentionType::Critical => NSCriticalRequest,
|
||||
UserAttentionType::Informational => NSInformationalRequest,
|
||||
});
|
||||
if let Some(ty) = ns_request_type {
|
||||
NSApp().requestUserAttention(ty);
|
||||
NSApplication::sharedApplication(mtm).requestUserAttention(ty);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
// Allow directly accessing the current monitor internally without unwrapping.
|
||||
pub(crate) fn current_monitor_inner(&self) -> Option<MonitorHandle> {
|
||||
let display_id = self.screen()?.display_id();
|
||||
let display_id = get_display_id(&*self.screen()?);
|
||||
Some(MonitorHandle::new(display_id))
|
||||
}
|
||||
|
||||
@@ -1343,7 +1395,7 @@ impl WinitWindow {
|
||||
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
|
||||
let mut window_handle = rwh_04::AppKitHandle::empty();
|
||||
window_handle.ns_window = self as *const Self as *mut _;
|
||||
window_handle.ns_view = Id::as_ptr(&self.contentView()) as *mut _;
|
||||
window_handle.ns_view = Id::as_ptr(&self.contentView().unwrap()) as *mut _;
|
||||
rwh_04::RawWindowHandle::AppKit(window_handle)
|
||||
}
|
||||
|
||||
@@ -1352,7 +1404,7 @@ impl WinitWindow {
|
||||
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
||||
let mut window_handle = rwh_05::AppKitWindowHandle::empty();
|
||||
window_handle.ns_window = self as *const Self as *mut _;
|
||||
window_handle.ns_view = Id::as_ptr(&self.contentView()) as *mut _;
|
||||
window_handle.ns_view = Id::as_ptr(&self.contentView().unwrap()) as *mut _;
|
||||
rwh_05::RawWindowHandle::AppKit(window_handle)
|
||||
}
|
||||
|
||||
@@ -1364,12 +1416,12 @@ impl WinitWindow {
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
|
||||
let window_handle = rwh_06::AppKitWindowHandle::new({
|
||||
let ptr = Id::as_ptr(&self.contentView()) as *mut _;
|
||||
let ptr = Id::as_ptr(&self.contentView().unwrap()) as *mut _;
|
||||
std::ptr::NonNull::new(ptr).expect("Id<T> should never be null")
|
||||
});
|
||||
Ok(rwh_06::RawWindowHandle::AppKit(window_handle))
|
||||
rwh_06::RawWindowHandle::AppKit(window_handle)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
@@ -1402,21 +1454,24 @@ impl WinitWindow {
|
||||
}
|
||||
|
||||
pub fn set_theme(&self, theme: Option<Theme>) {
|
||||
set_ns_theme(theme);
|
||||
self.lock_shared_state("set_theme").current_theme = theme.or_else(|| Some(get_ns_theme()));
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
set_ns_theme(theme, mtm);
|
||||
self.lock_shared_state("set_theme").current_theme =
|
||||
theme.or_else(|| Some(get_ns_theme(mtm)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_content_protected(&self, protected: bool) {
|
||||
self.setSharingType(if protected {
|
||||
NSWindowSharingType::NSWindowSharingNone
|
||||
NSWindowSharingNone
|
||||
} else {
|
||||
NSWindowSharingType::NSWindowSharingReadOnly
|
||||
NSWindowSharingReadOnly
|
||||
})
|
||||
}
|
||||
|
||||
pub fn title(&self) -> String {
|
||||
self.title_().to_string()
|
||||
let window: &NSWindow = self;
|
||||
window.title().to_string()
|
||||
}
|
||||
|
||||
pub fn reset_dead_keys(&self) {
|
||||
@@ -1433,9 +1488,10 @@ impl WindowExtMacOS for WinitWindow {
|
||||
|
||||
#[inline]
|
||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let mut shared_state_lock = self.lock_shared_state("set_simple_fullscreen");
|
||||
|
||||
let app = NSApp();
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let is_native_fullscreen = shared_state_lock.fullscreen.is_some();
|
||||
let is_simple_fullscreen = shared_state_lock.is_simple_fullscreen;
|
||||
|
||||
@@ -1463,20 +1519,19 @@ impl WindowExtMacOS for WinitWindow {
|
||||
|
||||
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
|
||||
let presentation_options =
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
|
||||
NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar;
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
// Hide the titlebar
|
||||
self.toggle_style_mask(NSWindowStyleMask::NSTitledWindowMask, false);
|
||||
self.toggle_style_mask(NSWindowStyleMaskTitled, false);
|
||||
|
||||
// Set the window frame to the screen frame size
|
||||
let screen = self.screen().expect("expected screen to be available");
|
||||
self.setFrame_display(screen.frame(), true);
|
||||
|
||||
// Fullscreen windows can't be resized, minimized, or moved
|
||||
self.toggle_style_mask(NSWindowStyleMask::NSMiniaturizableWindowMask, false);
|
||||
self.toggle_style_mask(NSWindowStyleMask::NSResizableWindowMask, false);
|
||||
self.toggle_style_mask(NSWindowStyleMaskMiniaturizable, false);
|
||||
self.toggle_style_mask(NSWindowStyleMaskResizable, false);
|
||||
self.setMovable(false);
|
||||
|
||||
true
|
||||
@@ -1525,24 +1580,20 @@ impl WindowExtMacOS for WinitWindow {
|
||||
|
||||
#[inline]
|
||||
fn select_next_tab(&self) {
|
||||
if let Some(group) = self.tabGroup() {
|
||||
group.selectNextTab();
|
||||
}
|
||||
self.selectNextTab(None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select_previous_tab(&self) {
|
||||
if let Some(group) = self.tabGroup() {
|
||||
group.selectPreviousTab()
|
||||
}
|
||||
unsafe { self.selectPreviousTab(None) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select_tab_at_index(&self, index: usize) {
|
||||
if let Some(group) = self.tabGroup() {
|
||||
if let Some(windows) = group.tabbedWindows() {
|
||||
if let Some(windows) = unsafe { self.tabbedWindows() } {
|
||||
if index < windows.len() {
|
||||
group.setSelectedWindow(&windows[index]);
|
||||
group.setSelectedWindow(Some(&windows[index]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1550,8 +1601,7 @@ impl WindowExtMacOS for WinitWindow {
|
||||
|
||||
#[inline]
|
||||
fn num_tabs(&self) -> usize {
|
||||
self.tabGroup()
|
||||
.and_then(|group| group.tabbedWindows())
|
||||
unsafe { self.tabbedWindows() }
|
||||
.map(|windows| windows.len())
|
||||
.unwrap_or(1)
|
||||
}
|
||||
@@ -1575,25 +1625,31 @@ impl WindowExtMacOS for WinitWindow {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_ns_theme() -> Theme {
|
||||
let app = NSApp();
|
||||
fn mask_contains(mask: NSWindowStyleMask, value: NSWindowStyleMask) -> bool {
|
||||
mask & value == value
|
||||
}
|
||||
|
||||
pub(super) fn get_ns_theme(mtm: MainThreadMarker) -> Theme {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] };
|
||||
if !has_theme {
|
||||
return Theme::Light;
|
||||
}
|
||||
let appearance = app.effectiveAppearance();
|
||||
let name = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
|
||||
NSString::from_str("NSAppearanceNameAqua"),
|
||||
NSString::from_str("NSAppearanceNameDarkAqua"),
|
||||
]));
|
||||
let name = appearance
|
||||
.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
|
||||
NSString::from_str("NSAppearanceNameAqua"),
|
||||
NSString::from_str("NSAppearanceNameDarkAqua"),
|
||||
]))
|
||||
.unwrap();
|
||||
match &*name.to_string() {
|
||||
"NSAppearanceNameDarkAqua" => Theme::Dark,
|
||||
_ => Theme::Light,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_ns_theme(theme: Option<Theme>) {
|
||||
let app = NSApp();
|
||||
fn set_ns_theme(theme: Option<Theme>, mtm: MainThreadMarker) {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] };
|
||||
if has_theme {
|
||||
let appearance = theme.map(|t| {
|
||||
@@ -1601,7 +1657,7 @@ fn set_ns_theme(theme: Option<Theme>) {
|
||||
Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"),
|
||||
Theme::Light => NSString::from_str("NSAppearanceNameAqua"),
|
||||
};
|
||||
NSAppearance::appearanceNamed(&name)
|
||||
NSAppearance::appearanceNamed(&name).unwrap()
|
||||
});
|
||||
app.setAppearance(appearance.as_ref().map(|a| a.as_ref()));
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::cell::Cell;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::ptr;
|
||||
|
||||
use icrate::Foundation::{NSArray, NSObject, NSSize, NSString};
|
||||
use objc2::declare::{Ivar, IvarDrop};
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType};
|
||||
|
||||
use super::appkit::{
|
||||
NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState,
|
||||
use icrate::AppKit::{
|
||||
NSApplicationPresentationFullScreen, NSApplicationPresentationHideDock,
|
||||
NSApplicationPresentationHideMenuBar, NSApplicationPresentationOptions, NSDraggingDestination,
|
||||
NSFilenamesPboardType, NSPasteboard, NSWindowDelegate, NSWindowOcclusionStateVisible,
|
||||
};
|
||||
use icrate::Foundation::{MainThreadMarker, NSArray, NSObject, NSObjectProtocol, NSSize, NSString};
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::runtime::{AnyObject, ProtocolObject};
|
||||
use objc2::{
|
||||
class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
|
||||
};
|
||||
|
||||
use super::{
|
||||
app_state::AppState,
|
||||
util,
|
||||
@@ -24,7 +27,9 @@ use crate::{
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
pub(crate) struct State {
|
||||
window: Id<WinitWindow>,
|
||||
|
||||
// This is set when WindowBuilder::with_fullscreen was set,
|
||||
// see comments of `window_did_fail_to_enter_fullscreen`
|
||||
initial_fullscreen: Cell<bool>,
|
||||
@@ -37,72 +42,21 @@ pub struct State {
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WinitWindowDelegate {
|
||||
window: IvarDrop<Id<WinitWindow>, "_window">,
|
||||
|
||||
// TODO: It may be possible for delegate methods to be called
|
||||
// asynchronously, causing data races panics?
|
||||
// TODO: Remove unnecessary boxing here
|
||||
state: IvarDrop<Box<State>, "_state">,
|
||||
}
|
||||
|
||||
mod ivars;
|
||||
pub(crate) struct WinitWindowDelegate;
|
||||
|
||||
unsafe impl ClassType for WinitWindowDelegate {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitWindowDelegate";
|
||||
}
|
||||
|
||||
unsafe impl WinitWindowDelegate {
|
||||
#[method(initWithWindow:initialFullscreen:)]
|
||||
unsafe fn init_with_winit(
|
||||
this: *mut Self,
|
||||
window: &WinitWindow,
|
||||
initial_fullscreen: bool,
|
||||
) -> Option<NonNull<Self>> {
|
||||
let this: Option<&mut Self> = unsafe { msg_send![super(this), init] };
|
||||
this.map(|this| {
|
||||
let scale_factor = window.scale_factor();
|
||||
|
||||
Ivar::write(&mut this.window, window.retain());
|
||||
Ivar::write(
|
||||
&mut this.state,
|
||||
Box::new(State {
|
||||
initial_fullscreen: Cell::new(initial_fullscreen),
|
||||
previous_position: Cell::new(None),
|
||||
previous_scale_factor: Cell::new(scale_factor),
|
||||
}),
|
||||
);
|
||||
|
||||
if scale_factor != 1.0 {
|
||||
this.queue_static_scale_factor_changed_event();
|
||||
}
|
||||
this.window.setDelegate(Some(this));
|
||||
|
||||
// Enable theme change event
|
||||
let notification_center: Id<AnyObject> =
|
||||
unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] };
|
||||
let notification_name =
|
||||
NSString::from_str("AppleInterfaceThemeChangedNotification");
|
||||
let _: () = unsafe {
|
||||
msg_send![
|
||||
¬ification_center,
|
||||
addObserver: &*this
|
||||
selector: sel!(effectiveAppearanceDidChange:)
|
||||
name: &*notification_name
|
||||
object: ptr::null::<AnyObject>()
|
||||
]
|
||||
};
|
||||
|
||||
NonNull::from(this)
|
||||
})
|
||||
}
|
||||
impl DeclaredClass for WinitWindowDelegate {
|
||||
type Ivars = State;
|
||||
}
|
||||
|
||||
// NSWindowDelegate + NSDraggingDestination protocols
|
||||
unsafe impl WinitWindowDelegate {
|
||||
unsafe impl NSObjectProtocol for WinitWindowDelegate {}
|
||||
|
||||
unsafe impl NSWindowDelegate for WinitWindowDelegate {
|
||||
#[method(windowShouldClose:)]
|
||||
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
|
||||
trace_scope!("windowShouldClose:");
|
||||
@@ -117,7 +71,7 @@ declare_class!(
|
||||
autoreleasepool(|_| {
|
||||
// Since El Capitan, we need to be careful that delegate methods can't
|
||||
// be called after the window closes.
|
||||
self.window.setDelegate(None);
|
||||
self.ivars().window.setDelegate(None);
|
||||
});
|
||||
self.queue_event(WindowEvent::Destroyed);
|
||||
}
|
||||
@@ -134,16 +88,19 @@ declare_class!(
|
||||
trace_scope!("windowWillStartLiveResize:");
|
||||
|
||||
let increments = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_enter_fullscreen")
|
||||
.resize_increments;
|
||||
self.window.set_resize_increments_inner(increments);
|
||||
self.ivars().window.set_resize_increments_inner(increments);
|
||||
}
|
||||
|
||||
#[method(windowDidEndLiveResize:)]
|
||||
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEndLiveResize:");
|
||||
self.window.set_resize_increments_inner(NSSize::new(1., 1.));
|
||||
self.ivars()
|
||||
.window
|
||||
.set_resize_increments_inner(NSSize::new(1., 1.));
|
||||
}
|
||||
|
||||
// This won't be triggered if the move was part of a resize.
|
||||
@@ -177,11 +134,183 @@ declare_class!(
|
||||
// NSWindowDelegate, and as a result a tracked modifiers state can quite
|
||||
// easily fall out of synchrony with reality. This requires us to emit
|
||||
// a synthetic ModifiersChanged event when we lose focus.
|
||||
self.window.view().reset_modifiers();
|
||||
self.ivars().window.view().reset_modifiers();
|
||||
|
||||
self.queue_event(WindowEvent::Focused(false));
|
||||
}
|
||||
|
||||
/// Invoked when before enter fullscreen
|
||||
#[method(windowWillEnterFullScreen:)]
|
||||
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillEnterFullScreen:");
|
||||
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_enter_fullscreen");
|
||||
shared_state.maximized = self.ivars().window.is_zoomed();
|
||||
let fullscreen = shared_state.fullscreen.as_ref();
|
||||
match fullscreen {
|
||||
// Exclusive mode sets the state in `set_fullscreen` as the user
|
||||
// can't enter exclusive mode by other means (like the
|
||||
// fullscreen button on the window decorations)
|
||||
Some(Fullscreen::Exclusive(_)) => (),
|
||||
// `window_will_enter_fullscreen` was triggered and we're already
|
||||
// in fullscreen, so we must've reached here by `set_fullscreen`
|
||||
// as it updates the state
|
||||
Some(Fullscreen::Borderless(_)) => (),
|
||||
// Otherwise, we must've reached fullscreen by the user clicking
|
||||
// on the green fullscreen button. Update state!
|
||||
None => {
|
||||
let current_monitor = self.ivars().window.current_monitor_inner();
|
||||
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
|
||||
}
|
||||
}
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
/// Invoked when before exit fullscreen
|
||||
#[method(windowWillExitFullScreen:)]
|
||||
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillExitFullScreen:");
|
||||
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
#[method(window:willUseFullScreenPresentationOptions:)]
|
||||
fn window_will_use_fullscreen_presentation_options(
|
||||
&self,
|
||||
_: Option<&AnyObject>,
|
||||
proposed_options: NSApplicationPresentationOptions,
|
||||
) -> NSApplicationPresentationOptions {
|
||||
trace_scope!("window:willUseFullScreenPresentationOptions:");
|
||||
// Generally, games will want to disable the menu bar and the dock. Ideally,
|
||||
// this would be configurable by the user. Unfortunately because of our
|
||||
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
|
||||
// placed on top of the menu bar in exclusive fullscreen mode. This looks
|
||||
// broken so we always disable the menu bar in exclusive fullscreen. We may
|
||||
// still want to make this configurable for borderless fullscreen. Right now
|
||||
// we don't, for consistency. If we do, it should be documented that the
|
||||
// user-provided options are ignored in exclusive fullscreen.
|
||||
let mut options = proposed_options;
|
||||
let shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_use_fullscreen_presentation_options");
|
||||
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
|
||||
options = NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationHideMenuBar;
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
|
||||
/// Invoked when entered fullscreen
|
||||
#[method(windowDidEnterFullScreen:)]
|
||||
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEnterFullScreen:");
|
||||
self.ivars().initial_fullscreen.set(false);
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.ivars().window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when exited fullscreen
|
||||
#[method(windowDidExitFullScreen:)]
|
||||
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidExitFullScreen:");
|
||||
|
||||
self.ivars().window.restore_state_from_fullscreen();
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.ivars().window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when fail to enter fullscreen
|
||||
///
|
||||
/// When this window launch from a fullscreen app (e.g. launch from VS Code
|
||||
/// terminal), it creates a new virtual destkop and a transition animation.
|
||||
/// This animation takes one second and cannot be disable without
|
||||
/// elevated privileges. In this animation time, all toggleFullscreen events
|
||||
/// will be failed. In this implementation, we will try again by using
|
||||
/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen.
|
||||
/// It should be fine as we only do this at initialzation (i.e with_fullscreen
|
||||
/// was set).
|
||||
///
|
||||
/// From Apple doc:
|
||||
/// In some cases, the transition to enter full-screen mode can fail,
|
||||
/// due to being in the midst of handling some other animation or user gesture.
|
||||
/// This method indicates that there was an error, and you should clean up any
|
||||
/// work you may have done to prepare to enter full-screen mode.
|
||||
#[method(windowDidFailToEnterFullScreen:)]
|
||||
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidFailToEnterFullScreen:");
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_fail_to_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
shared_state.target_fullscreen = None;
|
||||
if self.ivars().initial_fullscreen.get() {
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
&*self.ivars().window,
|
||||
performSelector: sel!(toggleFullScreen:),
|
||||
withObject: ptr::null::<AnyObject>(),
|
||||
afterDelay: 0.5,
|
||||
];
|
||||
};
|
||||
} else {
|
||||
self.ivars().window.restore_state_from_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when the occlusion state of the window changes
|
||||
#[method(windowDidChangeOcclusionState:)]
|
||||
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeOcclusionState:");
|
||||
let visible = self.ivars().window.occlusionState() & NSWindowOcclusionStateVisible
|
||||
== NSWindowOcclusionStateVisible;
|
||||
self.queue_event(WindowEvent::Occluded(!visible));
|
||||
}
|
||||
|
||||
#[method(windowDidChangeScreen:)]
|
||||
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeScreen:");
|
||||
let is_simple_fullscreen = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_change_screen")
|
||||
.is_simple_fullscreen;
|
||||
if is_simple_fullscreen {
|
||||
if let Some(screen) = self.ivars().window.screen() {
|
||||
self.ivars().window.setFrame_display(screen.frame(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSDraggingDestination for WinitWindowDelegate {
|
||||
/// Invoked when the dragged image enters destination bounds or frame
|
||||
#[method(draggingEntered:)]
|
||||
fn dragging_entered(&self, sender: &NSObject) -> bool {
|
||||
@@ -190,7 +319,9 @@ declare_class!(
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap();
|
||||
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
@@ -216,7 +347,9 @@ declare_class!(
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap();
|
||||
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
@@ -239,153 +372,9 @@ declare_class!(
|
||||
trace_scope!("draggingExited:");
|
||||
self.queue_event(WindowEvent::HoveredFileCancelled);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when before enter fullscreen
|
||||
#[method(windowWillEnterFullScreen:)]
|
||||
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillEnterFullScreen:");
|
||||
|
||||
let mut shared_state = self
|
||||
.window
|
||||
.lock_shared_state("window_will_enter_fullscreen");
|
||||
shared_state.maximized = self.window.is_zoomed();
|
||||
let fullscreen = shared_state.fullscreen.as_ref();
|
||||
match fullscreen {
|
||||
// Exclusive mode sets the state in `set_fullscreen` as the user
|
||||
// can't enter exclusive mode by other means (like the
|
||||
// fullscreen button on the window decorations)
|
||||
Some(Fullscreen::Exclusive(_)) => (),
|
||||
// `window_will_enter_fullscreen` was triggered and we're already
|
||||
// in fullscreen, so we must've reached here by `set_fullscreen`
|
||||
// as it updates the state
|
||||
Some(Fullscreen::Borderless(_)) => (),
|
||||
// Otherwise, we must've reached fullscreen by the user clicking
|
||||
// on the green fullscreen button. Update state!
|
||||
None => {
|
||||
let current_monitor = self.window.current_monitor_inner();
|
||||
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
|
||||
}
|
||||
}
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
/// Invoked when before exit fullscreen
|
||||
#[method(windowWillExitFullScreen:)]
|
||||
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillExitFullScreen:");
|
||||
|
||||
let mut shared_state = self.window.lock_shared_state("window_will_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
#[method(window:willUseFullScreenPresentationOptions:)]
|
||||
fn window_will_use_fullscreen_presentation_options(
|
||||
&self,
|
||||
_: Option<&AnyObject>,
|
||||
proposed_options: NSApplicationPresentationOptions,
|
||||
) -> NSApplicationPresentationOptions {
|
||||
trace_scope!("window:willUseFullScreenPresentationOptions:");
|
||||
// Generally, games will want to disable the menu bar and the dock. Ideally,
|
||||
// this would be configurable by the user. Unfortunately because of our
|
||||
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
|
||||
// placed on top of the menu bar in exclusive fullscreen mode. This looks
|
||||
// broken so we always disable the menu bar in exclusive fullscreen. We may
|
||||
// still want to make this configurable for borderless fullscreen. Right now
|
||||
// we don't, for consistency. If we do, it should be documented that the
|
||||
// user-provided options are ignored in exclusive fullscreen.
|
||||
let mut options = proposed_options;
|
||||
let shared_state = self
|
||||
.window
|
||||
.lock_shared_state("window_will_use_fullscreen_presentation_options");
|
||||
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
|
||||
options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
|
||||
/// Invoked when entered fullscreen
|
||||
#[method(windowDidEnterFullScreen:)]
|
||||
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEnterFullScreen:");
|
||||
self.state.initial_fullscreen.set(false);
|
||||
let mut shared_state = self.window.lock_shared_state("window_did_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when exited fullscreen
|
||||
#[method(windowDidExitFullScreen:)]
|
||||
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidExitFullScreen:");
|
||||
|
||||
self.window.restore_state_from_fullscreen();
|
||||
let mut shared_state = self.window.lock_shared_state("window_did_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when fail to enter fullscreen
|
||||
///
|
||||
/// When this window launch from a fullscreen app (e.g. launch from VS Code
|
||||
/// terminal), it creates a new virtual destkop and a transition animation.
|
||||
/// This animation takes one second and cannot be disable without
|
||||
/// elevated privileges. In this animation time, all toggleFullscreen events
|
||||
/// will be failed. In this implementation, we will try again by using
|
||||
/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen.
|
||||
/// It should be fine as we only do this at initialzation (i.e with_fullscreen
|
||||
/// was set).
|
||||
///
|
||||
/// From Apple doc:
|
||||
/// In some cases, the transition to enter full-screen mode can fail,
|
||||
/// due to being in the midst of handling some other animation or user gesture.
|
||||
/// This method indicates that there was an error, and you should clean up any
|
||||
/// work you may have done to prepare to enter full-screen mode.
|
||||
#[method(windowDidFailToEnterFullScreen:)]
|
||||
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidFailToEnterFullScreen:");
|
||||
let mut shared_state = self
|
||||
.window
|
||||
.lock_shared_state("window_did_fail_to_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
shared_state.target_fullscreen = None;
|
||||
if self.state.initial_fullscreen.get() {
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
&*self.window,
|
||||
performSelector: sel!(toggleFullScreen:),
|
||||
withObject: ptr::null::<AnyObject>(),
|
||||
afterDelay: 0.5,
|
||||
];
|
||||
};
|
||||
} else {
|
||||
self.window.restore_state_from_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when the occlusion state of the window changes
|
||||
#[method(windowDidChangeOcclusionState:)]
|
||||
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeOcclusionState:");
|
||||
self.queue_event(WindowEvent::Occluded(
|
||||
!self
|
||||
.window
|
||||
.occlusionState()
|
||||
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible),
|
||||
))
|
||||
}
|
||||
|
||||
unsafe impl WinitWindowDelegate {
|
||||
// Observe theme change
|
||||
#[method(effectiveAppearanceDidChange:)]
|
||||
fn effective_appearance_did_change(&self, sender: Option<&AnyObject>) {
|
||||
@@ -402,8 +391,10 @@ declare_class!(
|
||||
|
||||
#[method(effectiveAppearanceDidChangedOnMainThread:)]
|
||||
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) {
|
||||
let theme = get_ns_theme();
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let theme = get_ns_theme(mtm);
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("effective_appearance_did_change");
|
||||
let current_theme = shared_state.current_theme;
|
||||
@@ -413,71 +404,80 @@ declare_class!(
|
||||
self.queue_event(WindowEvent::ThemeChanged(theme));
|
||||
}
|
||||
}
|
||||
|
||||
#[method(windowDidChangeScreen:)]
|
||||
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeScreen:");
|
||||
let is_simple_fullscreen = self
|
||||
.window
|
||||
.lock_shared_state("window_did_change_screen")
|
||||
.is_simple_fullscreen;
|
||||
if is_simple_fullscreen {
|
||||
if let Some(screen) = self.window.screen() {
|
||||
self.window.setFrame_display(screen.frame(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitWindowDelegate {
|
||||
pub fn new(window: &WinitWindow, initial_fullscreen: bool) -> Id<Self> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
Self::alloc(),
|
||||
initWithWindow: window,
|
||||
initialFullscreen: initial_fullscreen,
|
||||
]
|
||||
let mtm = MainThreadMarker::from(window);
|
||||
let scale_factor = window.scale_factor();
|
||||
let this = mtm.alloc().set_ivars(State {
|
||||
window: window.retain(),
|
||||
initial_fullscreen: Cell::new(initial_fullscreen),
|
||||
previous_position: Cell::new(None),
|
||||
previous_scale_factor: Cell::new(scale_factor),
|
||||
});
|
||||
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
|
||||
if scale_factor != 1.0 {
|
||||
this.queue_static_scale_factor_changed_event();
|
||||
}
|
||||
window.setDelegate(Some(ProtocolObject::from_ref(&*this)));
|
||||
|
||||
// Enable theme change event
|
||||
let notification_center: Id<AnyObject> =
|
||||
unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] };
|
||||
let notification_name = NSString::from_str("AppleInterfaceThemeChangedNotification");
|
||||
let _: () = unsafe {
|
||||
msg_send![
|
||||
¬ification_center,
|
||||
addObserver: &*this
|
||||
selector: sel!(effectiveAppearanceDidChange:)
|
||||
name: &*notification_name
|
||||
object: ptr::null::<AnyObject>()
|
||||
]
|
||||
};
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
||||
let event = Event::WindowEvent {
|
||||
window_id: WindowId(self.window.id()),
|
||||
window_id: WindowId(self.ivars().window.id()),
|
||||
event,
|
||||
};
|
||||
AppState::queue_event(event);
|
||||
}
|
||||
|
||||
fn queue_static_scale_factor_changed_event(&self) {
|
||||
let scale_factor = self.window.scale_factor();
|
||||
if scale_factor == self.state.previous_scale_factor.get() {
|
||||
let scale_factor = self.ivars().window.scale_factor();
|
||||
if scale_factor == self.ivars().previous_scale_factor.get() {
|
||||
return;
|
||||
};
|
||||
|
||||
self.state.previous_scale_factor.set(scale_factor);
|
||||
self.ivars().previous_scale_factor.set(scale_factor);
|
||||
let suggested_size = self.view_size();
|
||||
AppState::queue_static_scale_factor_changed_event(
|
||||
self.window.clone(),
|
||||
self.ivars().window.clone(),
|
||||
suggested_size.to_physical(scale_factor),
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
|
||||
fn emit_move_event(&self) {
|
||||
let rect = self.window.frame();
|
||||
let rect = self.ivars().window.frame();
|
||||
let x = rect.origin.x as f64;
|
||||
let y = util::bottom_left_to_top_left(rect);
|
||||
if self.state.previous_position.get() != Some((x, y)) {
|
||||
self.state.previous_position.set(Some((x, y)));
|
||||
let scale_factor = self.window.scale_factor();
|
||||
if self.ivars().previous_position.get() != Some((x, y)) {
|
||||
self.ivars().previous_position.set(Some((x, y)));
|
||||
let scale_factor = self.ivars().window.scale_factor();
|
||||
let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
|
||||
self.queue_event(WindowEvent::Moved(physical_pos));
|
||||
}
|
||||
}
|
||||
|
||||
fn view_size(&self) -> LogicalSize<f64> {
|
||||
let size = self.window.contentView().frame().size;
|
||||
let size = self.ivars().window.contentView().unwrap().frame().size;
|
||||
LogicalSize::new(size.width as f64, size.height as f64)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
mod event_loop;
|
||||
|
||||
pub use self::window::Window;
|
||||
pub(crate) use self::window::{OwnedWindowHandle, Window};
|
||||
mod window;
|
||||
|
||||
struct RedoxSocket {
|
||||
@@ -193,6 +193,8 @@ impl Display for OsError {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
|
||||
@@ -12,8 +12,8 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket,
|
||||
TimeSocket, WindowId, WindowProperties,
|
||||
EventLoopWindowTarget, MonitorHandle, PlatformCustomCursor,
|
||||
PlatformSpecificWindowBuilderAttributes, RedoxSocket, TimeSocket, WindowId, WindowProperties,
|
||||
};
|
||||
|
||||
// These values match the values uses in the `window_new` function in orbital:
|
||||
@@ -25,6 +25,18 @@ const ORBITAL_FLAG_BORDERLESS: char = 'l';
|
||||
const ORBITAL_FLAG_RESIZABLE: char = 'r';
|
||||
const ORBITAL_FLAG_TRANSPARENT: char = 't';
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OwnedWindowHandle {}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
// Parent windows are currently unsupported, though owned window
|
||||
// handles would be implementable.
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
window_socket: Arc<RedoxSocket>,
|
||||
redraws: Arc<Mutex<VecDeque<WindowId>>>,
|
||||
@@ -352,6 +364,8 @@ impl Window {
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
|
||||
|
||||
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
|
||||
@@ -6,65 +6,67 @@ use std::sync::mpsc::{self, Receiver, RecvError, SendError, Sender, TryRecvError
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::task::Poll;
|
||||
|
||||
// NOTE: This channel doesn't wake up when all senders or receivers are
|
||||
// dropped. This is acceptable as long as it's only used in `Dispatcher`, which
|
||||
// has it's own `Drop` behavior.
|
||||
|
||||
pub fn channel<T>() -> (AsyncSender<T>, AsyncReceiver<T>) {
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
let sender = Arc::new(Mutex::new(sender));
|
||||
let inner = Arc::new(Inner {
|
||||
let shared = Arc::new(Shared {
|
||||
closed: AtomicBool::new(false),
|
||||
waker: AtomicWaker::new(),
|
||||
});
|
||||
|
||||
let sender = AsyncSender {
|
||||
sender,
|
||||
inner: Arc::clone(&inner),
|
||||
};
|
||||
let sender = AsyncSender(Arc::new(SenderInner {
|
||||
sender: Mutex::new(sender),
|
||||
shared: Arc::clone(&shared),
|
||||
}));
|
||||
let receiver = AsyncReceiver {
|
||||
receiver: Rc::new(receiver),
|
||||
inner,
|
||||
shared,
|
||||
};
|
||||
|
||||
(sender, receiver)
|
||||
}
|
||||
|
||||
pub struct AsyncSender<T> {
|
||||
pub struct AsyncSender<T>(Arc<SenderInner<T>>);
|
||||
|
||||
struct SenderInner<T> {
|
||||
// We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't
|
||||
// be accessed on the main thread, as it could block. Additionally we need
|
||||
// to wrap it in an `Arc` to make it clonable on the main thread without
|
||||
// to wrap `Sender` in an `Arc` to make it clonable on the main thread without
|
||||
// having to block.
|
||||
sender: Arc<Mutex<Sender<T>>>,
|
||||
inner: Arc<Inner>,
|
||||
sender: Mutex<Sender<T>>,
|
||||
shared: Arc<Shared>,
|
||||
}
|
||||
|
||||
impl<T> AsyncSender<T> {
|
||||
pub fn send(&self, event: T) -> Result<(), SendError<T>> {
|
||||
self.sender.lock().unwrap().send(event)?;
|
||||
self.inner.waker.wake();
|
||||
self.0.sender.lock().unwrap().send(event)?;
|
||||
self.0.shared.waker.wake();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
self.inner.closed.store(true, Ordering::Relaxed);
|
||||
self.inner.waker.wake()
|
||||
impl<T> SenderInner<T> {
|
||||
fn close(&self) {
|
||||
self.shared.closed.store(true, Ordering::Relaxed);
|
||||
self.shared.waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for AsyncSender<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
sender: Arc::clone(&self.sender),
|
||||
inner: Arc::clone(&self.inner),
|
||||
}
|
||||
Self(Arc::clone(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for SenderInner<T> {
|
||||
fn drop(&mut self) {
|
||||
self.close();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AsyncReceiver<T> {
|
||||
receiver: Rc<Receiver<T>>,
|
||||
inner: Arc<Inner>,
|
||||
shared: Arc<Shared>,
|
||||
}
|
||||
|
||||
impl<T> AsyncReceiver<T> {
|
||||
@@ -72,12 +74,12 @@ impl<T> AsyncReceiver<T> {
|
||||
future::poll_fn(|cx| match self.receiver.try_recv() {
|
||||
Ok(event) => Poll::Ready(Ok(event)),
|
||||
Err(TryRecvError::Empty) => {
|
||||
self.inner.waker.register(cx.waker());
|
||||
self.shared.waker.register(cx.waker());
|
||||
|
||||
match self.receiver.try_recv() {
|
||||
Ok(event) => Poll::Ready(Ok(event)),
|
||||
Err(TryRecvError::Empty) => {
|
||||
if self.inner.closed.load(Ordering::Relaxed) {
|
||||
if self.shared.closed.load(Ordering::Relaxed) {
|
||||
Poll::Ready(Err(RecvError))
|
||||
} else {
|
||||
Poll::Pending
|
||||
@@ -104,12 +106,18 @@ impl<T> Clone for AsyncReceiver<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
receiver: Rc::clone(&self.receiver),
|
||||
inner: Arc::clone(&self.inner),
|
||||
shared: Arc::clone(&self.shared),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
impl<T> Drop for AsyncReceiver<T> {
|
||||
fn drop(&mut self) {
|
||||
self.shared.closed.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
struct Shared {
|
||||
closed: AtomicBool,
|
||||
waker: AtomicWaker,
|
||||
}
|
||||
|
||||
@@ -82,12 +82,6 @@ impl<T> Dispatcher<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Dispatcher<T> {
|
||||
fn drop(&mut self) {
|
||||
self.0.with_sender_data(|sender| sender.close())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DispatchRunner<T: 'static> {
|
||||
wrapper: Wrapper<true, T, AsyncSender<Closure<T>>, Closure<T>>,
|
||||
receiver: AsyncReceiver<Closure<T>>,
|
||||
|
||||
@@ -3,7 +3,7 @@ mod dispatcher;
|
||||
mod waker;
|
||||
mod wrapper;
|
||||
|
||||
use self::channel::{channel, AsyncReceiver, AsyncSender};
|
||||
pub use self::channel::{channel, AsyncReceiver, AsyncSender};
|
||||
pub use self::dispatcher::{DispatchRunner, Dispatcher};
|
||||
pub use self::waker::{Waker, WakerSpawner};
|
||||
use self::wrapper::Wrapper;
|
||||
|
||||
579
src/platform_impl/web/cursor.rs
Normal file
579
src/platform_impl/web/cursor.rs
Normal file
@@ -0,0 +1,579 @@
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
future,
|
||||
hash::{Hash, Hasher},
|
||||
mem,
|
||||
ops::DerefMut,
|
||||
rc::{self, Rc},
|
||||
sync::{self, Arc},
|
||||
task::{Poll, Waker},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cursor::{BadImage, CursorImage},
|
||||
platform_impl::platform::r#async,
|
||||
};
|
||||
use cursor_icon::CursorIcon;
|
||||
use once_cell::sync::Lazy;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{
|
||||
Blob, Document, HtmlCanvasElement, HtmlImageElement, ImageBitmap, ImageBitmapOptions,
|
||||
ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window,
|
||||
};
|
||||
|
||||
use self::thread_safe::ThreadSafe;
|
||||
|
||||
use super::{backend::Style, r#async::AsyncSender, EventLoopWindowTarget};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum CustomCursorBuilder {
|
||||
Image(CursorImage),
|
||||
Url {
|
||||
url: String,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
},
|
||||
}
|
||||
|
||||
impl CustomCursorBuilder {
|
||||
pub fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<CustomCursorBuilder, BadImage> {
|
||||
Ok(CustomCursorBuilder::Image(CursorImage::from_rgba(
|
||||
rgba, width, height, hotspot_x, hotspot_y,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CustomCursor(Arc<Inner>);
|
||||
|
||||
impl Hash for CustomCursor {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
Arc::as_ptr(&self.0).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CustomCursor {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for CustomCursor {}
|
||||
|
||||
impl CustomCursor {
|
||||
pub(crate) fn build<T>(
|
||||
builder: CustomCursorBuilder,
|
||||
window_target: &EventLoopWindowTarget<T>,
|
||||
) -> Self {
|
||||
Lazy::force(&DROP_HANDLER);
|
||||
|
||||
Self(match builder {
|
||||
CustomCursorBuilder::Image(image) => ImageState::from_rgba(
|
||||
window_target.runner.window(),
|
||||
window_target.runner.document().clone(),
|
||||
&image,
|
||||
),
|
||||
CustomCursorBuilder::Url {
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
} => ImageState::from_url(url, hotspot_x, hotspot_y),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner(Option<ThreadSafe<RefCell<ImageState>>>);
|
||||
|
||||
static DROP_HANDLER: Lazy<AsyncSender<ThreadSafe<RefCell<ImageState>>>> = Lazy::new(|| {
|
||||
let (sender, receiver) = r#async::channel();
|
||||
wasm_bindgen_futures::spawn_local(async move { while receiver.next().await.is_ok() {} });
|
||||
|
||||
sender
|
||||
});
|
||||
|
||||
impl Inner {
|
||||
fn new() -> Arc<Self> {
|
||||
Arc::new(Inner(Some(ThreadSafe::new(RefCell::new(
|
||||
ImageState::Loading(None),
|
||||
)))))
|
||||
}
|
||||
|
||||
fn get(&self) -> &RefCell<ImageState> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.expect("value has accidently already been dropped")
|
||||
.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
let value = self
|
||||
.0
|
||||
.take()
|
||||
.expect("value has accidently already been dropped");
|
||||
|
||||
if !value.in_origin_thread() {
|
||||
DROP_HANDLER
|
||||
.send(value)
|
||||
.expect("sender dropped in main thread")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CursorState(Rc<RefCell<State>>);
|
||||
|
||||
impl CursorState {
|
||||
pub fn new(style: Style) -> Self {
|
||||
Self(Rc::new(RefCell::new(State {
|
||||
style,
|
||||
visible: true,
|
||||
cursor: SelectedCursor::default(),
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
let mut this = self.0.borrow_mut();
|
||||
|
||||
if let SelectedCursor::ImageLoading { state, .. } = &this.cursor {
|
||||
if let ImageState::Loading(state) = state.get().borrow_mut().deref_mut() {
|
||||
state.take();
|
||||
}
|
||||
}
|
||||
|
||||
this.cursor = SelectedCursor::Named(cursor);
|
||||
this.set_style();
|
||||
}
|
||||
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
let mut this = self.0.borrow_mut();
|
||||
|
||||
match cursor.0.get().borrow_mut().deref_mut() {
|
||||
ImageState::Loading(state) => {
|
||||
this.cursor = SelectedCursor::ImageLoading {
|
||||
state: cursor.0.clone(),
|
||||
previous: mem::take(&mut this.cursor).into(),
|
||||
};
|
||||
*state = Some(Rc::downgrade(&self.0));
|
||||
}
|
||||
ImageState::Failed => log::error!("tried to load invalid cursor"),
|
||||
ImageState::Ready(image) => {
|
||||
this.cursor = SelectedCursor::ImageReady(image.clone());
|
||||
this.set_style();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
if !visible && state.visible {
|
||||
state.visible = false;
|
||||
state.style.set("cursor", "none");
|
||||
} else if visible && !state.visible {
|
||||
state.visible = true;
|
||||
state.set_style();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
style: Style,
|
||||
visible: bool,
|
||||
cursor: SelectedCursor,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn set_style(&self) {
|
||||
if self.visible {
|
||||
let value = match &self.cursor {
|
||||
SelectedCursor::Named(icon) => icon.name(),
|
||||
SelectedCursor::ImageLoading { previous, .. } => previous.style(),
|
||||
SelectedCursor::ImageReady(image) => &image.style,
|
||||
};
|
||||
|
||||
self.style.set("cursor", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SelectedCursor {
|
||||
Named(CursorIcon),
|
||||
ImageLoading {
|
||||
state: Arc<Inner>,
|
||||
previous: Previous,
|
||||
},
|
||||
ImageReady(Rc<Image>),
|
||||
}
|
||||
|
||||
impl Default for SelectedCursor {
|
||||
fn default() -> Self {
|
||||
Self::Named(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Previous> for SelectedCursor {
|
||||
fn from(previous: Previous) -> Self {
|
||||
match previous {
|
||||
Previous::Named(icon) => Self::Named(icon),
|
||||
Previous::Image(image) => Self::ImageReady(image),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Previous {
|
||||
Named(CursorIcon),
|
||||
Image(Rc<Image>),
|
||||
}
|
||||
|
||||
impl Previous {
|
||||
fn style(&self) -> &str {
|
||||
match self {
|
||||
Previous::Named(icon) => icon.name(),
|
||||
Previous::Image(image) => &image.style,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SelectedCursor> for Previous {
|
||||
fn from(value: SelectedCursor) -> Self {
|
||||
match value {
|
||||
SelectedCursor::Named(icon) => Self::Named(icon),
|
||||
SelectedCursor::ImageLoading { previous, .. } => previous,
|
||||
SelectedCursor::ImageReady(image) => Self::Image(image),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ImageState {
|
||||
Loading(Option<rc::Weak<RefCell<State>>>),
|
||||
Failed,
|
||||
Ready(Rc<Image>),
|
||||
}
|
||||
|
||||
impl ImageState {
|
||||
fn from_rgba(window: &Window, document: Document, image: &CursorImage) -> Arc<Inner> {
|
||||
// 1. Create an `ImageData` from the RGBA data.
|
||||
// 2. Create an `ImageBitmap` from the `ImageData`.
|
||||
// 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
|
||||
// 4. Create a `Blob` from the `HTMLCanvasElement`.
|
||||
// 5. Create an object URL from the `Blob`.
|
||||
// 6. Decode the image on an `HTMLImageElement` from the URL.
|
||||
// 7. Change the `CursorState` if queued.
|
||||
|
||||
// 1. Create an `ImageData` from the RGBA data.
|
||||
// Adapted from https://github.com/rust-windowing/softbuffer/blob/ab7688e2ed2e2eca51b3c4e1863a5bd7fe85800e/src/web.rs#L196-L223
|
||||
#[cfg(target_feature = "atomics")]
|
||||
// Can't share `SharedArrayBuffer` with `ImageData`.
|
||||
let result = {
|
||||
use js_sys::{Uint8Array, Uint8ClampedArray};
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = ImageData)]
|
||||
type ImageDataExt;
|
||||
#[wasm_bindgen(catch, constructor, js_class = ImageData)]
|
||||
fn new(array: Uint8ClampedArray, sw: u32) -> Result<ImageDataExt, JsValue>;
|
||||
}
|
||||
|
||||
let array = Uint8Array::new_with_length(image.rgba.len() as u32);
|
||||
array.copy_from(&image.rgba);
|
||||
let array = Uint8ClampedArray::new(&array);
|
||||
ImageDataExt::new(array, image.width as u32)
|
||||
.map(JsValue::from)
|
||||
.map(ImageData::unchecked_from_js)
|
||||
};
|
||||
#[cfg(not(target_feature = "atomics"))]
|
||||
let result = ImageData::new_with_u8_clamped_array(
|
||||
wasm_bindgen::Clamped(&image.rgba),
|
||||
image.width as u32,
|
||||
);
|
||||
let image_data = result.expect("found wrong image size");
|
||||
|
||||
// 2. Create an `ImageBitmap` from the `ImageData`.
|
||||
//
|
||||
// We call `createImageBitmap()` before spawning the future,
|
||||
// to not have to clone the image buffer.
|
||||
let mut options = ImageBitmapOptions::new();
|
||||
options.premultiply_alpha(PremultiplyAlpha::None);
|
||||
let bitmap = JsFuture::from(
|
||||
window
|
||||
.create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options)
|
||||
.expect("unexpected exception in `createImageBitmap()`"),
|
||||
);
|
||||
|
||||
let this = Inner::new();
|
||||
|
||||
wasm_bindgen_futures::spawn_local({
|
||||
let weak = Arc::downgrade(&this);
|
||||
let CursorImage {
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
..
|
||||
} = *image;
|
||||
async move {
|
||||
// Keep checking if all references are dropped between every `await` call.
|
||||
if weak.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let bitmap: ImageBitmap = bitmap
|
||||
.await
|
||||
.expect("found invalid state in `ImageData`")
|
||||
.unchecked_into();
|
||||
|
||||
if weak.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let canvas: HtmlCanvasElement = document
|
||||
.create_element("canvas")
|
||||
.expect("invalid tag name")
|
||||
.unchecked_into();
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
canvas.set_width(width as u32);
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
canvas.set_height(height as u32);
|
||||
|
||||
// 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
|
||||
let context: ImageBitmapRenderingContext = canvas
|
||||
.get_context("bitmaprenderer")
|
||||
.expect("unexpected exception in `HTMLCanvasElement.getContext()`")
|
||||
.expect("`bitmaprenderer` context unsupported")
|
||||
.unchecked_into();
|
||||
context.transfer_from_image_bitmap(&bitmap);
|
||||
|
||||
// 4. Create a `Blob` from the `HTMLCanvasElement`.
|
||||
//
|
||||
// To keep the `Closure` alive until `HTMLCanvasElement.toBlob()` is done,
|
||||
// we do the whole `Waker` strategy. Commonly on `Drop` the callback is aborted,
|
||||
// but it would increase complexity and isn't possible in this case.
|
||||
// Keep in mind that `HTMLCanvasElement.toBlob()` can call the callback immediately.
|
||||
let value = Rc::new(RefCell::new(None));
|
||||
let waker = Rc::new(RefCell::<Option<Waker>>::new(None));
|
||||
let callback = Closure::once({
|
||||
let value = value.clone();
|
||||
let waker = waker.clone();
|
||||
move |blob: Option<Blob>| {
|
||||
*value.borrow_mut() = Some(blob);
|
||||
if let Some(waker) = waker.borrow_mut().take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
});
|
||||
canvas
|
||||
.to_blob(callback.as_ref().unchecked_ref())
|
||||
.expect("failed with `SecurityError` despite only source coming from memory");
|
||||
let blob = future::poll_fn(|cx| {
|
||||
if let Some(blob) = value.borrow_mut().take() {
|
||||
Poll::Ready(blob)
|
||||
} else {
|
||||
*waker.borrow_mut() = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
let url = {
|
||||
let Some(this) = weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let mut this = this.get().borrow_mut();
|
||||
|
||||
let Some(blob) = blob else {
|
||||
log::error!("creating custom cursor failed");
|
||||
let ImageState::Loading(state) = this.deref_mut() else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
let state = state.take();
|
||||
*this = ImageState::Failed;
|
||||
|
||||
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
|
||||
let mut state = state.borrow_mut();
|
||||
let SelectedCursor::ImageLoading { previous, .. } =
|
||||
mem::take(&mut state.cursor)
|
||||
else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
state.cursor = previous.into();
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
// 5. Create an object URL from the `Blob`.
|
||||
Url::create_object_url_with_blob(&blob)
|
||||
.expect("unexpected exception in `URL.createObjectURL()`")
|
||||
};
|
||||
|
||||
Self::decode(weak, url, true, hotspot_x, hotspot_y).await;
|
||||
}
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Arc<Inner> {
|
||||
let this = Inner::new();
|
||||
wasm_bindgen_futures::spawn_local(Self::decode(
|
||||
Arc::downgrade(&this),
|
||||
url,
|
||||
false,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
async fn decode(
|
||||
weak: sync::Weak<Inner>,
|
||||
url: String,
|
||||
object: bool,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) {
|
||||
if weak.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Decode the image on an `HTMLImageElement` from the URL.
|
||||
let image =
|
||||
HtmlImageElement::new().expect("unexpected exception in `new HtmlImageElement`");
|
||||
image.set_src(&url);
|
||||
let result = JsFuture::from(image.decode()).await;
|
||||
|
||||
let Some(this) = weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let mut this = this.get().borrow_mut();
|
||||
|
||||
let ImageState::Loading(state) = this.deref_mut() else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
let state = state.take();
|
||||
|
||||
if let Err(error) = result {
|
||||
log::error!("creating custom cursor failed: {error:?}");
|
||||
*this = ImageState::Failed;
|
||||
|
||||
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
|
||||
let mut state = state.borrow_mut();
|
||||
let SelectedCursor::ImageLoading { previous, .. } = mem::take(&mut state.cursor)
|
||||
else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
state.cursor = previous.into();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let image = Image::new(url, object, image, hotspot_x, hotspot_y);
|
||||
|
||||
// 7. Change the `CursorState` if queued.
|
||||
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
|
||||
let mut state = state.borrow_mut();
|
||||
state.cursor = SelectedCursor::ImageReady(image.clone());
|
||||
state.set_style();
|
||||
}
|
||||
|
||||
*this = ImageState::Ready(image);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Image {
|
||||
style: String,
|
||||
url: String,
|
||||
object: bool,
|
||||
_image: HtmlImageElement,
|
||||
}
|
||||
|
||||
impl Drop for Image {
|
||||
fn drop(&mut self) {
|
||||
if self.object {
|
||||
Url::revoke_object_url(&self.url)
|
||||
.expect("unexpected exception in `URL.revokeObjectURL()`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Image {
|
||||
fn new(
|
||||
url: String,
|
||||
object: bool,
|
||||
image: HtmlImageElement,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Rc<Self> {
|
||||
let style = format!("url({url}) {hotspot_x} {hotspot_y}, auto");
|
||||
|
||||
Rc::new(Self {
|
||||
style,
|
||||
url,
|
||||
object,
|
||||
_image: image,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
mod thread_safe {
|
||||
use std::mem;
|
||||
use std::thread::{self, ThreadId};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadSafe<T> {
|
||||
origin_thread: ThreadId,
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl<T> ThreadSafe<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
origin_thread: thread::current().id(),
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &T {
|
||||
if self.origin_thread == thread::current().id() {
|
||||
&self.value
|
||||
} else {
|
||||
panic!("value not accessible outside its origin thread")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_origin_thread(&self) -> bool {
|
||||
self.origin_thread == thread::current().id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for ThreadSafe<T> {
|
||||
fn drop(&mut self) {
|
||||
if mem::needs_drop::<T>() && self.origin_thread != thread::current().id() {
|
||||
panic!("value can't be dropped outside its origin thread")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for ThreadSafe<T> {}
|
||||
unsafe impl<T> Sync for ThreadSafe<T> {}
|
||||
}
|
||||
@@ -67,7 +67,6 @@ pub struct Execution {
|
||||
on_key_press: OnEventHandle<KeyboardEvent>,
|
||||
on_key_release: OnEventHandle<KeyboardEvent>,
|
||||
on_visibility_change: OnEventHandle<web_sys::Event>,
|
||||
on_touch_end: OnEventHandle<web_sys::Event>,
|
||||
}
|
||||
|
||||
enum RunnerEnum {
|
||||
@@ -181,7 +180,6 @@ impl Shared {
|
||||
on_key_press: RefCell::new(None),
|
||||
on_key_release: RefCell::new(None),
|
||||
on_visibility_change: RefCell::new(None),
|
||||
on_touch_end: RefCell::new(None),
|
||||
}
|
||||
}))
|
||||
}
|
||||
@@ -342,8 +340,6 @@ impl Shared {
|
||||
self.window().clone(),
|
||||
"pointerdown",
|
||||
Closure::new(move |event: PointerEvent| {
|
||||
runner.transient_activation();
|
||||
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
@@ -367,8 +363,6 @@ impl Shared {
|
||||
self.window().clone(),
|
||||
"pointerup",
|
||||
Closure::new(move |event: PointerEvent| {
|
||||
runner.transient_activation();
|
||||
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
@@ -392,8 +386,6 @@ impl Shared {
|
||||
self.window().clone(),
|
||||
"keydown",
|
||||
Closure::new(move |event: KeyboardEvent| {
|
||||
runner.transient_activation();
|
||||
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
@@ -452,14 +444,6 @@ impl Shared {
|
||||
}
|
||||
}),
|
||||
));
|
||||
let runner = self.clone();
|
||||
*self.0.on_touch_end.borrow_mut() = Some(EventListenerHandle::new(
|
||||
self.window().clone(),
|
||||
"touchend",
|
||||
Closure::new(move |_| {
|
||||
runner.transient_activation();
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
// Generate a strictly increasing ID
|
||||
@@ -788,18 +772,6 @@ impl Shared {
|
||||
}
|
||||
}
|
||||
|
||||
fn transient_activation(&self) {
|
||||
self.0
|
||||
.all_canvases
|
||||
.borrow()
|
||||
.iter()
|
||||
.for_each(|(_, canvas, _)| {
|
||||
if let Some(canvas) = canvas.upgrade() {
|
||||
canvas.borrow().transient_activation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn event_loop_recreation(&self, allow: bool) {
|
||||
self.0.event_loop_recreation.set(allow)
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
) {
|
||||
let canvas_clone = canvas.clone();
|
||||
let mut canvas = canvas.borrow_mut();
|
||||
#[cfg(any(feature = "rwh_04", feature = "rwh_05"))]
|
||||
canvas.set_attribute("data-raw-handle", &id.0.to_string());
|
||||
|
||||
canvas.on_touch_start(prevent_default);
|
||||
@@ -648,7 +649,7 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id)));
|
||||
|
||||
canvas.on_touch_end();
|
||||
canvas.on_context_menu(prevent_default);
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
// compliant way.
|
||||
|
||||
mod r#async;
|
||||
mod cursor;
|
||||
mod device;
|
||||
mod error;
|
||||
mod event_loop;
|
||||
@@ -34,8 +35,12 @@ pub(crate) use self::event_loop::{
|
||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
|
||||
};
|
||||
pub use self::monitor::{MonitorHandle, VideoMode};
|
||||
pub use self::window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId};
|
||||
pub(crate) use self::window::{
|
||||
OwnedWindowHandle, PlatformSpecificWindowBuilderAttributes, Window, WindowId,
|
||||
};
|
||||
|
||||
pub(crate) use self::keyboard::KeyEventExtra;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
pub(crate) use cursor::CustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use cursor::CustomCursorBuilder as PlatformCustomCursorBuilder;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::cell::Cell;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use smol_str::SmolStr;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use web_sys::{
|
||||
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, WheelEvent,
|
||||
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
|
||||
PointerEvent, WheelEvent,
|
||||
};
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
@@ -18,11 +20,10 @@ use crate::window::{WindowAttributes, WindowId as RootWindowId};
|
||||
use super::super::WindowId;
|
||||
use super::animation_frame::AnimationFrameHandler;
|
||||
use super::event_handle::EventListenerHandle;
|
||||
use super::fullscreen::FullscreenHandler;
|
||||
use super::intersection_handle::IntersectionObserverHandle;
|
||||
use super::media_query_handle::MediaQueryListHandle;
|
||||
use super::pointer::PointerHandler;
|
||||
use super::{event, ButtonsState, ResizeScaleHandle};
|
||||
use super::{event, fullscreen, ButtonsState, ResizeScaleHandle};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Canvas {
|
||||
@@ -42,17 +43,24 @@ pub struct Canvas {
|
||||
on_intersect: Option<IntersectionObserverHandle>,
|
||||
animation_frame_handler: AnimationFrameHandler,
|
||||
on_touch_end: Option<EventListenerHandle<dyn FnMut(Event)>>,
|
||||
on_context_menu: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||
}
|
||||
|
||||
pub struct Common {
|
||||
pub window: web_sys::Window,
|
||||
pub document: Document,
|
||||
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
|
||||
pub raw: HtmlCanvasElement,
|
||||
style: CssStyleDeclaration,
|
||||
/// Note: this is read-only because we use a pointer to this for [`WindowHandle`](rwh_06::WindowHandle).
|
||||
raw: Rc<HtmlCanvasElement>,
|
||||
style: Style,
|
||||
old_size: Rc<Cell<PhysicalSize<u32>>>,
|
||||
current_size: Rc<Cell<PhysicalSize<u32>>>,
|
||||
fullscreen_handler: Rc<FullscreenHandler>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Style {
|
||||
read: CssStyleDeclaration,
|
||||
write: CssStyleDeclaration,
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
@@ -90,21 +98,15 @@ impl Canvas {
|
||||
.map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?;
|
||||
}
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let style = window
|
||||
.get_computed_style(&canvas)
|
||||
.expect("Failed to obtain computed style")
|
||||
// this can't fail: we aren't using a pseudo-element
|
||||
.expect("Invalid pseudo-element");
|
||||
let style = Style::new(&window, &canvas);
|
||||
|
||||
let common = Common {
|
||||
window: window.clone(),
|
||||
document: document.clone(),
|
||||
raw: canvas.clone(),
|
||||
raw: Rc::new(canvas.clone()),
|
||||
style,
|
||||
old_size: Rc::default(),
|
||||
current_size: Rc::default(),
|
||||
fullscreen_handler: Rc::new(FullscreenHandler::new(document.clone(), canvas.clone())),
|
||||
};
|
||||
|
||||
if let Some(size) = attr.inner_size {
|
||||
@@ -128,7 +130,7 @@ impl Canvas {
|
||||
}
|
||||
|
||||
if attr.fullscreen.0.is_some() {
|
||||
common.fullscreen_handler.request_fullscreen();
|
||||
fullscreen::request_fullscreen(&document, &canvas);
|
||||
}
|
||||
|
||||
if attr.active {
|
||||
@@ -152,6 +154,7 @@ impl Canvas {
|
||||
on_intersect: None,
|
||||
animation_frame_handler: AnimationFrameHandler::new(window),
|
||||
on_touch_end: None,
|
||||
on_context_menu: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -178,9 +181,7 @@ impl Canvas {
|
||||
y: bounds.y(),
|
||||
};
|
||||
|
||||
if self.document().contains(Some(self.raw()))
|
||||
&& self.style().get_property_value("display").unwrap() != "none"
|
||||
{
|
||||
if self.document().contains(Some(self.raw())) && self.style().get("display") != "none" {
|
||||
position.x += super::style_size_property(self.style(), "border-left-width")
|
||||
+ super::style_size_property(self.style(), "padding-left");
|
||||
position.y += super::style_size_property(self.style(), "border-top-width")
|
||||
@@ -226,7 +227,7 @@ impl Canvas {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn style(&self) -> &CssStyleDeclaration {
|
||||
pub fn style(&self) -> &Style {
|
||||
&self.common.style
|
||||
}
|
||||
|
||||
@@ -282,7 +283,7 @@ impl Canvas {
|
||||
where
|
||||
F: 'static + FnMut(PhysicalKey, Key, Option<SmolStr>, KeyLocation, bool, ModifiersState),
|
||||
{
|
||||
self.on_keyboard_press = Some(self.common.add_transient_event(
|
||||
self.on_keyboard_press = Some(self.common.add_event(
|
||||
"keydown",
|
||||
move |event: KeyboardEvent| {
|
||||
if prevent_default {
|
||||
@@ -442,20 +443,27 @@ impl Canvas {
|
||||
self.animation_frame_handler.on_animation_frame(f)
|
||||
}
|
||||
|
||||
pub(crate) fn on_touch_end(&mut self) {
|
||||
self.on_touch_end = Some(self.common.add_transient_event("touchend", |_| {}));
|
||||
pub(crate) fn on_context_menu(&mut self, prevent_default: bool) {
|
||||
self.on_context_menu = Some(self.common.add_event(
|
||||
"contextmenu",
|
||||
move |event: PointerEvent| {
|
||||
if prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn request_fullscreen(&self) {
|
||||
self.common.fullscreen_handler.request_fullscreen()
|
||||
fullscreen::request_fullscreen(self.document(), self.raw());
|
||||
}
|
||||
|
||||
pub fn exit_fullscreen(&self) {
|
||||
self.common.fullscreen_handler.exit_fullscreen()
|
||||
fullscreen::exit_fullscreen(self.document(), self.raw());
|
||||
}
|
||||
|
||||
pub fn is_fullscreen(&self) -> bool {
|
||||
self.common.fullscreen_handler.is_fullscreen()
|
||||
fullscreen::is_fullscreen(self.document(), self.raw())
|
||||
}
|
||||
|
||||
pub fn request_animation_frame(&self) {
|
||||
@@ -506,10 +514,6 @@ impl Canvas {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn transient_activation(&self) {
|
||||
self.common.fullscreen_handler.transient_activation()
|
||||
}
|
||||
|
||||
pub fn remove_listeners(&mut self) {
|
||||
self.on_touch_start = None;
|
||||
self.on_focus = None;
|
||||
@@ -523,7 +527,7 @@ impl Canvas {
|
||||
self.on_intersect = None;
|
||||
self.animation_frame_handler.cancel();
|
||||
self.on_touch_end = None;
|
||||
self.common.fullscreen_handler.cancel();
|
||||
self.on_context_menu = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,29 +541,44 @@ impl Common {
|
||||
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
||||
F: 'static + FnMut(E),
|
||||
{
|
||||
EventListenerHandle::new(self.raw.clone(), event_name, Closure::new(handler))
|
||||
EventListenerHandle::new(self.raw.deref().clone(), event_name, Closure::new(handler))
|
||||
}
|
||||
|
||||
// The difference between add_event and add_user_event is that the latter has a special meaning
|
||||
// for browser security. A user event is a deliberate action by the user (like a mouse or key
|
||||
// press) and is the only time things like a fullscreen request may be successfully completed.)
|
||||
pub fn add_transient_event<E, F>(
|
||||
&self,
|
||||
event_name: &'static str,
|
||||
mut handler: F,
|
||||
) -> EventListenerHandle<dyn FnMut(E)>
|
||||
where
|
||||
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
||||
F: 'static + FnMut(E),
|
||||
{
|
||||
let fullscreen_handler = Rc::downgrade(&self.fullscreen_handler);
|
||||
|
||||
self.add_event(event_name, move |event: E| {
|
||||
handler(event);
|
||||
|
||||
if let Some(fullscreen_handler) = Weak::upgrade(&fullscreen_handler) {
|
||||
fullscreen_handler.transient_activation()
|
||||
}
|
||||
})
|
||||
pub fn raw(&self) -> &HtmlCanvasElement {
|
||||
&self.raw
|
||||
}
|
||||
}
|
||||
|
||||
impl Style {
|
||||
fn new(window: &web_sys::Window, canvas: &HtmlCanvasElement) -> Self {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let read = window
|
||||
.get_computed_style(canvas)
|
||||
.expect("Failed to obtain computed style")
|
||||
// this can't fail: we aren't using a pseudo-element
|
||||
.expect("Invalid pseudo-element");
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let write = canvas.style();
|
||||
|
||||
Self { read, write }
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, property: &str) -> String {
|
||||
self.read
|
||||
.get_property_value(property)
|
||||
.expect("Invalid property")
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&self, property: &str) {
|
||||
self.write
|
||||
.remove_property(property)
|
||||
.expect("Property is read only");
|
||||
}
|
||||
|
||||
pub(crate) fn set(&self, property: &str, value: &str) {
|
||||
self.write
|
||||
.set_property(property, value)
|
||||
.expect("Property is read only");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use crate::dpi::LogicalPosition;
|
||||
use crate::event::{MouseButton, MouseScrollDelta};
|
||||
use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
|
||||
|
||||
use once_cell::unsync::OnceCell;
|
||||
use smol_str::SmolStr;
|
||||
use std::cell::OnceCell;
|
||||
use std::convert::TryInto;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
|
||||
@@ -1,145 +1,91 @@
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::cell::OnceCell;
|
||||
|
||||
use js_sys::Promise;
|
||||
use once_cell::unsync::OnceCell;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{Document, Element, HtmlCanvasElement};
|
||||
|
||||
use super::EventListenerHandle;
|
||||
pub fn request_fullscreen(document: &Document, canvas: &HtmlCanvasElement) {
|
||||
if is_fullscreen(document, canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static FULLSCREEN_API_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(extends = HtmlCanvasElement)]
|
||||
type RequestFullscreen;
|
||||
|
||||
#[wasm_bindgen(method, js_name = requestFullscreen)]
|
||||
fn request_fullscreen(this: &RequestFullscreen) -> Promise;
|
||||
|
||||
#[wasm_bindgen(method, js_name = webkitRequestFullscreen)]
|
||||
fn webkit_request_fullscreen(this: &RequestFullscreen);
|
||||
}
|
||||
|
||||
let canvas: &RequestFullscreen = canvas.unchecked_ref();
|
||||
|
||||
if has_fullscreen_api_support(canvas) {
|
||||
thread_local! {
|
||||
static REJECT_HANDLER: Closure<dyn FnMut(JsValue)> = Closure::new(|_| ());
|
||||
}
|
||||
REJECT_HANDLER.with(|handler| {
|
||||
let _ = canvas.request_fullscreen().catch(handler);
|
||||
});
|
||||
} else {
|
||||
canvas.webkit_request_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FullscreenHandler {
|
||||
document: Document,
|
||||
canvas: HtmlCanvasElement,
|
||||
fullscreen_requested: Rc<Cell<bool>>,
|
||||
_fullscreen_change: EventListenerHandle<dyn FnMut()>,
|
||||
pub fn is_fullscreen(document: &Document, canvas: &HtmlCanvasElement) -> bool {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type FullscreenElement;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = webkitFullscreenElement)]
|
||||
fn webkit_fullscreen_element(this: &FullscreenElement) -> Option<Element>;
|
||||
}
|
||||
|
||||
let element = if has_fullscreen_api_support(canvas) {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
document.fullscreen_element()
|
||||
} else {
|
||||
let document: &FullscreenElement = document.unchecked_ref();
|
||||
document.webkit_fullscreen_element()
|
||||
};
|
||||
|
||||
match element {
|
||||
Some(element) => {
|
||||
let canvas: &Element = canvas;
|
||||
canvas == &element
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl FullscreenHandler {
|
||||
pub fn new(document: Document, canvas: HtmlCanvasElement) -> Self {
|
||||
let fullscreen_requested = Rc::new(Cell::new(false));
|
||||
let fullscreen_change = EventListenerHandle::new(
|
||||
canvas.clone(),
|
||||
if has_fullscreen_api_support(&canvas) {
|
||||
"fullscreenchange"
|
||||
} else {
|
||||
"webkitfullscreenchange"
|
||||
},
|
||||
Closure::new({
|
||||
let fullscreen_requested = fullscreen_requested.clone();
|
||||
move || {
|
||||
// It doesn't matter if the canvas entered or exitted fullscreen mode,
|
||||
// we don't want to request it again later.
|
||||
fullscreen_requested.set(false);
|
||||
}
|
||||
}),
|
||||
);
|
||||
pub fn exit_fullscreen(document: &Document, canvas: &HtmlCanvasElement) {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type ExitFullscreen;
|
||||
|
||||
Self {
|
||||
document,
|
||||
canvas,
|
||||
fullscreen_requested,
|
||||
_fullscreen_change: fullscreen_change,
|
||||
}
|
||||
#[wasm_bindgen(method, js_name = webkitExitFullscreen)]
|
||||
fn webkit_exit_fullscreen(this: &ExitFullscreen);
|
||||
}
|
||||
|
||||
fn internal_request_fullscreen(&self) {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type RequestFullscreen;
|
||||
|
||||
#[wasm_bindgen(method, js_name = requestFullscreen)]
|
||||
fn request_fullscreen(this: &RequestFullscreen) -> Promise;
|
||||
|
||||
#[wasm_bindgen(method, js_name = webkitRequestFullscreen)]
|
||||
fn webkit_request_fullscreen(this: &RequestFullscreen);
|
||||
}
|
||||
|
||||
let canvas: &RequestFullscreen = self.canvas.unchecked_ref();
|
||||
|
||||
if has_fullscreen_api_support(&self.canvas) {
|
||||
thread_local! {
|
||||
static REJECT_HANDLER: Closure<dyn FnMut(JsValue)> = Closure::new(|_| ());
|
||||
}
|
||||
REJECT_HANDLER.with(|handler| {
|
||||
let _ = canvas.request_fullscreen().catch(handler);
|
||||
});
|
||||
} else {
|
||||
canvas.webkit_request_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_fullscreen(&self) {
|
||||
if !self.is_fullscreen() {
|
||||
self.internal_request_fullscreen();
|
||||
self.fullscreen_requested.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transient_activation(&self) {
|
||||
if self.fullscreen_requested.get() {
|
||||
self.internal_request_fullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_fullscreen(&self) -> bool {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type FullscreenElement;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = webkitFullscreenElement)]
|
||||
fn webkit_fullscreen_element(this: &FullscreenElement) -> Option<Element>;
|
||||
}
|
||||
|
||||
let element = if has_fullscreen_api_support(&self.canvas) {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
self.document.fullscreen_element()
|
||||
} else {
|
||||
let document: &FullscreenElement = self.document.unchecked_ref();
|
||||
document.webkit_fullscreen_element()
|
||||
};
|
||||
|
||||
match element {
|
||||
Some(element) => {
|
||||
let canvas: &Element = &self.canvas;
|
||||
canvas == &element
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exit_fullscreen(&self) {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type ExitFullscreen;
|
||||
|
||||
#[wasm_bindgen(method, js_name = webkitExitFullscreen)]
|
||||
fn webkit_exit_fullscreen(this: &ExitFullscreen);
|
||||
}
|
||||
|
||||
if has_fullscreen_api_support(&self.canvas) {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
self.document.exit_fullscreen()
|
||||
} else {
|
||||
let document: &ExitFullscreen = self.document.unchecked_ref();
|
||||
document.webkit_exit_fullscreen()
|
||||
}
|
||||
|
||||
self.fullscreen_requested.set(false);
|
||||
}
|
||||
|
||||
pub fn cancel(&self) {
|
||||
self.fullscreen_requested.set(false);
|
||||
if has_fullscreen_api_support(canvas) {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
document.exit_fullscreen()
|
||||
} else {
|
||||
let document: &ExitFullscreen = document.unchecked_ref();
|
||||
document.webkit_exit_fullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
fn has_fullscreen_api_support(canvas: &HtmlCanvasElement) -> bool {
|
||||
thread_local! {
|
||||
static FULLSCREEN_API_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
}
|
||||
|
||||
FULLSCREEN_API_SUPPORT.with(|support| {
|
||||
*support.get_or_init(|| {
|
||||
#[wasm_bindgen]
|
||||
|
||||
@@ -10,6 +10,7 @@ mod resize_scaling;
|
||||
mod schedule;
|
||||
|
||||
pub use self::canvas::Canvas;
|
||||
pub use self::canvas::Style;
|
||||
pub use self::event::ButtonsState;
|
||||
pub use self::event_handle::EventListenerHandle;
|
||||
pub use self::resize_scaling::ResizeScaleHandle;
|
||||
@@ -17,9 +18,7 @@ pub use self::schedule::Schedule;
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use web_sys::{
|
||||
CssStyleDeclaration, Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState,
|
||||
};
|
||||
use web_sys::{Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState};
|
||||
|
||||
pub fn throw(msg: &str) {
|
||||
wasm_bindgen::throw_str(msg);
|
||||
@@ -51,8 +50,8 @@ pub fn scale_factor(window: &web_sys::Window) -> f64 {
|
||||
window.device_pixel_ratio()
|
||||
}
|
||||
|
||||
fn fix_canvas_size(style: &CssStyleDeclaration, mut size: LogicalSize<f64>) -> LogicalSize<f64> {
|
||||
if style.get_property_value("box-sizing").unwrap() == "border-box" {
|
||||
fn fix_canvas_size(style: &Style, mut size: LogicalSize<f64>) -> LogicalSize<f64> {
|
||||
if style.get("box-sizing") == "border-box" {
|
||||
size.width += style_size_property(style, "border-left-width")
|
||||
+ style_size_property(style, "border-right-width")
|
||||
+ style_size_property(style, "padding-left")
|
||||
@@ -69,76 +68,68 @@ fn fix_canvas_size(style: &CssStyleDeclaration, mut size: LogicalSize<f64>) -> L
|
||||
pub fn set_canvas_size(
|
||||
document: &Document,
|
||||
raw: &HtmlCanvasElement,
|
||||
style: &CssStyleDeclaration,
|
||||
style: &Style,
|
||||
new_size: LogicalSize<f64>,
|
||||
) {
|
||||
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" {
|
||||
if !document.contains(Some(raw)) || style.get("display") == "none" {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_size = fix_canvas_size(style, new_size);
|
||||
|
||||
set_canvas_style_property(raw, "width", &format!("{}px", new_size.width));
|
||||
set_canvas_style_property(raw, "height", &format!("{}px", new_size.height));
|
||||
style.set("width", &format!("{}px", new_size.width));
|
||||
style.set("height", &format!("{}px", new_size.height));
|
||||
}
|
||||
|
||||
pub fn set_canvas_min_size(
|
||||
document: &Document,
|
||||
raw: &HtmlCanvasElement,
|
||||
style: &CssStyleDeclaration,
|
||||
style: &Style,
|
||||
dimensions: Option<LogicalSize<f64>>,
|
||||
) {
|
||||
if let Some(dimensions) = dimensions {
|
||||
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" {
|
||||
if !document.contains(Some(raw)) || style.get("display") == "none" {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_size = fix_canvas_size(style, dimensions);
|
||||
|
||||
set_canvas_style_property(raw, "min-width", &format!("{}px", new_size.width));
|
||||
set_canvas_style_property(raw, "min-height", &format!("{}px", new_size.height));
|
||||
style.set("min-width", &format!("{}px", new_size.width));
|
||||
style.set("min-height", &format!("{}px", new_size.height));
|
||||
} else {
|
||||
style
|
||||
.remove_property("min-width")
|
||||
.expect("Property is read only");
|
||||
style
|
||||
.remove_property("min-height")
|
||||
.expect("Property is read only");
|
||||
style.remove("min-width");
|
||||
style.remove("min-height");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_canvas_max_size(
|
||||
document: &Document,
|
||||
raw: &HtmlCanvasElement,
|
||||
style: &CssStyleDeclaration,
|
||||
style: &Style,
|
||||
dimensions: Option<LogicalSize<f64>>,
|
||||
) {
|
||||
if let Some(dimensions) = dimensions {
|
||||
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" {
|
||||
if !document.contains(Some(raw)) || style.get("display") == "none" {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_size = fix_canvas_size(style, dimensions);
|
||||
|
||||
set_canvas_style_property(raw, "max-width", &format!("{}px", new_size.width));
|
||||
set_canvas_style_property(raw, "max-height", &format!("{}px", new_size.height));
|
||||
style.set("max-width", &format!("{}px", new_size.width));
|
||||
style.set("max-height", &format!("{}px", new_size.height));
|
||||
} else {
|
||||
style
|
||||
.remove_property("max-width")
|
||||
.expect("Property is read only");
|
||||
style
|
||||
.remove_property("max-height")
|
||||
.expect("Property is read only");
|
||||
style.remove("max-width");
|
||||
style.remove("max-height");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_canvas_position(
|
||||
document: &Document,
|
||||
raw: &HtmlCanvasElement,
|
||||
style: &CssStyleDeclaration,
|
||||
style: &Style,
|
||||
mut position: LogicalPosition<f64>,
|
||||
) {
|
||||
if document.contains(Some(raw)) && style.get_property_value("display").unwrap() != "none" {
|
||||
if document.contains(Some(raw)) && style.get("display") != "none" {
|
||||
position.x -= style_size_property(style, "margin-left")
|
||||
+ style_size_property(style, "border-left-width")
|
||||
+ style_size_property(style, "padding-left");
|
||||
@@ -147,30 +138,21 @@ pub fn set_canvas_position(
|
||||
+ style_size_property(style, "padding-top");
|
||||
}
|
||||
|
||||
set_canvas_style_property(raw, "position", "fixed");
|
||||
set_canvas_style_property(raw, "left", &format!("{}px", position.x));
|
||||
set_canvas_style_property(raw, "top", &format!("{}px", position.y));
|
||||
style.set("position", "fixed");
|
||||
style.set("left", &format!("{}px", position.x));
|
||||
style.set("top", &format!("{}px", position.y));
|
||||
}
|
||||
|
||||
/// This function will panic if the element is not inserted in the DOM
|
||||
/// or is not a CSS property that represents a size in pixel.
|
||||
pub fn style_size_property(style: &CssStyleDeclaration, property: &str) -> f64 {
|
||||
let prop = style
|
||||
.get_property_value(property)
|
||||
.expect("Found invalid property");
|
||||
pub fn style_size_property(style: &Style, property: &str) -> f64 {
|
||||
let prop = style.get(property);
|
||||
prop.strip_suffix("px")
|
||||
.expect("Element was not inserted into the DOM or is not a size in pixel")
|
||||
.parse()
|
||||
.expect("CSS property is not a size in pixel")
|
||||
}
|
||||
|
||||
pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: &str) {
|
||||
let style = raw.style();
|
||||
style
|
||||
.set_property(property, value)
|
||||
.unwrap_or_else(|err| panic!("error: {err:?}\nFailed to set {property}"))
|
||||
}
|
||||
|
||||
pub fn is_dark_mode(window: &web_sys::Window) -> Option<bool> {
|
||||
window
|
||||
.match_media("(prefers-color-scheme: dark)")
|
||||
|
||||
@@ -80,7 +80,7 @@ impl PointerHandler {
|
||||
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
|
||||
{
|
||||
let window = canvas_common.window.clone();
|
||||
self.on_pointer_release = Some(canvas_common.add_transient_event(
|
||||
self.on_pointer_release = Some(canvas_common.add_event(
|
||||
"pointerup",
|
||||
move |event: PointerEvent| {
|
||||
let modifiers = event::mouse_modifiers(&event);
|
||||
@@ -117,8 +117,8 @@ impl PointerHandler {
|
||||
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
|
||||
{
|
||||
let window = canvas_common.window.clone();
|
||||
let canvas = canvas_common.raw.clone();
|
||||
self.on_pointer_press = Some(canvas_common.add_transient_event(
|
||||
let canvas = canvas_common.raw().clone();
|
||||
self.on_pointer_press = Some(canvas_common.add_event(
|
||||
"pointerdown",
|
||||
move |event: PointerEvent| {
|
||||
if prevent_default {
|
||||
@@ -174,7 +174,7 @@ impl PointerHandler {
|
||||
B: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, ButtonsState, MouseButton),
|
||||
{
|
||||
let window = canvas_common.window.clone();
|
||||
let canvas = canvas_common.raw.clone();
|
||||
let canvas = canvas_common.raw().clone();
|
||||
self.on_cursor_move = Some(canvas_common.add_event(
|
||||
"pointermove",
|
||||
move |event: PointerEvent| {
|
||||
|
||||
@@ -2,14 +2,14 @@ use js_sys::{Array, Object};
|
||||
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{
|
||||
CssStyleDeclaration, Document, HtmlCanvasElement, MediaQueryList, ResizeObserver,
|
||||
ResizeObserverBoxOptions, ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize,
|
||||
Window,
|
||||
Document, HtmlCanvasElement, MediaQueryList, ResizeObserver, ResizeObserverBoxOptions,
|
||||
ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize, Window,
|
||||
};
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
|
||||
use super::super::backend;
|
||||
use super::canvas::Style;
|
||||
use super::media_query_handle::MediaQueryListHandle;
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
@@ -22,7 +22,7 @@ impl ResizeScaleHandle {
|
||||
window: Window,
|
||||
document: Document,
|
||||
canvas: HtmlCanvasElement,
|
||||
style: CssStyleDeclaration,
|
||||
style: Style,
|
||||
scale_handler: S,
|
||||
resize_handler: R,
|
||||
) -> Self
|
||||
@@ -51,7 +51,7 @@ struct ResizeScaleInternal {
|
||||
window: Window,
|
||||
document: Document,
|
||||
canvas: HtmlCanvasElement,
|
||||
style: CssStyleDeclaration,
|
||||
style: Style,
|
||||
mql: MediaQueryListHandle,
|
||||
observer: ResizeObserver,
|
||||
_observer_closure: Closure<dyn FnMut(Array, ResizeObserver)>,
|
||||
@@ -65,7 +65,7 @@ impl ResizeScaleInternal {
|
||||
window: Window,
|
||||
document: Document,
|
||||
canvas: HtmlCanvasElement,
|
||||
style: CssStyleDeclaration,
|
||||
style: Style,
|
||||
scale_handler: S,
|
||||
resize_handler: R,
|
||||
) -> Rc<RefCell<Self>>
|
||||
@@ -152,9 +152,7 @@ impl ResizeScaleInternal {
|
||||
}
|
||||
|
||||
fn notify(&mut self) {
|
||||
if !self.document.contains(Some(&self.canvas))
|
||||
|| self.style.get_property_value("display").unwrap() == "none"
|
||||
{
|
||||
if !self.document.contains(Some(&self.canvas)) || self.style.get("display") == "none" {
|
||||
let size = PhysicalSize::new(0, 0);
|
||||
|
||||
if self.notify_scale.replace(false) {
|
||||
@@ -180,7 +178,7 @@ impl ResizeScaleInternal {
|
||||
backend::style_size_property(&self.style, "height"),
|
||||
);
|
||||
|
||||
if self.style.get_property_value("box-sizing").unwrap() == "border-box" {
|
||||
if self.style.get("box-sizing") == "border-box" {
|
||||
size.width -= backend::style_size_property(&self.style, "border-left-width")
|
||||
+ backend::style_size_property(&self.style, "border-right-width")
|
||||
+ backend::style_size_property(&self.style, "padding-left")
|
||||
@@ -246,10 +244,7 @@ impl ResizeScaleInternal {
|
||||
.get(0)
|
||||
.unchecked_into();
|
||||
|
||||
let writing_mode = self
|
||||
.style
|
||||
.get_property_value("writing-mode")
|
||||
.expect("`writing-mode` is a valid CSS property");
|
||||
let writing_mode = self.style.get("writing-mode");
|
||||
|
||||
// means the canvas is not inserted into the DOM
|
||||
if writing_mode.is_empty() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use js_sys::{Function, Object, Promise, Reflect};
|
||||
use once_cell::unsync::OnceCell;
|
||||
use std::cell::OnceCell;
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
@@ -7,15 +7,28 @@ use crate::window::{
|
||||
};
|
||||
use crate::SendSyncWrapper;
|
||||
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use super::cursor::CursorState;
|
||||
use super::r#async::Dispatcher;
|
||||
use super::PlatformCustomCursor;
|
||||
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OwnedWindowHandle {}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
// Parent windows are currently unsupported, though owned window
|
||||
// handles would be implementable.
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
inner: Dispatcher<Inner>,
|
||||
}
|
||||
@@ -24,7 +37,7 @@ pub struct Inner {
|
||||
id: WindowId,
|
||||
pub window: web_sys::Window,
|
||||
canvas: Rc<RefCell<backend::Canvas>>,
|
||||
previous_pointer: RefCell<&'static str>,
|
||||
cursor: CursorState,
|
||||
destroy_fn: Option<Box<dyn FnOnce()>>,
|
||||
}
|
||||
|
||||
@@ -43,6 +56,7 @@ impl Window {
|
||||
let canvas =
|
||||
backend::Canvas::create(id, window.clone(), document.clone(), &attr, platform_attr)?;
|
||||
let canvas = Rc::new(RefCell::new(canvas));
|
||||
let cursor = CursorState::new(canvas.borrow().style().clone());
|
||||
|
||||
target.register(&canvas, id, prevent_default);
|
||||
|
||||
@@ -53,7 +67,7 @@ impl Window {
|
||||
id,
|
||||
window: window.clone(),
|
||||
canvas,
|
||||
previous_pointer: RefCell::new("auto"),
|
||||
cursor,
|
||||
destroy_fn: Some(destroy_fn),
|
||||
};
|
||||
|
||||
@@ -82,6 +96,22 @@ impl Window {
|
||||
.value()
|
||||
.map(|inner| inner.canvas.borrow().raw().clone())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
self.inner
|
||||
.value()
|
||||
.map(|inner| {
|
||||
let canvas = inner.canvas.borrow();
|
||||
// SAFETY: This will only work if the reference to `HtmlCanvasElement` stays valid.
|
||||
let canvas: &wasm_bindgen::JsValue = canvas.raw();
|
||||
let window_handle =
|
||||
rwh_06::WebCanvasWindowHandle::new(std::ptr::NonNull::from(canvas).cast());
|
||||
rwh_06::RawWindowHandle::WebCanvas(window_handle)
|
||||
})
|
||||
.ok_or(rwh_06::HandleError::Unavailable)
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
@@ -195,8 +225,12 @@ impl Inner {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
*self.previous_pointer.borrow_mut() = cursor.name();
|
||||
backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", cursor.name());
|
||||
self.cursor.set_cursor_icon(cursor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
|
||||
self.cursor.set_custom_cursor(cursor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -222,15 +256,7 @@ impl Inner {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
if !visible {
|
||||
backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", "none");
|
||||
} else {
|
||||
backend::set_canvas_style_property(
|
||||
self.canvas.borrow().raw(),
|
||||
"cursor",
|
||||
&self.previous_pointer.borrow(),
|
||||
);
|
||||
}
|
||||
self.cursor.set_cursor_visible(visible)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -379,13 +405,6 @@ impl Inner {
|
||||
rwh_05::RawDisplayHandle::Web(rwh_05::WebDisplayHandle::empty())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
let window_handle = rwh_06::WebWindowHandle::new(self.id.0);
|
||||
Ok(rwh_06::RawWindowHandle::Web(window_handle))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user