Compare commits
125 Commits
notgull/im
...
notgull/sp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d28c867b61 | ||
|
|
cfd35d2708 | ||
|
|
1b9c576f53 | ||
|
|
52532d2579 | ||
|
|
4b28f10470 | ||
|
|
daf1304879 | ||
|
|
c6e57430bb | ||
|
|
284f4c0558 | ||
|
|
859a6f109c | ||
|
|
a232f7bc77 | ||
|
|
48831f7910 | ||
|
|
114e6999d3 | ||
|
|
2f197315d9 | ||
|
|
557464489e | ||
|
|
623b3e5070 | ||
|
|
a97255e0fc | ||
|
|
3a817647d5 | ||
|
|
56035e1f13 | ||
|
|
08fc4099e8 | ||
|
|
4d4d6e5052 | ||
|
|
cf5f4de19e | ||
|
|
21df84b7f4 | ||
|
|
dd13ccda4c | ||
|
|
df8805c0d2 | ||
|
|
db1ca45a17 | ||
|
|
ff731197dc | ||
|
|
f526a47152 | ||
|
|
f204467838 | ||
|
|
6641dfa412 | ||
|
|
c1168b4f58 | ||
|
|
f8b7c4b78f | ||
|
|
3830b492c4 | ||
|
|
8862ce0163 | ||
|
|
569c44a632 | ||
|
|
ef2ec904ce | ||
|
|
98d3391f2d | ||
|
|
d0a1917603 | ||
|
|
b36d8d1e52 | ||
|
|
a5b08fc48c | ||
|
|
0482d9cfce | ||
|
|
10a785019c | ||
|
|
0cc19716f3 | ||
|
|
572d7ee77c | ||
|
|
b0c59c8416 | ||
|
|
d7c7ba1d6c | ||
|
|
aec608f93c | ||
|
|
d1717b6a01 | ||
|
|
6b29253797 | ||
|
|
30775f4982 | ||
|
|
41070d7c67 | ||
|
|
6df972d108 | ||
|
|
73910d20cc | ||
|
|
16d860736b | ||
|
|
2ee44246ae | ||
|
|
14b418a3a7 | ||
|
|
c86b0daf7f | ||
|
|
40b61d2d92 | ||
|
|
22311802b5 | ||
|
|
169cd39f93 | ||
|
|
bdeb2574dc | ||
|
|
4fe38d8067 | ||
|
|
816798bfd1 | ||
|
|
f99c810bec | ||
|
|
d39528aa69 | ||
|
|
787b2d7362 | ||
|
|
37b6243289 | ||
|
|
8ea1da7879 | ||
|
|
b1209bc253 | ||
|
|
021fd23c34 | ||
|
|
dd127463c5 | ||
|
|
ac247cd081 | ||
|
|
ea1bfd254d | ||
|
|
178f5fda05 | ||
|
|
42dbc4748e | ||
|
|
8b3de7cedf | ||
|
|
8b0ffb7e7d | ||
|
|
c55a2c779b | ||
|
|
c5a422eed6 | ||
|
|
1893b0ec42 | ||
|
|
5e106b4dbb | ||
|
|
5a1d3e4656 | ||
|
|
8f6de4ef4b | ||
|
|
ad1843aea6 | ||
|
|
ca1674519a | ||
|
|
f78edc7ef1 | ||
|
|
4f295e0c94 | ||
|
|
658f49b014 | ||
|
|
34e42ff94d | ||
|
|
ba654bb61e | ||
|
|
f5c691467b | ||
|
|
a87cfb62c3 | ||
|
|
25d6a1d46d | ||
|
|
e0fea25b06 | ||
|
|
843d7904d6 | ||
|
|
28a811bbba | ||
|
|
61a873d79a | ||
|
|
be4a660011 | ||
|
|
34dd2cdba9 | ||
|
|
775c8ece70 | ||
|
|
c12c7b82e8 | ||
|
|
8cc5cb9d9b | ||
|
|
9a28bb4b49 | ||
|
|
4f6fd44c6c | ||
|
|
5a43ea8cd6 | ||
|
|
e9a25a4c91 | ||
|
|
674657efb6 | ||
|
|
7d5bee767c | ||
|
|
745cfaab2c | ||
|
|
a8f49dc8ef | ||
|
|
e5310ade08 | ||
|
|
37946e0a3a | ||
|
|
86b737f5e7 | ||
|
|
e37585e5bc | ||
|
|
4aeeb24745 | ||
|
|
8cd3aaa8a2 | ||
|
|
2c15de7cf9 | ||
|
|
0a7ea61834 | ||
|
|
4ee11018c2 | ||
|
|
4f669ebbd2 | ||
|
|
7761b2b16c | ||
|
|
ae41e3265f | ||
|
|
8702a09333 | ||
|
|
8b5c84f404 | ||
|
|
a676d0018b | ||
|
|
04ca85a909 |
16
.github/CODEOWNERS
vendored
@@ -1,12 +1,6 @@
|
|||||||
# Core maintainers:
|
|
||||||
# - @msiglreith
|
|
||||||
# - @kchibisov
|
|
||||||
# - @madsmtm
|
|
||||||
# - @maroider
|
|
||||||
|
|
||||||
# Android
|
# Android
|
||||||
/src/platform/android.rs @msiglreith
|
/src/platform/android.rs @msiglreith @MarijnS95
|
||||||
/src/platform_impl/android @msiglreith
|
/src/platform_impl/android @msiglreith @MarijnS95
|
||||||
|
|
||||||
# iOS
|
# iOS
|
||||||
/src/platform/ios.rs @madsmtm
|
/src/platform/ios.rs @madsmtm
|
||||||
@@ -20,14 +14,14 @@
|
|||||||
/src/platform_impl/linux/wayland @kchibisov
|
/src/platform_impl/linux/wayland @kchibisov
|
||||||
|
|
||||||
# X11
|
# X11
|
||||||
/src/platform/x11.rs @kchibisov
|
/src/platform/x11.rs @kchibisov @notgull
|
||||||
/src/platform_impl/linux/x11 @kchibisov
|
/src/platform_impl/linux/x11 @kchibisov @notgull
|
||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
/src/platform/macos.rs @madsmtm
|
/src/platform/macos.rs @madsmtm
|
||||||
/src/platform_impl/macos @madsmtm
|
/src/platform_impl/macos @madsmtm
|
||||||
|
|
||||||
# Web (no maintainer)
|
# Web
|
||||||
/src/platform/web.rs @daxpedda
|
/src/platform/web.rs @daxpedda
|
||||||
/src/platform_impl/web @daxpedda
|
/src/platform_impl/web @daxpedda
|
||||||
|
|
||||||
|
|||||||
44
.github/workflows/ci.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
toolchain: [stable, nightly, '1.65.0']
|
toolchain: [stable, nightly, '1.70.0']
|
||||||
platform:
|
platform:
|
||||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
# 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, }
|
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
||||||
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
|
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
|
||||||
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
|
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
|
||||||
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--features=android-native-activity', cmd: 'apk --' }
|
||||||
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
|
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
|
||||||
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
|
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
|
||||||
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
|
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
|
||||||
@@ -43,11 +43,20 @@ jobs:
|
|||||||
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
||||||
exclude:
|
exclude:
|
||||||
# Android is tested on stable-3
|
# 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 --' }
|
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||||
include:
|
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 --' }
|
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||||
|
- toolchain: 'nightly'
|
||||||
|
platform: {
|
||||||
|
name: 'web Atomic',
|
||||||
|
target: wasm32-unknown-unknown,
|
||||||
|
os: ubuntu-latest,
|
||||||
|
options: '-Zbuild-std=panic_abort,std',
|
||||||
|
rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
|
||||||
|
components: rust-src,
|
||||||
|
}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Set more verbose terminal output
|
# Set more verbose terminal output
|
||||||
@@ -55,8 +64,7 @@ jobs:
|
|||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
# Faster compilation and error on warnings
|
# Faster compilation and error on warnings
|
||||||
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings'
|
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
|
||||||
RUSTDOCFLAGS: '--deny=warnings'
|
|
||||||
|
|
||||||
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
|
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
|
||||||
CMD: ${{ matrix.platform.cmd }}
|
CMD: ${{ matrix.platform.cmd }}
|
||||||
@@ -109,19 +117,21 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
|
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
|
||||||
targets: ${{ matrix.platform.target }}
|
targets: ${{ matrix.platform.target }}
|
||||||
components: clippy
|
components: clippy, ${{ matrix.platform.components }}
|
||||||
|
|
||||||
- name: Check documentation
|
- name: Check documentation
|
||||||
run: cargo doc --no-deps $OPTIONS --document-private-items
|
run: cargo doc --no-deps $OPTIONS --document-private-items
|
||||||
|
env:
|
||||||
|
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}'
|
||||||
|
|
||||||
- name: Build crate
|
- name: Build crate
|
||||||
run: cargo $CMD build $OPTIONS
|
run: cargo $CMD build -p winit $OPTIONS
|
||||||
|
|
||||||
- name: Build tests
|
- name: Build tests
|
||||||
if: >
|
if: >
|
||||||
!contains(matrix.platform.target, 'redox') &&
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
matrix.toolchain != '1.65.0'
|
matrix.toolchain != '1.70.0'
|
||||||
run: cargo $CMD test --no-run $OPTIONS
|
run: cargo $CMD test -p winit --no-run $OPTIONS
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: >
|
if: >
|
||||||
@@ -129,7 +139,7 @@ jobs:
|
|||||||
!contains(matrix.platform.target, 'ios') &&
|
!contains(matrix.platform.target, 'ios') &&
|
||||||
!contains(matrix.platform.target, 'wasm32') &&
|
!contains(matrix.platform.target, 'wasm32') &&
|
||||||
!contains(matrix.platform.target, 'redox') &&
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
matrix.toolchain != '1.65.0'
|
matrix.toolchain != '1.70.0'
|
||||||
run: cargo $CMD test $OPTIONS
|
run: cargo $CMD test $OPTIONS
|
||||||
|
|
||||||
- name: Lint with clippy
|
- name: Lint with clippy
|
||||||
@@ -139,8 +149,8 @@ jobs:
|
|||||||
- name: Build tests with serde enabled
|
- name: Build tests with serde enabled
|
||||||
if: >
|
if: >
|
||||||
!contains(matrix.platform.target, 'redox') &&
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
matrix.toolchain != '1.65.0'
|
matrix.toolchain != '1.70.0'
|
||||||
run: cargo $CMD test --no-run $OPTIONS --features serde
|
run: cargo $CMD test --no-run $OPTIONS -p winit --features serde
|
||||||
|
|
||||||
- name: Run tests with serde enabled
|
- name: Run tests with serde enabled
|
||||||
if: >
|
if: >
|
||||||
@@ -148,9 +158,15 @@ jobs:
|
|||||||
!contains(matrix.platform.target, 'ios') &&
|
!contains(matrix.platform.target, 'ios') &&
|
||||||
!contains(matrix.platform.target, 'wasm32') &&
|
!contains(matrix.platform.target, 'wasm32') &&
|
||||||
!contains(matrix.platform.target, 'redox') &&
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
matrix.toolchain != '1.65.0'
|
matrix.toolchain != '1.70.0'
|
||||||
run: cargo $CMD test $OPTIONS --features serde
|
run: cargo $CMD test $OPTIONS --features serde
|
||||||
|
|
||||||
|
- name: Check docs.rs documentation
|
||||||
|
if: matrix.toolchain == 'nightly'
|
||||||
|
run: cargo doc --no-deps $OPTIONS --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
|
||||||
|
env:
|
||||||
|
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs'
|
||||||
|
|
||||||
# See restore step above
|
# See restore step above
|
||||||
- name: Save cache of cargo folder
|
- name: Save cache of cargo folder
|
||||||
uses: actions/cache/save@v3
|
uses: actions/cache/save@v3
|
||||||
|
|||||||
50
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: Docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docs:
|
||||||
|
name: Documentation
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}winit
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@master
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
|
||||||
|
- name: Run Rustdoc
|
||||||
|
env:
|
||||||
|
RUSTDOCFLAGS: --crate-version master --cfg=docsrs
|
||||||
|
run: |
|
||||||
|
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
|
||||||
|
- name: Fix permissions
|
||||||
|
run: |
|
||||||
|
chmod -c -R +rX "target/doc" | while read line; do
|
||||||
|
echo "::warning title=Invalid file permissions automatically fixed::$line"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: target/doc
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
82
CHANGELOG.md
@@ -11,12 +11,87 @@ Unreleased` header.
|
|||||||
|
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
|
- On X11, fix swapped instance and general class names.
|
||||||
|
- **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`.
|
||||||
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
|
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
|
||||||
- Add `Window::set_custom_cursor`
|
- **Breaking:** Remove `Window::set_cursor_icon`
|
||||||
|
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`
|
||||||
- Add `CustomCursor`
|
- Add `CustomCursor`
|
||||||
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
|
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
|
||||||
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
|
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
|
||||||
|
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s.
|
||||||
- On macOS, add services menu.
|
- 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.
|
||||||
|
- **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`.
|
||||||
|
- On Web, add the ability to toggle calling `Event.preventDefault()` on `Window`.
|
||||||
|
- **Breaking:** Remove `WindowAttributes::fullscreen()` and expose as field directly.
|
||||||
|
- **Breaking:** Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold static data.
|
||||||
|
- **Breaking:** No longer export `platform::x11::XNotSupported`.
|
||||||
|
- **Breaking:** Renamed `platform::x11::XWindowType` to `platform::x11::WindowType`.
|
||||||
|
- Add the `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases.
|
||||||
|
- **Breaking:** Rename `TouchpadMagnify` to `PinchGesture`, `SmartMagnify` to `DoubleTapGesture` and `TouchpadRotate` to `RotationGesture` to represent the action rather than the intent.
|
||||||
|
- on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`.
|
||||||
|
- on Windows: add `with_system_backdrop`, `with_border_color`, `with_title_background_color`, `with_title_text_color` and `with_corner_preference`
|
||||||
|
- On Windows, Remove `WS_CAPTION`, `WS_BORDER` and `WS_EX_WINDOWEDGE` styles for child windows without decorations.
|
||||||
|
- On Windows, fixed a race condition when sending an event through the loop proxy.
|
||||||
|
- **Breaking:** Removed `EventLoopError::AlreadyRunning`, which can't happen as it is already prevented by the type system.
|
||||||
|
- On Wayland, disable `Occluded` event handling.
|
||||||
|
- Added `EventLoop::builder`, which is intended to replace the (now deprecated) `EventLoopBuilder::new`.
|
||||||
|
- **Breaking:** Changed the signature of `EventLoop::with_user_event` to return a builder.
|
||||||
|
- **Breaking:** Removed `EventLoopBuilder::with_user_event`, the functionality is now available in `EventLoop::with_user_event`.
|
||||||
|
- Add `Window::builder`, which is intended to replace the (now deprecated) `WindowBuilder::new`.
|
||||||
|
- On X11, reload dpi on `_XSETTINGS_SETTINGS` update.
|
||||||
|
- On X11, fix deadlock when adjusting DPI and resizing at the same time.
|
||||||
|
- On Wayland, fix `Focused(false)` being send when other seats still have window focused.
|
||||||
|
- On Wayland, fix `Window::set_{min,max}_inner_size` not always applied.
|
||||||
|
- On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop.
|
||||||
|
- On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
|
||||||
|
- **Breaking:** Move some types to the `winit-core` crate. Most types are
|
||||||
|
re-exported verbatim; however:
|
||||||
|
- `Event`, `WindowEvent` and `KeyEvent` now take a generic that `winit`
|
||||||
|
implements.
|
||||||
|
- `ActivationToken` and `InnerSizeWriter` expose new methods to make them
|
||||||
|
useful.
|
||||||
|
- `InnerSizeWriter::request_inner_size` returns an `InnerSizeIgnored` error
|
||||||
|
instead of an `ExternalError`.
|
||||||
|
|
||||||
|
# 0.29.10
|
||||||
|
|
||||||
|
- On Web, account for canvas being focused already before event loop starts.
|
||||||
|
- On Web, increase cursor position accuracy.
|
||||||
|
|
||||||
|
# 0.29.9
|
||||||
|
|
||||||
|
- On X11, fix `NotSupported` error not propagated when creating event loop.
|
||||||
|
- On Wayland, fix resize not issued when scale changes
|
||||||
|
- On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`.
|
||||||
|
- On macOS, report correct logical key when Ctrl or Cmd is pressed.
|
||||||
|
|
||||||
|
# 0.29.8
|
||||||
|
|
||||||
|
- On X11, fix IME input lagging behind.
|
||||||
|
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
|
||||||
|
- On X11, fix keymap not updated from xmodmap.
|
||||||
|
- On X11, reduce the amount of time spent fetching screen resources.
|
||||||
|
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
|
||||||
|
- On Wayland, fix `Window::inner_size` not using the correct rounding.
|
||||||
|
|
||||||
|
# 0.29.7
|
||||||
|
|
||||||
|
- On X11, fix `Xft.dpi` reload during runtime.
|
||||||
|
- On X11, fix window minimize.
|
||||||
|
|
||||||
|
# 0.29.6
|
||||||
|
|
||||||
|
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
|
||||||
|
- On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window.
|
||||||
|
- Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation
|
||||||
|
|
||||||
|
# 0.29.5
|
||||||
|
|
||||||
- On macOS, remove spurious error logging when handling `Fn`.
|
- On macOS, remove spurious error logging when handling `Fn`.
|
||||||
- On X11, fix an issue where floating point data from the server is
|
- On X11, fix an issue where floating point data from the server is
|
||||||
misinterpreted during a drag and drop operation.
|
misinterpreted during a drag and drop operation.
|
||||||
@@ -25,8 +100,8 @@ Unreleased` header.
|
|||||||
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
|
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
|
||||||
- On X11, fix `Xft.dpi` detection from Xresources.
|
- 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 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 Web, remove queuing fullscreen request in absence of transient activation.
|
- On Wayland, fix resize being sent on focus change.
|
||||||
- On Web, fix setting cursor icon overriding cursor visibility.
|
- On Windows, fix `set_ime_cursor_area`.
|
||||||
|
|
||||||
# 0.29.4
|
# 0.29.4
|
||||||
|
|
||||||
@@ -38,6 +113,7 @@ Unreleased` header.
|
|||||||
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
|
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
|
||||||
- On Wayland, fix `wl_surface` being destroyed before associated objects.
|
- On Wayland, fix `wl_surface` being destroyed before associated objects.
|
||||||
- On macOS, fix assertion when pressing `Fn` key.
|
- On macOS, fix assertion when pressing `Fn` key.
|
||||||
|
- On Windows, add `WindowBuilderExtWindows::with_clip_children` to control `WS_CLIPCHILDREN` style.
|
||||||
|
|
||||||
# 0.29.3
|
# 0.29.3
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ 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:
|
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.
|
- 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
|
- 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
|
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 updated any relevant documentation in winit
|
||||||
|
|||||||
243
Cargo.toml
@@ -1,237 +1,16 @@
|
|||||||
[package]
|
|
||||||
name = "winit"
|
|
||||||
version = "0.29.4"
|
|
||||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
|
||||||
description = "Cross-platform window creation library."
|
|
||||||
edition = "2021"
|
|
||||||
keywords = ["windowing"]
|
|
||||||
license = "Apache-2.0"
|
|
||||||
readme = "README.md"
|
|
||||||
repository = "https://github.com/rust-windowing/winit"
|
|
||||||
documentation = "https://docs.rs/winit"
|
|
||||||
categories = ["gui"]
|
|
||||||
rust-version = "1.65.0"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
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 = [
|
|
||||||
# Windows
|
|
||||||
"i686-pc-windows-msvc",
|
|
||||||
"x86_64-pc-windows-msvc",
|
|
||||||
# macOS
|
|
||||||
"x86_64-apple-darwin",
|
|
||||||
# Unix (X11 & Wayland)
|
|
||||||
"i686-unknown-linux-gnu",
|
|
||||||
"x86_64-unknown-linux-gnu",
|
|
||||||
# iOS
|
|
||||||
"x86_64-apple-ios",
|
|
||||||
# Android
|
|
||||||
"aarch64-linux-android",
|
|
||||||
# WebAssembly
|
|
||||||
"wasm32-unknown-unknown",
|
|
||||||
]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
|
||||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb", "xim"]
|
|
||||||
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
|
|
||||||
wayland-dlopen = ["wayland-backend/dlopen"]
|
|
||||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
|
|
||||||
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
|
|
||||||
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
|
|
||||||
android-native-activity = ["android-activity/native-activity"]
|
|
||||||
android-game-activity = ["android-activity/game-activity"]
|
|
||||||
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
|
|
||||||
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
|
|
||||||
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
|
|
||||||
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
cfg_aliases = "0.1.1"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bitflags = "2"
|
|
||||||
cursor-icon = "1.1.0"
|
|
||||||
log = "0.4"
|
|
||||||
mint = { version = "0.5.6", optional = true }
|
|
||||||
once_cell = "1.12"
|
|
||||||
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
|
|
||||||
rwh_05 = { package = "raw-window-handle", version = "0.5", features = ["std"], optional = true }
|
|
||||||
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
|
|
||||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
|
||||||
smol_str = "0.2.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
image = { version = "0.24.0", default-features = false, features = ["png"] }
|
|
||||||
simple_logger = { version = "4.2.0", default_features = false }
|
|
||||||
winit = { path = ".", features = ["rwh_05"] }
|
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
|
|
||||||
softbuffer = "0.3.0"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
|
||||||
android-activity = "0.5.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"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
|
||||||
core-graphics = "0.23.1"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies.icrate]
|
|
||||||
version = "0.0.4"
|
|
||||||
features = [
|
|
||||||
"dispatch",
|
|
||||||
"Foundation",
|
|
||||||
"Foundation_NSArray",
|
|
||||||
"Foundation_NSAttributedString",
|
|
||||||
"Foundation_NSMutableAttributedString",
|
|
||||||
"Foundation_NSData",
|
|
||||||
"Foundation_NSDictionary",
|
|
||||||
"Foundation_NSString",
|
|
||||||
"Foundation_NSProcessInfo",
|
|
||||||
"Foundation_NSThread",
|
|
||||||
"Foundation_NSNumber",
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "ios")'.dependencies.icrate]
|
|
||||||
version = "0.0.4"
|
|
||||||
features = [
|
|
||||||
"dispatch",
|
|
||||||
"Foundation",
|
|
||||||
"Foundation_NSArray",
|
|
||||||
"Foundation_NSString",
|
|
||||||
"Foundation_NSProcessInfo",
|
|
||||||
"Foundation_NSThread",
|
|
||||||
"Foundation_NSSet",
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
|
||||||
unicode-segmentation = "1.7.1"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
|
||||||
version = "0.48"
|
|
||||||
features = [
|
|
||||||
"Win32_Devices_HumanInterfaceDevice",
|
|
||||||
"Win32_Foundation",
|
|
||||||
"Win32_Globalization",
|
|
||||||
"Win32_Graphics_Dwm",
|
|
||||||
"Win32_Graphics_Gdi",
|
|
||||||
"Win32_Media",
|
|
||||||
"Win32_System_Com_StructuredStorage",
|
|
||||||
"Win32_System_Com",
|
|
||||||
"Win32_System_LibraryLoader",
|
|
||||||
"Win32_System_Ole",
|
|
||||||
"Win32_System_SystemInformation",
|
|
||||||
"Win32_System_SystemServices",
|
|
||||||
"Win32_System_Threading",
|
|
||||||
"Win32_System_WindowsProgramming",
|
|
||||||
"Win32_UI_Accessibility",
|
|
||||||
"Win32_UI_Controls",
|
|
||||||
"Win32_UI_HiDpi",
|
|
||||||
"Win32_UI_Input_Ime",
|
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
|
||||||
"Win32_UI_Input_Pointer",
|
|
||||||
"Win32_UI_Input_Touch",
|
|
||||||
"Win32_UI_Shell",
|
|
||||||
"Win32_UI_TextServices",
|
|
||||||
"Win32_UI_WindowsAndMessaging",
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
|
|
||||||
ahash = { version = "0.8.3", features = ["no-rng"], optional = true }
|
|
||||||
bytemuck = { version = "1.13.1", default-features = false, optional = true }
|
|
||||||
calloop = "0.12.3"
|
|
||||||
libc = "0.2.64"
|
|
||||||
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.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.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
|
||||||
xim = { version = "0.3.0", features = ["x11rb-client", "client"], optional = true }
|
|
||||||
xkbcommon-dl = "0.4.0"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "redox")'.dependencies]
|
|
||||||
orbclient = { version = "0.3.42", default-features = false }
|
|
||||||
redox_syscall = "0.3"
|
|
||||||
|
|
||||||
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
|
|
||||||
package = "web-sys"
|
|
||||||
version = "0.3.64"
|
|
||||||
features = [
|
|
||||||
'AbortController',
|
|
||||||
'AbortSignal',
|
|
||||||
'Blob',
|
|
||||||
'console',
|
|
||||||
'CssStyleDeclaration',
|
|
||||||
'Document',
|
|
||||||
'DomRect',
|
|
||||||
'DomRectReadOnly',
|
|
||||||
'Element',
|
|
||||||
'Event',
|
|
||||||
'EventTarget',
|
|
||||||
'FocusEvent',
|
|
||||||
'HtmlCanvasElement',
|
|
||||||
'HtmlElement',
|
|
||||||
'ImageBitmap',
|
|
||||||
'ImageBitmapOptions',
|
|
||||||
'ImageBitmapRenderingContext',
|
|
||||||
'ImageData',
|
|
||||||
'IntersectionObserver',
|
|
||||||
'IntersectionObserverEntry',
|
|
||||||
'KeyboardEvent',
|
|
||||||
'MediaQueryList',
|
|
||||||
'MessageChannel',
|
|
||||||
'MessagePort',
|
|
||||||
'Node',
|
|
||||||
'PageTransitionEvent',
|
|
||||||
'PointerEvent',
|
|
||||||
'PremultiplyAlpha',
|
|
||||||
'ResizeObserver',
|
|
||||||
'ResizeObserverBoxOptions',
|
|
||||||
'ResizeObserverEntry',
|
|
||||||
'ResizeObserverOptions',
|
|
||||||
'ResizeObserverSize',
|
|
||||||
'VisibilityState',
|
|
||||||
'Window',
|
|
||||||
'WheelEvent',
|
|
||||||
'Url',
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
|
||||||
atomic-waker = "1"
|
|
||||||
js-sys = "0.3.64"
|
|
||||||
wasm-bindgen = "0.2"
|
|
||||||
wasm-bindgen-futures = "0.4"
|
|
||||||
web-time = "0.2"
|
|
||||||
|
|
||||||
[target.'cfg(target_family = "wasm")'.dev-dependencies]
|
|
||||||
console_log = "1"
|
|
||||||
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"run-wasm",
|
"run-wasm",
|
||||||
|
"winit",
|
||||||
|
"winit-core"
|
||||||
]
|
]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
[patch.crates-io]
|
[workspace.dependencies]
|
||||||
xim = { git = "https://github.com/forkgull/xim-rs", branch = "x11rb-13" }
|
bitflags = "2"
|
||||||
|
cfg_aliases = "0.2.0"
|
||||||
|
cursor-icon = "1.1.0"
|
||||||
|
serde = { version = "1", features = ["serde_derive"] }
|
||||||
|
smol_str = "0.2.0"
|
||||||
|
web-time = "1"
|
||||||
|
winit-core = { path = "./winit-core", default-features = false, features = ["std"] }
|
||||||
|
|||||||
@@ -126,6 +126,11 @@ If your PR makes notable changes to Winit's features, please update this section
|
|||||||
* Setting a menu bar
|
* Setting a menu bar
|
||||||
* `WS_EX_NOREDIRECTIONBITMAP` support
|
* `WS_EX_NOREDIRECTIONBITMAP` support
|
||||||
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
|
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
|
||||||
|
* Changing a system-drawn backdrop
|
||||||
|
* Setting the window border color
|
||||||
|
* Setting the title bar background color
|
||||||
|
* Setting the title color
|
||||||
|
* Setting the corner rounding preference
|
||||||
|
|
||||||
### macOS
|
### macOS
|
||||||
* Window activation policy
|
* Window activation policy
|
||||||
|
|||||||
19
README.md
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
[](https://crates.io/crates/winit)
|
[](https://crates.io/crates/winit)
|
||||||
[](https://docs.rs/winit)
|
[](https://docs.rs/winit)
|
||||||
|
[](https://rust-windowing.github.io/winit/winit/index.html)
|
||||||
[](https://github.com/rust-windowing/winit/actions)
|
[](https://github.com/rust-windowing/winit/actions)
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
winit = "0.29.4"
|
winit = "0.29.10"
|
||||||
```
|
```
|
||||||
|
|
||||||
## [Documentation](https://docs.rs/winit)
|
## [Documentation](https://docs.rs/winit)
|
||||||
@@ -17,10 +19,9 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
|
|||||||
|
|
||||||
## Contact Us
|
## Contact Us
|
||||||
|
|
||||||
Join us in any of these:
|
Join us in our [](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [](https://web.libera.chat/#winit).
|
||||||
|
|
||||||
[](https://matrix.to/#/#rust-windowing:matrix.org)
|
The maintainers have a meeting every friday at UTC 14. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
|
||||||
[](https://web.libera.chat/#winit)
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
|
|||||||
|
|
||||||
## MSRV Policy
|
## MSRV Policy
|
||||||
|
|
||||||
This crate's Minimum Supported Rust Version (MSRV) 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.
|
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
|
As a **tentative** policy, the upper bound of the MSRV is given by the following
|
||||||
@@ -74,7 +75,7 @@ same MSRV policy.
|
|||||||
|
|
||||||
Note that windows don't appear on Wayland until you draw/present to them.
|
Note that windows don't appear on Wayland until you draw/present to them.
|
||||||
|
|
||||||
#### WebAssembly
|
#### Web
|
||||||
|
|
||||||
To run the web example: `cargo run-wasm --example web`
|
To run the web example: `cargo run-wasm --example web`
|
||||||
|
|
||||||
@@ -85,7 +86,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
|
create a `<canvas>` element which you can then retrieve][web canvas getter] and
|
||||||
insert it into the DOM yourself.
|
insert it into the DOM yourself.
|
||||||
|
|
||||||
For the example code using Winit with WebAssembly, check out the [web example]. For
|
For the example code using Winit on Web, check out the [web example]. For
|
||||||
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
|
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
|
||||||
book].
|
book].
|
||||||
|
|
||||||
@@ -150,13 +151,13 @@ class. Your application _must_ specify the base class it needs via a feature fla
|
|||||||
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
|
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
|
||||||
[Gradle]: https://developer.android.com/studio/build
|
[Gradle]: https://developer.android.com/studio/build
|
||||||
|
|
||||||
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
|
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
|
||||||
|
|
||||||
##### Converting from `ndk-glue` to `android-activity`
|
##### 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`
|
1. Remove `ndk-glue` from your `Cargo.toml`
|
||||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.4", features = [ "android-native-activity" ] }`
|
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.10", features = [ "android-native-activity" ] }`
|
||||||
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
|
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).
|
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
|
||||||
|
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ disallowed-methods = [
|
|||||||
{ path = "web_sys::Document::exit_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" },
|
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
|
||||||
{ path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
|
{ path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
|
||||||
|
{ path = "icrate::AppKit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -34,9 +34,10 @@ skip = [
|
|||||||
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
|
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
|
||||||
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
|
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
|
||||||
{ name = "libloading" }, # x11rb uses a different version until the next update
|
{ name = "libloading" }, # x11rb uses a different version until the next update
|
||||||
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
|
|
||||||
]
|
]
|
||||||
skip-tree = []
|
skip-tree = [
|
||||||
|
{ name = "run-wasm", version = "0.1.0" }
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
[licenses]
|
[licenses]
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
#![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,
|
|
||||||
keyboard::Key,
|
|
||||||
window::{CustomCursor, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn decode_cursor(bytes: &[u8]) -> 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);
|
|
||||||
CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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")),
|
|
||||||
decode_cursor(include_bytes!("data/cross2.png")),
|
|
||||||
];
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
//! Fill the window buffer with a solid color.
|
|
||||||
//!
|
|
||||||
//! Launching a window without drawing to it has unpredictable results varying from platform to
|
|
||||||
//! platform. In order to have well-defined examples, this module provides an easy way to
|
|
||||||
//! fill the window buffer with a solid color.
|
|
||||||
//!
|
|
||||||
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
|
|
||||||
//! also be used to fill the window buffer, but they are more complicated to use.
|
|
||||||
|
|
||||||
use winit::window::Window;
|
|
||||||
|
|
||||||
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
|
|
||||||
pub(super) fn fill_window(window: &Window) {
|
|
||||||
use softbuffer::{Context, Surface};
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::mem::ManuallyDrop;
|
|
||||||
use std::num::NonZeroU32;
|
|
||||||
use winit::window::WindowId;
|
|
||||||
|
|
||||||
/// The graphics context used to draw to a window.
|
|
||||||
struct GraphicsContext {
|
|
||||||
/// The global softbuffer context.
|
|
||||||
context: Context,
|
|
||||||
|
|
||||||
/// The hash map of window IDs to surfaces.
|
|
||||||
surfaces: HashMap<WindowId, Surface>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GraphicsContext {
|
|
||||||
fn new(w: &Window) -> Self {
|
|
||||||
Self {
|
|
||||||
context: unsafe { Context::new(w) }.expect("Failed to create a softbuffer context"),
|
|
||||||
surfaces: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn surface(&mut self, w: &Window) -> &mut Surface {
|
|
||||||
self.surfaces.entry(w.id()).or_insert_with(|| {
|
|
||||||
unsafe { Surface::new(&self.context, w) }
|
|
||||||
.expect("Failed to create a softbuffer surface")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
// NOTE: You should never do things like that, create context and drop it before
|
|
||||||
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
|
||||||
// ManuallyDrop to prevent destructors from running.
|
|
||||||
//
|
|
||||||
// A static, thread-local map of graphics contexts to open windows.
|
|
||||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
.get_or_insert_with(|| GraphicsContext::new(window))
|
|
||||||
.surface(window);
|
|
||||||
|
|
||||||
// Fill a buffer with a solid color.
|
|
||||||
const DARK_GRAY: u32 = 0xFF181818;
|
|
||||||
|
|
||||||
surface
|
|
||||||
.resize(width, height)
|
|
||||||
.expect("Failed to resize the softbuffer surface");
|
|
||||||
|
|
||||||
let mut buffer = surface
|
|
||||||
.buffer_mut()
|
|
||||||
.expect("Failed to get the softbuffer buffer");
|
|
||||||
buffer.fill(DARK_GRAY);
|
|
||||||
buffer
|
|
||||||
.present()
|
|
||||||
.expect("Failed to present the softbuffer buffer");
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
|
|
||||||
pub(super) fn fill_window(_window: &Window) {
|
|
||||||
// No-op on mobile platforms.
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,7 @@
|
|||||||
name = "run-wasm"
|
name = "run-wasm"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cargo-run-wasm = "0.2.0"
|
cargo-run-wasm = "0.2.0"
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
//! Contains traits with platform-specific methods in them.
|
|
||||||
//!
|
|
||||||
//! Contains the follow OS-specific modules:
|
|
||||||
//!
|
|
||||||
//! - `android`
|
|
||||||
//! - `ios`
|
|
||||||
//! - `macos`
|
|
||||||
//! - `unix`
|
|
||||||
//! - `windows`
|
|
||||||
//! - `web`
|
|
||||||
//!
|
|
||||||
//! And the following platform-specific modules:
|
|
||||||
//!
|
|
||||||
//! - `run_on_demand` (available on `windows`, `unix`, `macos`, `android`)
|
|
||||||
//! - `pump_events` (available on `windows`, `unix`, `macos`, `android`)
|
|
||||||
//!
|
|
||||||
//! However only the module corresponding to the platform you're compiling to will be available.
|
|
||||||
|
|
||||||
#[cfg(android_platform)]
|
|
||||||
pub mod android;
|
|
||||||
#[cfg(ios_platform)]
|
|
||||||
pub mod ios;
|
|
||||||
#[cfg(macos_platform)]
|
|
||||||
pub mod macos;
|
|
||||||
#[cfg(orbital_platform)]
|
|
||||||
pub mod orbital;
|
|
||||||
#[cfg(any(x11_platform, wayland_platform))]
|
|
||||||
pub mod startup_notify;
|
|
||||||
#[cfg(wayland_platform)]
|
|
||||||
pub mod wayland;
|
|
||||||
#[cfg(wasm_platform)]
|
|
||||||
pub mod web;
|
|
||||||
#[cfg(windows_platform)]
|
|
||||||
pub mod windows;
|
|
||||||
#[cfg(x11_platform)]
|
|
||||||
pub mod x11;
|
|
||||||
|
|
||||||
#[cfg(any(
|
|
||||||
windows_platform,
|
|
||||||
macos_platform,
|
|
||||||
android_platform,
|
|
||||||
x11_platform,
|
|
||||||
wayland_platform
|
|
||||||
))]
|
|
||||||
pub mod run_on_demand;
|
|
||||||
|
|
||||||
#[cfg(any(
|
|
||||||
windows_platform,
|
|
||||||
macos_platform,
|
|
||||||
android_platform,
|
|
||||||
x11_platform,
|
|
||||||
wayland_platform
|
|
||||||
))]
|
|
||||||
pub mod pump_events;
|
|
||||||
|
|
||||||
pub mod modifier_supplement;
|
|
||||||
pub mod scancode;
|
|
||||||
@@ -1,862 +0,0 @@
|
|||||||
//! IME handler, using the xim-rs crate.
|
|
||||||
|
|
||||||
use super::{X11Error, X11rbConnection, XConnection};
|
|
||||||
|
|
||||||
use x11rb::connection::Connection;
|
|
||||||
use x11rb::protocol::xproto::Window;
|
|
||||||
use x11rb::protocol::Event;
|
|
||||||
|
|
||||||
use xim::x11rb::{HasConnection, X11rbClient};
|
|
||||||
use xim::{AttributeName, Client as _, ClientError, ClientHandler, InputStyle, Point};
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::{HashMap, VecDeque};
|
|
||||||
use std::fmt;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
impl HasConnection for XConnection {
|
|
||||||
type Connection = X11rbConnection;
|
|
||||||
|
|
||||||
fn conn(&self) -> &Self::Connection {
|
|
||||||
self.xcb_connection()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A collection of the IME events that can occur.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum ImeEvent {
|
|
||||||
Enabled,
|
|
||||||
Start,
|
|
||||||
Update(String, Option<usize>),
|
|
||||||
Commit(String),
|
|
||||||
End,
|
|
||||||
Disabled,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invalid states that an IME client can enter.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum InvalidImeState {
|
|
||||||
/// The IME has no style information.
|
|
||||||
NoStyle,
|
|
||||||
|
|
||||||
/// No windows in the pending window queue.
|
|
||||||
NoWindows,
|
|
||||||
|
|
||||||
/// Invalid input context.
|
|
||||||
InvalidIc(u16),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for InvalidImeState {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
InvalidImeState::NoStyle => write!(f, "IME has no style information"),
|
|
||||||
InvalidImeState::NoWindows => {
|
|
||||||
write!(f, "IME has no windows in the pending window queue")
|
|
||||||
}
|
|
||||||
InvalidImeState::InvalidIc(ic) => write!(f, "IME has invalid input context {}", ic),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Request to control XIM handler from the window.
|
|
||||||
pub enum ImeRequest {
|
|
||||||
/// Set IME spot position for given `window_id`.
|
|
||||||
Position(Window, i16, i16),
|
|
||||||
|
|
||||||
/// Allow IME input for the given `window_id`.
|
|
||||||
Allow(Window, bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The IME data for winit.
|
|
||||||
pub(super) struct ImeData {
|
|
||||||
/// The XIM client manager.
|
|
||||||
client: X11rbClient<Arc<XConnection>>,
|
|
||||||
|
|
||||||
/// Relevant IME data.
|
|
||||||
handler: ImeHandler,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inner IME handler.
|
|
||||||
struct ImeHandler {
|
|
||||||
/// Handle to the event queue.
|
|
||||||
event_queue: Rc<RefCell<VecDeque<Event>>>,
|
|
||||||
|
|
||||||
/// Whether IME is currently disconnected.
|
|
||||||
disconnected: bool,
|
|
||||||
|
|
||||||
/// IME events waiting to be read.
|
|
||||||
ime_events: VecDeque<(Window, ImeEvent)>,
|
|
||||||
|
|
||||||
/// Windows waiting to be assigned an input context.
|
|
||||||
pending_windows: VecDeque<WindowData>,
|
|
||||||
|
|
||||||
/// Currently registered input styles.
|
|
||||||
styles: Option<(Style, Style)>,
|
|
||||||
|
|
||||||
/// The input method for the display, if there is one.
|
|
||||||
input_method: Option<u16>,
|
|
||||||
|
|
||||||
/// Hash map between input contexts and their associated data.
|
|
||||||
input_contexts: HashMap<u16, IcData>,
|
|
||||||
|
|
||||||
/// Map between window IDs and their associated input contexts.
|
|
||||||
window_contexts: HashMap<Window, u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Data relevant for each input context.
|
|
||||||
struct IcData {
|
|
||||||
/// Data associated with the window.
|
|
||||||
window: WindowData,
|
|
||||||
|
|
||||||
/// Newly set point for the context.
|
|
||||||
new_spot: Option<Point>,
|
|
||||||
|
|
||||||
/// The current preedit string.
|
|
||||||
///
|
|
||||||
/// We use a `Vec<char>` here instead of a string because the IME indices operate on chars,
|
|
||||||
/// not bytes.
|
|
||||||
text: Vec<char>,
|
|
||||||
|
|
||||||
/// The current cursor position in the preedit string.
|
|
||||||
cursor: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Windows waiting for IME events.
|
|
||||||
struct WindowData {
|
|
||||||
/// The window ID.
|
|
||||||
id: Window,
|
|
||||||
|
|
||||||
/// The style of the window.
|
|
||||||
style: Style,
|
|
||||||
|
|
||||||
/// Current "spot" for the context.
|
|
||||||
spot: Point,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
enum Style {
|
|
||||||
Preedit,
|
|
||||||
Nothing,
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImeData {
|
|
||||||
/// Creates the IME data for the display.
|
|
||||||
pub(super) fn new(
|
|
||||||
conn: &Arc<XConnection>,
|
|
||||||
screen: usize,
|
|
||||||
event_queue: &Rc<RefCell<VecDeque<Event>>>,
|
|
||||||
) -> Result<Self, X11Error> {
|
|
||||||
// IM servers to try, in order:
|
|
||||||
// - None, which defaults to the environment variable `XMODIFIERS` in xim's impl.
|
|
||||||
// - "local", which is the default for most IMEs.
|
|
||||||
// - empty string, which may work in some cases.
|
|
||||||
let input_methods = [None, Some("local"), Some("")];
|
|
||||||
let mut last_error = X11Error::Ime(ClientError::NoXimServer);
|
|
||||||
|
|
||||||
for im in input_methods {
|
|
||||||
// Try to initialize a client here.
|
|
||||||
match X11rbClient::init(conn.clone(), screen, im) {
|
|
||||||
Ok(client) => {
|
|
||||||
return Ok(Self {
|
|
||||||
client,
|
|
||||||
handler: ImeHandler {
|
|
||||||
event_queue: event_queue.clone(),
|
|
||||||
disconnected: true,
|
|
||||||
ime_events: VecDeque::new(),
|
|
||||||
pending_windows: VecDeque::new(),
|
|
||||||
styles: None,
|
|
||||||
input_method: None,
|
|
||||||
input_contexts: HashMap::new(),
|
|
||||||
window_contexts: HashMap::new(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!("Failed to create XIM client for {:?}: {err}", ImData(im));
|
|
||||||
last_error = X11Error::Ime(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(last_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Filter an event.
|
|
||||||
pub(super) fn filter_event(&mut self, event: &Event) -> Result<bool, X11Error> {
|
|
||||||
self.client
|
|
||||||
.filter_event(event, &mut self.handler)
|
|
||||||
.map_err(X11Error::Ime)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Connection to the X server.
|
|
||||||
fn conn(&self) -> &X11rbConnection {
|
|
||||||
self.client.conn()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an IME event.
|
|
||||||
pub(super) fn next_ime_event(&mut self) -> Option<(Window, ImeEvent)> {
|
|
||||||
self.handler.ime_events.pop_front()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new IME context for the provided window.
|
|
||||||
pub(super) fn create_context(
|
|
||||||
&mut self,
|
|
||||||
window: Window,
|
|
||||||
with_preedit: bool,
|
|
||||||
spot: Option<Point>,
|
|
||||||
) -> Result<bool, X11Error> {
|
|
||||||
// If we aren't connected, nothing can be done.
|
|
||||||
if self.handler.disconnected {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
let method = match self.handler.input_method {
|
|
||||||
Some(im) => im,
|
|
||||||
None => return Ok(false),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the current style.
|
|
||||||
let style = match (self.handler.styles, with_preedit) {
|
|
||||||
(None, _) => return Err(X11Error::InvalidImeState(InvalidImeState::NoStyle)),
|
|
||||||
(Some((preedit_style, _)), true) => preedit_style,
|
|
||||||
(Some((_, none_style)), false) => none_style,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Setup IC attributes.
|
|
||||||
let ic_attributes = {
|
|
||||||
let mut ic_attributes = self
|
|
||||||
.client
|
|
||||||
.build_ic_attributes()
|
|
||||||
.push(AttributeName::ClientWindow, window);
|
|
||||||
|
|
||||||
let ic_style = match style {
|
|
||||||
Style::Preedit => InputStyle::PREEDIT_POSITION | InputStyle::STATUS_NOTHING,
|
|
||||||
Style::Nothing => InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING,
|
|
||||||
Style::None => InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(spot) = spot.clone() {
|
|
||||||
ic_attributes = ic_attributes.push(AttributeName::SpotLocation, spot);
|
|
||||||
}
|
|
||||||
|
|
||||||
ic_attributes
|
|
||||||
.push(AttributeName::InputStyle, ic_style)
|
|
||||||
.build()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the IC.
|
|
||||||
self.client.create_ic(method, ic_attributes)?;
|
|
||||||
|
|
||||||
// Add to the waiting window list.
|
|
||||||
self.handler.pending_windows.push_back(WindowData {
|
|
||||||
id: window,
|
|
||||||
style,
|
|
||||||
spot: spot.unwrap_or(Point { x: 0, y: 0 }),
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove an IME context for a window.
|
|
||||||
pub(super) fn remove_context(&mut self, window: Window) -> Result<bool, X11Error> {
|
|
||||||
if self.handler.disconnected {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
let method = match self.handler.input_method {
|
|
||||||
Some(im) => im,
|
|
||||||
None => return Ok(false),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Remove the pending window if it's still pending.
|
|
||||||
let mut removed = false;
|
|
||||||
self.handler.pending_windows.retain(|pending| {
|
|
||||||
if pending.id == window {
|
|
||||||
removed = true;
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if removed {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the IC if it's already created.
|
|
||||||
if let Some(ic) = self.handler.window_contexts.remove(&window) {
|
|
||||||
self.handler.input_contexts.remove(&ic);
|
|
||||||
|
|
||||||
// Destroy the IC.
|
|
||||||
self.client.destroy_ic(method, ic)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Focus an IME context.
|
|
||||||
pub(super) fn focus_window(&mut self, window: Window) -> Result<bool, X11Error> {
|
|
||||||
if self.handler.disconnected {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let method = self.wait_for_method()?;
|
|
||||||
let ic = self.wait_for_context(window)?;
|
|
||||||
|
|
||||||
if let Some(ic) = ic {
|
|
||||||
self.client.set_focus(method, ic)?;
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unfocus an IME context.
|
|
||||||
pub(super) fn unfocus_window(&mut self, window: Window) -> Result<bool, X11Error> {
|
|
||||||
if self.handler.disconnected {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let method = self.wait_for_method()?;
|
|
||||||
let ic = self.wait_for_context(window)?;
|
|
||||||
|
|
||||||
if let Some(ic) = ic {
|
|
||||||
self.client.unset_focus(method, ic)?;
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the spot for an IME context.
|
|
||||||
pub(super) fn set_spot(&mut self, window: Window, x: i16, y: i16) -> Result<(), X11Error> {
|
|
||||||
if self.handler.disconnected {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let method = self.wait_for_method()?;
|
|
||||||
let ic = self.wait_for_context(window)?;
|
|
||||||
|
|
||||||
if let Some(ic) = ic {
|
|
||||||
// If the IC is not available, or if the spot is the same, then we don't need to update.
|
|
||||||
let ic_data = match self.handler.input_contexts.get_mut(&ic) {
|
|
||||||
Some(ic_data) => ic_data,
|
|
||||||
None => return Ok(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_point = Point { x, y };
|
|
||||||
if !matches!(ic_data.window.style, Style::None) || ic_data.window.spot == new_point {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_attrs = self
|
|
||||||
.client
|
|
||||||
.build_ic_attributes()
|
|
||||||
.push(AttributeName::SpotLocation, new_point.clone())
|
|
||||||
.build();
|
|
||||||
self.client.set_ic_values(method, ic, new_attrs)?;
|
|
||||||
|
|
||||||
// Indicate that we have a new spot.
|
|
||||||
debug_assert!(ic_data.new_spot.is_none());
|
|
||||||
ic_data.new_spot = Some(new_point);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn set_ime_allowed(
|
|
||||||
&mut self,
|
|
||||||
window: Window,
|
|
||||||
allowed: bool,
|
|
||||||
) -> Result<(), X11Error> {
|
|
||||||
if self.handler.disconnected {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the client info.
|
|
||||||
let _ = self.wait_for_method()?;
|
|
||||||
let ic = self.wait_for_context(window)?;
|
|
||||||
|
|
||||||
if let Some(ic) = ic {
|
|
||||||
let mut spot = None;
|
|
||||||
|
|
||||||
// See if we need to update the allowed state.
|
|
||||||
if let Some(ic_data) = self.handler.input_contexts.get(&ic) {
|
|
||||||
spot = Some(ic_data.window.spot.clone());
|
|
||||||
if matches!(ic_data.window.style, Style::None) != allowed {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete and re-install the IC.
|
|
||||||
self.remove_context(window)?;
|
|
||||||
self.create_context(window, allowed, spot)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wait for the input method to be set.
|
|
||||||
fn wait_for_method(&mut self) -> Result<u16, X11Error> {
|
|
||||||
loop {
|
|
||||||
if let Some(im) = self.handler.input_method {
|
|
||||||
return Ok(im);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait and hope the input method is set.
|
|
||||||
self.block_for_ime()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wait for an input context to be set.
|
|
||||||
fn wait_for_context(&mut self, window: Window) -> Result<Option<u16>, X11Error> {
|
|
||||||
if let Some(cid) = self.handler.window_contexts.get(&window) {
|
|
||||||
return Ok(Some(*cid));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the window isn't in our pending windows queue, there's no way for it to get an IC.
|
|
||||||
if !self
|
|
||||||
.handler
|
|
||||||
.pending_windows
|
|
||||||
.iter()
|
|
||||||
.any(|WindowData { id, .. }| *id == window)
|
|
||||||
{
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
self.block_for_ime()?;
|
|
||||||
if let Some(cid) = self.handler.window_contexts.get(&window) {
|
|
||||||
return Ok(Some(*cid));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wait until we've acted on an IME event.
|
|
||||||
fn block_for_ime(&mut self) -> Result<(), X11Error> {
|
|
||||||
let mut last_event = self.conn().poll_for_event()?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if let Some(last_event) = last_event.as_ref() {
|
|
||||||
if self.filter_event(last_event)? {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This scope keeps track of the event queue handle.
|
|
||||||
{
|
|
||||||
// Check the event queue for events.
|
|
||||||
let event_queue = self.handler.event_queue.clone();
|
|
||||||
let mut event_queue = event_queue.borrow_mut();
|
|
||||||
|
|
||||||
// Check the event queue for events we can use.
|
|
||||||
let mut found_event = false;
|
|
||||||
let mut last_err = None;
|
|
||||||
event_queue.retain(|event| match self.filter_event(event) {
|
|
||||||
Ok(false) => {
|
|
||||||
found_event = true;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Ok(true) => false,
|
|
||||||
Err(err) => {
|
|
||||||
last_err = Some(err);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Push our own event to the queue.
|
|
||||||
if let Some(last_event) = last_event.take() {
|
|
||||||
event_queue.push_back(last_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for errors.
|
|
||||||
if let Some(err) = last_err {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we found an event, then we're done.
|
|
||||||
if found_event {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!("Waiting for IME event");
|
|
||||||
last_event = Some(self.conn().wait_for_event()?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: xim::Client> ClientHandler<C> for ImeHandler {
|
|
||||||
fn handle_connect(&mut self, client: &mut C) -> Result<(), ClientError> {
|
|
||||||
// We have been connected, now request a new input method for our current locale.
|
|
||||||
self.disconnected = false;
|
|
||||||
client.open(&locale())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_disconnect(&mut self) {
|
|
||||||
// We are now disconnected.
|
|
||||||
self.disconnected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_open(&mut self, client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
|
|
||||||
// We now have an input method.
|
|
||||||
debug_assert!(self.input_method.is_none());
|
|
||||||
self.input_method = Some(input_method_id);
|
|
||||||
|
|
||||||
// Ask for the IM's attributes.
|
|
||||||
client.get_im_values(input_method_id, &[AttributeName::QueryInputStyle])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_close(&mut self, _client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
|
|
||||||
// No more input method.
|
|
||||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
|
||||||
self.input_method = None;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_get_im_values(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
input_method_id: u16,
|
|
||||||
mut attributes: xim::AHashMap<xim::AttributeName, Vec<u8>>,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
|
||||||
|
|
||||||
// Get the input styles.
|
|
||||||
let mut preedit_style = None;
|
|
||||||
let mut none_style = None;
|
|
||||||
|
|
||||||
let styles = {
|
|
||||||
let style = attributes
|
|
||||||
.remove(&AttributeName::QueryInputStyle)
|
|
||||||
.expect("No query input style");
|
|
||||||
let mut result = vec![0u32; style.len() / 4];
|
|
||||||
|
|
||||||
bytemuck::cast_slice_mut::<u32, u8>(&mut result).copy_from_slice(&style);
|
|
||||||
|
|
||||||
result
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
// The styles that we're looking for.
|
|
||||||
let lu_preedit_style = InputStyle::PREEDIT_CALLBACKS | InputStyle::STATUS_NOTHING;
|
|
||||||
let lu_nothing_style = InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING;
|
|
||||||
let lu_none_style = InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE;
|
|
||||||
|
|
||||||
for style in styles {
|
|
||||||
let style = InputStyle::from_bits_truncate(style);
|
|
||||||
|
|
||||||
if style == lu_preedit_style {
|
|
||||||
preedit_style = Some(Style::Preedit);
|
|
||||||
} else if style == lu_nothing_style {
|
|
||||||
preedit_style = Some(Style::Nothing);
|
|
||||||
} else if style == lu_none_style {
|
|
||||||
none_style = Some(Style::None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (preedit_style, none_style) = match (preedit_style, none_style) {
|
|
||||||
(None, None) => {
|
|
||||||
log::error!("No supported input styles found");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
(Some(style), None) | (None, Some(style)) => (style, style),
|
|
||||||
|
|
||||||
(Some(preedit_style), Some(none_style)) => (preedit_style, none_style),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.styles = Some((preedit_style, none_style));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_create_ic(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
input_method_id: u16,
|
|
||||||
input_context_id: u16,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
|
||||||
|
|
||||||
// Get the window that wanted the IC context.
|
|
||||||
let window = self
|
|
||||||
.pending_windows
|
|
||||||
.pop_front()
|
|
||||||
.ok_or_else(|| invalid_state(InvalidImeState::NoWindows))?;
|
|
||||||
|
|
||||||
// Create the IC data.
|
|
||||||
let ic_data = IcData {
|
|
||||||
window,
|
|
||||||
new_spot: None,
|
|
||||||
text: Vec::new(),
|
|
||||||
cursor: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Store the context.
|
|
||||||
let (window, style) = (ic_data.window.id, ic_data.window.style);
|
|
||||||
self.input_contexts.insert(input_context_id, ic_data);
|
|
||||||
self.window_contexts.insert(window, input_context_id);
|
|
||||||
|
|
||||||
// Indicate our status.
|
|
||||||
let event = if matches!(style, Style::Nothing) {
|
|
||||||
ImeEvent::Disabled
|
|
||||||
} else {
|
|
||||||
ImeEvent::Enabled
|
|
||||||
};
|
|
||||||
self.ime_events.push_back((window, event));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_destroy_ic(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
_input_method_id: u16,
|
|
||||||
_input_context_id: u16,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
// This is already handled by the higher-level function.
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_set_ic_values(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
input_method_id: u16,
|
|
||||||
input_context_id: u16,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
|
||||||
|
|
||||||
// Get the IC data.
|
|
||||||
let ic_data = self
|
|
||||||
.input_contexts
|
|
||||||
.get_mut(&input_context_id)
|
|
||||||
.ok_or_else(|| invalid_state(InvalidImeState::InvalidIc(input_context_id)))?;
|
|
||||||
|
|
||||||
// Move up the new spot
|
|
||||||
if let Some(spot) = ic_data.new_spot.take() {
|
|
||||||
ic_data.window.spot = spot;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_preedit_start(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
input_method_id: u16,
|
|
||||||
input_context_id: u16,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
|
||||||
|
|
||||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
|
||||||
// Start a pre-edit.
|
|
||||||
ic_data.text.clear();
|
|
||||||
ic_data.cursor = 0;
|
|
||||||
|
|
||||||
// Indicate the start.
|
|
||||||
self.ime_events
|
|
||||||
.push_back((ic_data.window.id, ImeEvent::Start));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_preedit_draw(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
input_method_id: u16,
|
|
||||||
input_context_id: u16,
|
|
||||||
caret: i32,
|
|
||||||
chg_first: i32,
|
|
||||||
chg_len: i32,
|
|
||||||
_status: xim::PreeditDrawStatus,
|
|
||||||
preedit_string: &str,
|
|
||||||
_feedbacks: Vec<xim::Feedback>,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
|
||||||
|
|
||||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
|
||||||
// Set the cursor.
|
|
||||||
ic_data.cursor = caret as usize;
|
|
||||||
|
|
||||||
// Figure out the range of text to change.
|
|
||||||
let change_range = chg_first as usize..(chg_first + chg_len) as usize;
|
|
||||||
|
|
||||||
// If the range doesn't fit our current text, warn and return.
|
|
||||||
if change_range.start > ic_data.text.len() || change_range.end > ic_data.text.len() {
|
|
||||||
warn!(
|
|
||||||
"Preedit draw range {}..{} doesn't fit text of length {}",
|
|
||||||
change_range.start,
|
|
||||||
change_range.end,
|
|
||||||
ic_data.text.len()
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the text in the changed range.
|
|
||||||
{
|
|
||||||
let text = &mut ic_data.text;
|
|
||||||
let mut old_text_tail = text.split_off(change_range.end);
|
|
||||||
|
|
||||||
text.truncate(change_range.start);
|
|
||||||
text.extend(preedit_string.chars());
|
|
||||||
text.append(&mut old_text_tail);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the event.
|
|
||||||
let cursor_byte_pos = calc_byte_position(&ic_data.text, ic_data.cursor);
|
|
||||||
let event = ImeEvent::Update(ic_data.text.iter().collect(), Some(cursor_byte_pos));
|
|
||||||
|
|
||||||
self.ime_events.push_back((ic_data.window.id, event));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_preedit_caret(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
input_method_id: u16,
|
|
||||||
input_context_id: u16,
|
|
||||||
position: &mut i32,
|
|
||||||
direction: xim::CaretDirection,
|
|
||||||
_style: xim::CaretStyle,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
// We only care about absolute position.
|
|
||||||
if matches!(direction, xim::CaretDirection::AbsolutePosition) {
|
|
||||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
|
||||||
|
|
||||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
|
||||||
ic_data.cursor = *position as usize;
|
|
||||||
|
|
||||||
// Send the event
|
|
||||||
let event =
|
|
||||||
ImeEvent::Update(ic_data.text.iter().collect(), Some(*position as usize));
|
|
||||||
self.ime_events.push_back((ic_data.window.id, event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_preedit_done(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
input_method_id: u16,
|
|
||||||
input_context_id: u16,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
|
||||||
|
|
||||||
// Get the client data.
|
|
||||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
|
||||||
// We're done with a preedit.
|
|
||||||
ic_data.text.clear();
|
|
||||||
ic_data.cursor = 0;
|
|
||||||
|
|
||||||
// Send a message to the window.
|
|
||||||
let window = ic_data.window.id;
|
|
||||||
self.ime_events.push_back((window, ImeEvent::End));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_commit(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
input_method_id: u16,
|
|
||||||
input_context_id: u16,
|
|
||||||
text: &str,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
|
||||||
|
|
||||||
// Get the client data.
|
|
||||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
|
||||||
// Send a message to the window.
|
|
||||||
let window = ic_data.window.id;
|
|
||||||
self.ime_events
|
|
||||||
.push_back((window, ImeEvent::Commit(text.to_owned())));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_query_extension(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
_extensions: &[xim::Extension],
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
// Don't care.
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_forward_event(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
_input_method_id: u16,
|
|
||||||
_input_context_id: u16,
|
|
||||||
_flag: xim::ForwardEventFlag,
|
|
||||||
_xev: C::XEvent,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
// Don't care.
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_set_event_mask(
|
|
||||||
&mut self,
|
|
||||||
_client: &mut C,
|
|
||||||
_input_method_id: u16,
|
|
||||||
_input_context_id: u16,
|
|
||||||
_forward_event_mask: u32,
|
|
||||||
_synchronous_event_mask: u32,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
// Don't care.
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn invalid_state(state: InvalidImeState) -> ClientError {
|
|
||||||
ClientError::Other(Box::new(X11Error::InvalidImeState(state)))
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ImData(Option<&'static str>);
|
|
||||||
|
|
||||||
impl fmt::Debug for ImData {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self.0 {
|
|
||||||
Some(name) => write!(f, "\"{}\"", name),
|
|
||||||
None => write!(f, "default input method"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current locale.
|
|
||||||
fn locale() -> String {
|
|
||||||
use std::ffi::CStr;
|
|
||||||
|
|
||||||
const EN_US: &str = "en_US.UTF-8";
|
|
||||||
|
|
||||||
// Get the pointer to the current locale.
|
|
||||||
let locale_ptr = unsafe { libc::setlocale(libc::LC_CTYPE, std::ptr::null()) };
|
|
||||||
|
|
||||||
// If locale_ptr is null, just default to en_US.UTF-8.
|
|
||||||
if locale_ptr.is_null() {
|
|
||||||
return EN_US.to_owned();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the pointer to a CStr.
|
|
||||||
let locale_cstr = unsafe { CStr::from_ptr(locale_ptr) };
|
|
||||||
|
|
||||||
// Convert the CStr to a String to prevent the result from getting clobbered.
|
|
||||||
locale_cstr.to_str().unwrap_or(EN_US).to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calc_byte_position(text: &[char], pos: usize) -> usize {
|
|
||||||
text.iter().take(pos).map(|c| c.len_utf8()).sum()
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
use x11rb::protocol::{
|
|
||||||
xinput::{self, ConnectionExt as _},
|
|
||||||
xkb,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub const VIRTUAL_CORE_POINTER: u16 = 2;
|
|
||||||
pub const VIRTUAL_CORE_KEYBOARD: u16 = 3;
|
|
||||||
|
|
||||||
impl XConnection {
|
|
||||||
pub fn select_xinput_events(
|
|
||||||
&self,
|
|
||||||
window: xproto::Window,
|
|
||||||
device_id: u16,
|
|
||||||
mask: xinput::XIEventMask,
|
|
||||||
) -> Result<VoidCookie<'_>, X11Error> {
|
|
||||||
self.xcb_connection()
|
|
||||||
.xinput_xi_select_events(
|
|
||||||
window,
|
|
||||||
&[xinput::EventMask {
|
|
||||||
deviceid: device_id,
|
|
||||||
mask: vec![mask],
|
|
||||||
}],
|
|
||||||
)
|
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select_xkb_events(
|
|
||||||
&self,
|
|
||||||
device_id: xkb::DeviceSpec,
|
|
||||||
mask: xkb::EventType,
|
|
||||||
) -> Result<bool, X11Error> {
|
|
||||||
let mask = u16::from(mask) as _;
|
|
||||||
let status =
|
|
||||||
unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id as _, mask, mask) };
|
|
||||||
|
|
||||||
if status == ffi::True {
|
|
||||||
self.flush_requests()?;
|
|
||||||
Ok(true)
|
|
||||||
} else {
|
|
||||||
error!("Could not select XKB events: The XKB extension is not initialized!");
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn query_pointer(
|
|
||||||
&self,
|
|
||||||
window: xproto::Window,
|
|
||||||
device_id: u16,
|
|
||||||
) -> Result<xinput::XIQueryPointerReply, X11Error> {
|
|
||||||
self.xcb_connection()
|
|
||||||
.xinput_xi_query_pointer(window, device_id)?
|
|
||||||
.reply()
|
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
#![allow(clippy::unnecessary_cast)]
|
|
||||||
|
|
||||||
use icrate::Foundation::NSObject;
|
|
||||||
use objc2::{declare_class, msg_send, mutability, ClassType};
|
|
||||||
|
|
||||||
use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
|
|
||||||
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;
|
|
||||||
const NAME: &'static str = "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)
|
|
||||||
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
|
|
||||||
#[method(sendEvent:)]
|
|
||||||
fn send_event(&self, event: &NSEvent) {
|
|
||||||
// For posterity, there are some undocumented event types
|
|
||||||
// (https://github.com/servo/cocoa-rs/issues/155)
|
|
||||||
// but that doesn't really matter here.
|
|
||||||
let event_type = event.type_();
|
|
||||||
let modifier_flags = event.modifierFlags();
|
|
||||||
if event_type == NSEventType::NSKeyUp
|
|
||||||
&& modifier_flags.contains(NSEventModifierFlags::NSCommandKeyMask)
|
|
||||||
{
|
|
||||||
if let Some(key_window) = self.keyWindow() {
|
|
||||||
unsafe { key_window.sendEvent(event) };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
maybe_dispatch_device_event(event);
|
|
||||||
unsafe { msg_send![super(self), sendEvent: event] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
fn maybe_dispatch_device_event(event: &NSEvent) {
|
|
||||||
let event_type = event.type_();
|
|
||||||
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;
|
|
||||||
|
|
||||||
if delta_x != 0.0 {
|
|
||||||
queue_device_event(DeviceEvent::Motion {
|
|
||||||
axis: 0,
|
|
||||||
value: delta_x,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if delta_y != 0.0 {
|
|
||||||
queue_device_event(DeviceEvent::Motion {
|
|
||||||
axis: 1,
|
|
||||||
value: delta_y,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if delta_x != 0.0 || delta_y != 0.0 {
|
|
||||||
queue_device_event(DeviceEvent::MouseMotion {
|
|
||||||
delta: (delta_x, delta_y),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NSEventType::NSLeftMouseDown
|
|
||||||
| NSEventType::NSRightMouseDown
|
|
||||||
| NSEventType::NSOtherMouseDown => {
|
|
||||||
queue_device_event(DeviceEvent::Button {
|
|
||||||
button: event.buttonNumber() as u32,
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
NSEventType::NSLeftMouseUp | NSEventType::NSRightMouseUp | NSEventType::NSOtherMouseUp => {
|
|
||||||
queue_device_event(DeviceEvent::Button {
|
|
||||||
button: event.buttonNumber() as u32,
|
|
||||||
state: ElementState::Released,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn queue_device_event(event: DeviceEvent) {
|
|
||||||
let event = Event::DeviceEvent {
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
event,
|
|
||||||
};
|
|
||||||
AppState::queue_event(event);
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
use std::ptr::NonNull;
|
|
||||||
|
|
||||||
use icrate::Foundation::NSObject;
|
|
||||||
use objc2::declare::{IvarBool, IvarEncode};
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::runtime::AnyObject;
|
|
||||||
use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType};
|
|
||||||
|
|
||||||
use super::app_state::AppState;
|
|
||||||
use super::appkit::NSApplicationActivationPolicy;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
unsafe impl ClassType for ApplicationDelegate {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationWillTerminate:)]
|
|
||||||
fn will_terminate(&self, _sender: Option<&AnyObject>) {
|
|
||||||
trace_scope!("applicationWillTerminate:");
|
|
||||||
// TODO: Notify every window that it will be destroyed, like done in iOS?
|
|
||||||
AppState::internal_exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
impl ApplicationDelegate {
|
|
||||||
pub(super) fn new(
|
|
||||||
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,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,698 +0,0 @@
|
|||||||
use std::{
|
|
||||||
cell::{RefCell, RefMut},
|
|
||||||
collections::VecDeque,
|
|
||||||
fmt::{self, Debug},
|
|
||||||
mem,
|
|
||||||
rc::{Rc, Weak},
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
mpsc, Arc, Mutex, MutexGuard,
|
|
||||||
},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};
|
|
||||||
use icrate::Foundation::{is_main_thread, 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,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
dpi::PhysicalSize,
|
|
||||||
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
|
|
||||||
window::WindowId,
|
|
||||||
};
|
|
||||||
|
|
||||||
static HANDLER: Lazy<Handler> = Lazy::new(Default::default);
|
|
||||||
|
|
||||||
impl<Never> Event<Never> {
|
|
||||||
fn userify<T: 'static>(self) -> Event<T> {
|
|
||||||
self.map_nonuser_event()
|
|
||||||
// `Never` can't be constructed, so the `UserEvent` variant can't
|
|
||||||
// be present here.
|
|
||||||
.unwrap_or_else(|_| unreachable!())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait EventHandler: Debug {
|
|
||||||
// Not sure probably it should accept Event<'static, Never>
|
|
||||||
fn handle_nonuser_event(&mut self, event: Event<Never>);
|
|
||||||
fn handle_user_events(&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) type Callback<T> = RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>)>;
|
|
||||||
|
|
||||||
struct EventLoopHandler<T: 'static> {
|
|
||||||
callback: Weak<Callback<T>>,
|
|
||||||
window_target: Rc<RootWindowTarget<T>>,
|
|
||||||
receiver: Rc<mpsc::Receiver<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopHandler<T> {
|
|
||||||
fn with_callback<F>(&mut self, f: F)
|
|
||||||
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
|
|
||||||
// `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`
|
|
||||||
if let Some(callback) = self.callback.upgrade() {
|
|
||||||
let callback = callback.borrow_mut();
|
|
||||||
(f)(self, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Debug for EventLoopHandler<T> {
|
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
formatter
|
|
||||||
.debug_struct("EventLoopHandler")
|
|
||||||
.field("window_target", &self.window_target)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventHandler for EventLoopHandler<T> {
|
|
||||||
fn handle_nonuser_event(&mut self, event: Event<Never>) {
|
|
||||||
self.with_callback(|this, mut callback| {
|
|
||||||
(callback)(event.userify(), &this.window_target);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_user_events(&mut self) {
|
|
||||||
self.with_callback(|this, mut callback| {
|
|
||||||
for event in this.receiver.try_iter() {
|
|
||||||
(callback)(Event::UserEvent(event), &this.window_target);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum EventWrapper {
|
|
||||||
StaticEvent(Event<Never>),
|
|
||||||
ScaleFactorChanged {
|
|
||||||
window: Id<WinitWindow>,
|
|
||||||
suggested_size: PhysicalSize<u32>,
|
|
||||||
scale_factor: f64,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Handler {
|
|
||||||
stop_app_on_launch: AtomicBool,
|
|
||||||
stop_app_before_wait: AtomicBool,
|
|
||||||
stop_app_after_wait: AtomicBool,
|
|
||||||
stop_app_on_redraw: AtomicBool,
|
|
||||||
launched: AtomicBool,
|
|
||||||
running: AtomicBool,
|
|
||||||
in_callback: AtomicBool,
|
|
||||||
control_flow: Mutex<ControlFlow>,
|
|
||||||
exit: AtomicBool,
|
|
||||||
start_time: Mutex<Option<Instant>>,
|
|
||||||
callback: Mutex<Option<Box<dyn EventHandler>>>,
|
|
||||||
pending_events: Mutex<VecDeque<EventWrapper>>,
|
|
||||||
pending_redraw: Mutex<Vec<WindowId>>,
|
|
||||||
wait_timeout: Mutex<Option<Instant>>,
|
|
||||||
waker: Mutex<EventLoopWaker>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for Handler {}
|
|
||||||
unsafe impl Sync for Handler {}
|
|
||||||
|
|
||||||
impl Handler {
|
|
||||||
fn events(&self) -> MutexGuard<'_, VecDeque<EventWrapper>> {
|
|
||||||
self.pending_events.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn redraw(&self) -> MutexGuard<'_, Vec<WindowId>> {
|
|
||||||
self.pending_redraw.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn waker(&self) -> MutexGuard<'_, EventLoopWaker> {
|
|
||||||
self.waker.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `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.
|
|
||||||
fn is_launched(&self) -> bool {
|
|
||||||
self.launched.load(Ordering::Acquire)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set via `ApplicationDelegate::applicationDidFinishLaunching`
|
|
||||||
fn set_launched(&self) {
|
|
||||||
self.launched.store(true, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `true` if an `EventLoop` is currently running
|
|
||||||
///
|
|
||||||
/// NB: This is global / `NSApp` state and may persist beyond the lifetime of
|
|
||||||
/// a running `EventLoop`.
|
|
||||||
///
|
|
||||||
/// # Caveat
|
|
||||||
/// This is only intended to be called from the main thread
|
|
||||||
fn is_running(&self) -> bool {
|
|
||||||
self.running.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set when an `EventLoop` starts running, after the `NSApp` is launched
|
|
||||||
///
|
|
||||||
/// # Caveat
|
|
||||||
/// This is only intended to be called from the main thread
|
|
||||||
fn set_running(&self) {
|
|
||||||
self.running.store(true, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits
|
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
///
|
|
||||||
/// # Caveat
|
|
||||||
/// This is only intended to be called from the main thread
|
|
||||||
fn internal_exit(&self) {
|
|
||||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
|
||||||
// interiour mutability
|
|
||||||
//
|
|
||||||
// XXX: As an aside; having each individual bit of state for `Handler` be atomic or wrapped in a
|
|
||||||
// `Mutex` for the sake of interior mutability seems a bit odd, and also a potential foot
|
|
||||||
// gun in case the state is unwittingly accessed across threads because the fine-grained locking
|
|
||||||
// wouldn't ensure that there's interior consistency.
|
|
||||||
//
|
|
||||||
// Maybe the whole thing should just be put in a static `Mutex<>` to make it clear
|
|
||||||
// the we can mutate more than one peice of state while maintaining consistency. (though it
|
|
||||||
// looks like there have been recuring re-entrancy issues with callback handling that might
|
|
||||||
// make that awkward)
|
|
||||||
self.running.store(false, Ordering::Relaxed);
|
|
||||||
self.set_stop_app_on_redraw_requested(false);
|
|
||||||
self.set_stop_app_before_wait(false);
|
|
||||||
self.set_stop_app_after_wait(false);
|
|
||||||
self.set_wait_timeout(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exit(&self) {
|
|
||||||
self.exit.store(true, Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exiting(&self) -> bool {
|
|
||||||
self.exit.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_stop_app_on_launch(&self) {
|
|
||||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
|
||||||
// interior mutability
|
|
||||||
self.stop_app_on_launch.store(true, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn should_stop_app_on_launch(&self) -> bool {
|
|
||||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
|
||||||
// interior mutability
|
|
||||||
self.stop_app_on_launch.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_stop_app_before_wait(&self, stop_before_wait: bool) {
|
|
||||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
|
||||||
// interior mutability
|
|
||||||
self.stop_app_before_wait
|
|
||||||
.store(stop_before_wait, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn should_stop_app_before_wait(&self) -> bool {
|
|
||||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
|
||||||
// interior mutability
|
|
||||||
self.stop_app_before_wait.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_stop_app_after_wait(&self, stop_after_wait: bool) {
|
|
||||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
|
||||||
// interior mutability
|
|
||||||
self.stop_app_after_wait
|
|
||||||
.store(stop_after_wait, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_wait_timeout(&self, new_timeout: Option<Instant>) {
|
|
||||||
let mut timeout = self.wait_timeout.lock().unwrap();
|
|
||||||
*timeout = new_timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait_timeout(&self) -> Option<Instant> {
|
|
||||||
*self.wait_timeout.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn should_stop_app_after_wait(&self) -> bool {
|
|
||||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
|
||||||
// interior mutability
|
|
||||||
self.stop_app_after_wait.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_stop_app_on_redraw_requested(&self, stop_on_redraw: bool) {
|
|
||||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
|
||||||
// interior mutability
|
|
||||||
self.stop_app_on_redraw
|
|
||||||
.store(stop_on_redraw, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn should_stop_app_on_redraw_requested(&self) -> bool {
|
|
||||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
|
||||||
// interior mutability
|
|
||||||
self.stop_app_on_redraw.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_control_flow(&self, new_control_flow: ControlFlow) {
|
|
||||||
*self.control_flow.lock().unwrap() = new_control_flow
|
|
||||||
}
|
|
||||||
|
|
||||||
fn control_flow(&self) -> ControlFlow {
|
|
||||||
*self.control_flow.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_start_time(&self) -> Option<Instant> {
|
|
||||||
*self.start_time.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_start_time(&self) {
|
|
||||||
*self.start_time.lock().unwrap() = Some(Instant::now());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take_events(&self) -> VecDeque<EventWrapper> {
|
|
||||||
mem::take(&mut *self.events())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_redraw(&self) -> Vec<WindowId> {
|
|
||||||
mem::take(&mut *self.redraw())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_in_callback(&self) -> bool {
|
|
||||||
self.in_callback.load(Ordering::Acquire)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_in_callback(&self, in_callback: bool) {
|
|
||||||
self.in_callback.store(in_callback, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn have_callback(&self) -> bool {
|
|
||||||
self.callback.lock().unwrap().is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_nonuser_event(&self, event: Event<Never>) {
|
|
||||||
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
|
|
||||||
callback.handle_nonuser_event(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_user_events(&self) {
|
|
||||||
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
|
|
||||||
callback.handle_user_events();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_scale_factor_changed_event(
|
|
||||||
&self,
|
|
||||||
window: &WinitWindow,
|
|
||||||
suggested_size: PhysicalSize<u32>,
|
|
||||||
scale_factor: f64,
|
|
||||||
) {
|
|
||||||
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
|
|
||||||
let new_inner_size = Arc::new(Mutex::new(suggested_size));
|
|
||||||
let scale_factor_changed_event = Event::WindowEvent {
|
|
||||||
window_id: WindowId(window.id()),
|
|
||||||
event: WindowEvent::ScaleFactorChanged {
|
|
||||||
scale_factor,
|
|
||||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) enum AppState {}
|
|
||||||
|
|
||||||
impl AppState {
|
|
||||||
/// Associate the application's event callback with the (global static) Handler state
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// This is ignoring the lifetime of the application callback (which may not be 'static)
|
|
||||||
/// and can lead to undefined behaviour if the callback is not cleared before the end of
|
|
||||||
/// its real lifetime.
|
|
||||||
///
|
|
||||||
/// All public APIs that take an event callback (`run`, `run_on_demand`,
|
|
||||||
/// `pump_events`) _must_ pair a call to `set_callback` with
|
|
||||||
/// a call to `clear_callback` before returning to avoid undefined behaviour.
|
|
||||||
pub unsafe fn set_callback<T>(
|
|
||||||
callback: Weak<Callback<T>>,
|
|
||||||
window_target: Rc<RootWindowTarget<T>>,
|
|
||||||
receiver: Rc<mpsc::Receiver<T>>,
|
|
||||||
) {
|
|
||||||
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
|
|
||||||
callback,
|
|
||||||
window_target,
|
|
||||||
receiver,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_callback() {
|
|
||||||
HANDLER.callback.lock().unwrap().take();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_launched() -> bool {
|
|
||||||
HANDLER.is_launched()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_running() -> bool {
|
|
||||||
HANDLER.is_running()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// `pump_events`
|
|
||||||
pub fn request_stop_on_launch() {
|
|
||||||
HANDLER.request_stop_app_on_launch();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_stop_app_before_wait(stop_before_wait: bool) {
|
|
||||||
HANDLER.set_stop_app_before_wait(stop_before_wait);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_stop_app_after_wait(stop_after_wait: bool) {
|
|
||||||
HANDLER.set_stop_app_after_wait(stop_after_wait);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_wait_timeout(timeout: Option<Instant>) {
|
|
||||||
HANDLER.set_wait_timeout(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_stop_app_on_redraw_requested(stop_on_redraw: bool) {
|
|
||||||
HANDLER.set_stop_app_on_redraw_requested(stop_on_redraw);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_control_flow(control_flow: ControlFlow) {
|
|
||||||
HANDLER.set_control_flow(control_flow)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn control_flow() -> ControlFlow {
|
|
||||||
HANDLER.control_flow()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn internal_exit() {
|
|
||||||
HANDLER.set_in_callback(true);
|
|
||||||
HANDLER.handle_nonuser_event(Event::LoopExiting);
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
HANDLER.internal_exit();
|
|
||||||
Self::clear_callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exit() {
|
|
||||||
HANDLER.exit()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exiting() -> bool {
|
|
||||||
HANDLER.exiting()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dispatch_init_events() {
|
|
||||||
HANDLER.set_in_callback(true);
|
|
||||||
HANDLER.handle_nonuser_event(Event::NewEvents(StartCause::Init));
|
|
||||||
// NB: For consistency all platforms must emit a 'resumed' event even though macOS
|
|
||||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
|
||||||
HANDLER.handle_nonuser_event(Event::Resumed);
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_running() {
|
|
||||||
debug_assert!(HANDLER.is_launched());
|
|
||||||
|
|
||||||
HANDLER.set_running();
|
|
||||||
Self::dispatch_init_events()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn launched(
|
|
||||||
activation_policy: NSApplicationActivationPolicy,
|
|
||||||
create_default_menu: bool,
|
|
||||||
activate_ignoring_other_apps: bool,
|
|
||||||
) {
|
|
||||||
let app = NSApp();
|
|
||||||
// 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);
|
|
||||||
app.activateIgnoringOtherApps(activate_ignoring_other_apps);
|
|
||||||
|
|
||||||
HANDLER.set_launched();
|
|
||||||
HANDLER.waker().start();
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::start_running();
|
|
||||||
|
|
||||||
// If the `NSApp` 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`
|
|
||||||
Self::stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by RunLoopObserver after finishing waiting for new events
|
|
||||||
pub fn wakeup(panic_info: Weak<PanicInfo>) {
|
|
||||||
let panic_info = panic_info
|
|
||||||
.upgrade()
|
|
||||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
|
||||||
|
|
||||||
// Return when in 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()
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if HANDLER.should_stop_app_after_wait() {
|
|
||||||
Self::stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
let start = HANDLER.get_start_time().unwrap();
|
|
||||||
let cause = match HANDLER.control_flow() {
|
|
||||||
ControlFlow::Poll => StartCause::Poll,
|
|
||||||
ControlFlow::Wait => StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: None,
|
|
||||||
},
|
|
||||||
ControlFlow::WaitUntil(requested_resume) => {
|
|
||||||
if Instant::now() >= requested_resume {
|
|
||||||
StartCause::ResumeTimeReached {
|
|
||||||
start,
|
|
||||||
requested_resume,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: Some(requested_resume),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
HANDLER.set_in_callback(true);
|
|
||||||
HANDLER.handle_nonuser_event(Event::NewEvents(cause));
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is called from multiple threads at present
|
|
||||||
pub fn queue_redraw(window_id: WindowId) {
|
|
||||||
let mut pending_redraw = HANDLER.redraw();
|
|
||||||
if !pending_redraw.contains(&window_id) {
|
|
||||||
pending_redraw.push(window_id);
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
let rl = CFRunLoopGetMain();
|
|
||||||
CFRunLoopWakeUp(rl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_redraw(window_id: WindowId) {
|
|
||||||
// Redraw request might come out of order from the OS.
|
|
||||||
// -> Don't go back into the callback when our callstack originates from there
|
|
||||||
if !HANDLER.in_callback.swap(true, Ordering::AcqRel) {
|
|
||||||
HANDLER.handle_nonuser_event(Event::WindowEvent {
|
|
||||||
window_id,
|
|
||||||
event: WindowEvent::RedrawRequested,
|
|
||||||
});
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
|
|
||||||
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested events
|
|
||||||
// as a way to ensure that `pump_events` can't block an external loop indefinitely
|
|
||||||
if HANDLER.should_stop_app_on_redraw_requested() {
|
|
||||||
AppState::stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue_event(event: Event<Never>) {
|
|
||||||
if !is_main_thread() {
|
|
||||||
panic!("Event queued from different thread: {event:#?}");
|
|
||||||
}
|
|
||||||
HANDLER.events().push_back(EventWrapper::StaticEvent(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue_static_scale_factor_changed_event(
|
|
||||||
window: Id<WinitWindow>,
|
|
||||||
suggested_size: PhysicalSize<u32>,
|
|
||||||
scale_factor: f64,
|
|
||||||
) {
|
|
||||||
HANDLER
|
|
||||||
.events()
|
|
||||||
.push_back(EventWrapper::ScaleFactorChanged {
|
|
||||||
window,
|
|
||||||
suggested_size,
|
|
||||||
scale_factor,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop() {
|
|
||||||
let app = NSApp();
|
|
||||||
autoreleasepool(|_| {
|
|
||||||
app.stop(None);
|
|
||||||
// To stop event loop immediately, we need to post some event here.
|
|
||||||
app.postEvent_atStart(&NSEvent::dummy(), true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by RunLoopObserver before waiting for new events
|
|
||||||
pub fn cleared(panic_info: Weak<PanicInfo>) {
|
|
||||||
let panic_info = panic_info
|
|
||||||
.upgrade()
|
|
||||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
|
||||||
|
|
||||||
// Return when in callback due to https://github.com/rust-windowing/winit/issues/1779
|
|
||||||
// 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()
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLER.set_in_callback(true);
|
|
||||||
HANDLER.handle_user_events();
|
|
||||||
for event in HANDLER.take_events() {
|
|
||||||
match event {
|
|
||||||
EventWrapper::StaticEvent(event) => {
|
|
||||||
HANDLER.handle_nonuser_event(event);
|
|
||||||
}
|
|
||||||
EventWrapper::ScaleFactorChanged {
|
|
||||||
window,
|
|
||||||
suggested_size,
|
|
||||||
scale_factor,
|
|
||||||
} => {
|
|
||||||
HANDLER.handle_scale_factor_changed_event(
|
|
||||||
&window,
|
|
||||||
suggested_size,
|
|
||||||
scale_factor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for window_id in HANDLER.should_redraw() {
|
|
||||||
HANDLER.handle_nonuser_event(Event::WindowEvent {
|
|
||||||
window_id,
|
|
||||||
event: WindowEvent::RedrawRequested,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLER.handle_nonuser_event(Event::AboutToWait);
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
|
|
||||||
if HANDLER.exiting() {
|
|
||||||
Self::stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if HANDLER.should_stop_app_before_wait() {
|
|
||||||
Self::stop();
|
|
||||||
}
|
|
||||||
HANDLER.update_start_time();
|
|
||||||
let wait_timeout = HANDLER.wait_timeout(); // configured by pump_events
|
|
||||||
let app_timeout = match HANDLER.control_flow() {
|
|
||||||
ControlFlow::Wait => None,
|
|
||||||
ControlFlow::Poll => Some(Instant::now()),
|
|
||||||
ControlFlow::WaitUntil(instant) => Some(instant),
|
|
||||||
};
|
|
||||||
HANDLER
|
|
||||||
.waker()
|
|
||||||
.start_at(min_timeout(wait_timeout, app_timeout));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the minimum `Option<Instant>`, taking into account that `None`
|
|
||||||
/// equates to an infinite timeout, not a zero timeout (so can't just use
|
|
||||||
/// `Option::min`)
|
|
||||||
fn min_timeout(a: Option<Instant>, b: Option<Instant>) -> Option<Instant> {
|
|
||||||
a.map_or(b, |a_timeout| {
|
|
||||||
b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A hack to make activation of multiple windows work when creating them before
|
|
||||||
/// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`.
|
|
||||||
///
|
|
||||||
/// Alternative to this would be the user calling `window.set_visible(true)` in
|
|
||||||
/// `StartCause::Init`.
|
|
||||||
///
|
|
||||||
/// If this becomes too bothersome to maintain, it can probably be removed
|
|
||||||
/// without too much damage.
|
|
||||||
fn window_activation_hack(app: &NSApplication) {
|
|
||||||
// TODO: Proper ordering of the windows
|
|
||||||
app.windows().into_iter().for_each(|window| {
|
|
||||||
// Call `makeKeyAndOrderFront` if it was called on the window in `WinitWindow::new`
|
|
||||||
// This way we preserve the user's desired initial visiblity status
|
|
||||||
// TODO: Also filter on the type/"level" of the window, and maybe other things?
|
|
||||||
if window.isVisible() {
|
|
||||||
trace!("Activating visible window");
|
|
||||||
window.makeKeyAndOrderFront(None);
|
|
||||||
} else {
|
|
||||||
trace!("Skipping activating invisible window");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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,143 +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(setServicesMenu:)]
|
|
||||||
pub fn setServicesMenu(&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,56 +0,0 @@
|
|||||||
use std::ffi::c_uchar;
|
|
||||||
|
|
||||||
use icrate::Foundation::{NSInteger, NSObject, NSString};
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::runtime::Bool;
|
|
||||||
use objc2::{extern_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub struct NSImageRep;
|
|
||||||
|
|
||||||
unsafe impl ClassType for NSImageRep {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
static NSDeviceRGBColorSpace: &'static NSString;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern_class!(
|
|
||||||
// <https://developer.apple.com/documentation/appkit/nsbitmapimagerep?language=objc>
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct NSBitmapImageRep;
|
|
||||||
|
|
||||||
unsafe impl ClassType for NSBitmapImageRep {
|
|
||||||
type Super = NSImageRep;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
extern_methods!(
|
|
||||||
unsafe impl NSBitmapImageRep {
|
|
||||||
pub fn init_rgba(width: NSInteger, height: NSInteger) -> Id<Self> {
|
|
||||||
unsafe {
|
|
||||||
msg_send_id![Self::alloc(),
|
|
||||||
initWithBitmapDataPlanes: std::ptr::null_mut::<*mut c_uchar>(),
|
|
||||||
pixelsWide: width,
|
|
||||||
pixelsHigh: height,
|
|
||||||
bitsPerSample: 8 as NSInteger,
|
|
||||||
samplesPerPixel: 4 as NSInteger,
|
|
||||||
hasAlpha: Bool::new(true),
|
|
||||||
isPlanar: Bool::new(false),
|
|
||||||
colorSpaceName: NSDeviceRGBColorSpace,
|
|
||||||
bytesPerRow: width * 4,
|
|
||||||
bitsPerPixel: 32 as NSInteger,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bitmap_data(&self) -> *mut u8 {
|
|
||||||
unsafe { msg_send![self, bitmapData] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -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,259 +0,0 @@
|
|||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use icrate::ns_string;
|
|
||||||
use icrate::Foundation::{
|
|
||||||
NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, NSString,
|
|
||||||
};
|
|
||||||
use objc2::rc::{DefaultId, Id};
|
|
||||||
use objc2::runtime::Sel;
|
|
||||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, sel, ClassType};
|
|
||||||
|
|
||||||
use super::{NSBitmapImageRep, NSImage};
|
|
||||||
use crate::cursor::CursorImage;
|
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_image(cursor: &CursorImage) -> Id<Self> {
|
|
||||||
let width = cursor.width;
|
|
||||||
let height = cursor.height;
|
|
||||||
|
|
||||||
let bitmap = NSBitmapImageRep::init_rgba(width as isize, height as isize);
|
|
||||||
let bitmap_data =
|
|
||||||
unsafe { std::slice::from_raw_parts_mut(bitmap.bitmap_data(), cursor.rgba.len()) };
|
|
||||||
bitmap_data.copy_from_slice(&cursor.rgba);
|
|
||||||
|
|
||||||
let image = NSImage::init_with_size(NSSize::new(width.into(), height.into()));
|
|
||||||
image.add_representation(&bitmap);
|
|
||||||
|
|
||||||
let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64);
|
|
||||||
|
|
||||||
NSCursor::new(&image, hotspot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,46 +0,0 @@
|
|||||||
use icrate::Foundation::{NSData, NSObject, NSSize, NSString};
|
|
||||||
use objc2::rc::Id;
|
|
||||||
use objc2::{extern_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
|
|
||||||
|
|
||||||
use super::NSBitmapImageRep;
|
|
||||||
|
|
||||||
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] }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_with_size(size: NSSize) -> Id<Self> {
|
|
||||||
unsafe { msg_send_id![Self::alloc(), initWithSize: size] }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_representation(&self, representation: &NSBitmapImageRep) {
|
|
||||||
unsafe { msg_send![self, addRepresentation: representation] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -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,47 +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: Option<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,68 +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 bitmap_image_rep;
|
|
||||||
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::bitmap_image_rep::NSBitmapImageRep;
|
|
||||||
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,440 +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(windowNumber)]
|
|
||||||
pub(crate) fn windowNumber(&self) -> NSInteger;
|
|
||||||
|
|
||||||
#[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;
|
|
||||||
}
|
|
||||||
@@ -1,621 +0,0 @@
|
|||||||
use std::ffi::c_void;
|
|
||||||
|
|
||||||
use core_foundation::{
|
|
||||||
base::CFRelease,
|
|
||||||
data::{CFDataGetBytePtr, CFDataRef},
|
|
||||||
};
|
|
||||||
use icrate::Foundation::MainThreadMarker;
|
|
||||||
use smol_str::SmolStr;
|
|
||||||
|
|
||||||
use super::appkit::{NSEvent, NSEventModifierFlags};
|
|
||||||
use crate::{
|
|
||||||
event::{ElementState, KeyEvent, Modifiers},
|
|
||||||
keyboard::{
|
|
||||||
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey,
|
|
||||||
NativeKeyCode, PhysicalKey,
|
|
||||||
},
|
|
||||||
platform::{
|
|
||||||
modifier_supplement::KeyEventExtModifierSupplement, scancode::PhysicalKeyExtScancode,
|
|
||||||
},
|
|
||||||
platform_impl::platform::ffi,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct KeyEventExtra {
|
|
||||||
pub text_with_all_modifiers: Option<SmolStr>,
|
|
||||||
pub key_without_modifiers: Key,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyEventExtModifierSupplement for KeyEvent {
|
|
||||||
fn text_with_all_modifiers(&self) -> Option<&str> {
|
|
||||||
self.platform_specific
|
|
||||||
.text_with_all_modifiers
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_without_modifiers(&self) -> Key {
|
|
||||||
self.platform_specific.key_without_modifiers.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_modifierless_char(scancode: u16) -> Key {
|
|
||||||
let mut string = [0; 16];
|
|
||||||
let input_source;
|
|
||||||
let layout;
|
|
||||||
unsafe {
|
|
||||||
input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource();
|
|
||||||
if input_source.is_null() {
|
|
||||||
log::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
|
|
||||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
|
||||||
}
|
|
||||||
let layout_data =
|
|
||||||
ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData);
|
|
||||||
if layout_data.is_null() {
|
|
||||||
CFRelease(input_source as *mut c_void);
|
|
||||||
log::error!("`TISGetInputSourceProperty` returned null ptr");
|
|
||||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
|
||||||
}
|
|
||||||
layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout;
|
|
||||||
}
|
|
||||||
let keyboard_type = MainThreadMarker::run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() });
|
|
||||||
|
|
||||||
let mut result_len = 0;
|
|
||||||
let mut dead_keys = 0;
|
|
||||||
let modifiers = 0;
|
|
||||||
let translate_result = unsafe {
|
|
||||||
ffi::UCKeyTranslate(
|
|
||||||
layout,
|
|
||||||
scancode,
|
|
||||||
ffi::kUCKeyActionDisplay,
|
|
||||||
modifiers,
|
|
||||||
keyboard_type as u32,
|
|
||||||
ffi::kUCKeyTranslateNoDeadKeysMask,
|
|
||||||
&mut dead_keys,
|
|
||||||
string.len() as ffi::UniCharCount,
|
|
||||||
&mut result_len,
|
|
||||||
string.as_mut_ptr(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
CFRelease(input_source as *mut c_void);
|
|
||||||
}
|
|
||||||
if translate_result != 0 {
|
|
||||||
log::error!(
|
|
||||||
"`UCKeyTranslate` returned with the non-zero value: {}",
|
|
||||||
translate_result
|
|
||||||
);
|
|
||||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
|
||||||
}
|
|
||||||
if result_len == 0 {
|
|
||||||
// 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]);
|
|
||||||
Key::Character(SmolStr::new(chars))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
|
|
||||||
let string = ns_event
|
|
||||||
.charactersIgnoringModifiers()
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
if string.is_empty() {
|
|
||||||
// Probably a dead key
|
|
||||||
let first_char = modifierless_chars.chars().next();
|
|
||||||
return Key::Dead(first_char);
|
|
||||||
}
|
|
||||||
Key::Character(SmolStr::new(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create `KeyEvent` for the given `NSEvent`.
|
|
||||||
///
|
|
||||||
/// This function shouldn't be called when the IME input is in process.
|
|
||||||
pub(crate) fn create_key_event(
|
|
||||||
ns_event: &NSEvent,
|
|
||||||
is_press: bool,
|
|
||||||
is_repeat: bool,
|
|
||||||
key_override: Option<PhysicalKey>,
|
|
||||||
) -> KeyEvent {
|
|
||||||
use ElementState::{Pressed, Released};
|
|
||||||
let state = if is_press { Pressed } else { Released };
|
|
||||||
|
|
||||||
let scancode = ns_event.key_code();
|
|
||||||
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()
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
if characters.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
|
|
||||||
// The key may be one of the funky function keys
|
|
||||||
physical_key = extra_function_key_to_code(scancode, &characters);
|
|
||||||
}
|
|
||||||
Some(SmolStr::new(characters))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let key_from_code = code_to_key(physical_key, scancode);
|
|
||||||
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 logical_key = match text_with_all_modifiers.as_ref() {
|
|
||||||
// Only checking for ctrl here, not checking for alt because we DO want to
|
|
||||||
// include its effect in the key. For example if -on the Germay layout- one
|
|
||||||
// presses alt+8, the logical key should be "{"
|
|
||||||
// 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()),
|
|
||||||
_ => 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)
|
|
||||||
} else {
|
|
||||||
(key_from_code.clone(), key_from_code)
|
|
||||||
};
|
|
||||||
|
|
||||||
let text = if is_press {
|
|
||||||
logical_key.to_text().map(SmolStr::new)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let location = code_to_location(physical_key);
|
|
||||||
|
|
||||||
KeyEvent {
|
|
||||||
location,
|
|
||||||
logical_key,
|
|
||||||
physical_key,
|
|
||||||
repeat: is_repeat,
|
|
||||||
state,
|
|
||||||
text,
|
|
||||||
platform_specific: KeyEventExtra {
|
|
||||||
key_without_modifiers,
|
|
||||||
text_with_all_modifiers,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
|
|
||||||
let code = match key {
|
|
||||||
PhysicalKey::Code(code) => code,
|
|
||||||
PhysicalKey::Unidentified(code) => return Key::Unidentified(code.into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
Key::Named(match code {
|
|
||||||
KeyCode::Enter => NamedKey::Enter,
|
|
||||||
KeyCode::Tab => NamedKey::Tab,
|
|
||||||
KeyCode::Space => NamedKey::Space,
|
|
||||||
KeyCode::Backspace => NamedKey::Backspace,
|
|
||||||
KeyCode::Escape => NamedKey::Escape,
|
|
||||||
KeyCode::SuperRight => NamedKey::Super,
|
|
||||||
KeyCode::SuperLeft => NamedKey::Super,
|
|
||||||
KeyCode::ShiftLeft => NamedKey::Shift,
|
|
||||||
KeyCode::AltLeft => NamedKey::Alt,
|
|
||||||
KeyCode::ControlLeft => NamedKey::Control,
|
|
||||||
KeyCode::ShiftRight => NamedKey::Shift,
|
|
||||||
KeyCode::AltRight => NamedKey::Alt,
|
|
||||||
KeyCode::ControlRight => NamedKey::Control,
|
|
||||||
|
|
||||||
KeyCode::NumLock => NamedKey::NumLock,
|
|
||||||
KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp,
|
|
||||||
KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown,
|
|
||||||
|
|
||||||
// Other numpad keys all generate text on macOS (if I understand correctly)
|
|
||||||
KeyCode::NumpadEnter => NamedKey::Enter,
|
|
||||||
|
|
||||||
KeyCode::F1 => NamedKey::F1,
|
|
||||||
KeyCode::F2 => NamedKey::F2,
|
|
||||||
KeyCode::F3 => NamedKey::F3,
|
|
||||||
KeyCode::F4 => NamedKey::F4,
|
|
||||||
KeyCode::F5 => NamedKey::F5,
|
|
||||||
KeyCode::F6 => NamedKey::F6,
|
|
||||||
KeyCode::F7 => NamedKey::F7,
|
|
||||||
KeyCode::F8 => NamedKey::F8,
|
|
||||||
KeyCode::F9 => NamedKey::F9,
|
|
||||||
KeyCode::F10 => NamedKey::F10,
|
|
||||||
KeyCode::F11 => NamedKey::F11,
|
|
||||||
KeyCode::F12 => NamedKey::F12,
|
|
||||||
KeyCode::F13 => NamedKey::F13,
|
|
||||||
KeyCode::F14 => NamedKey::F14,
|
|
||||||
KeyCode::F15 => NamedKey::F15,
|
|
||||||
KeyCode::F16 => NamedKey::F16,
|
|
||||||
KeyCode::F17 => NamedKey::F17,
|
|
||||||
KeyCode::F18 => NamedKey::F18,
|
|
||||||
KeyCode::F19 => NamedKey::F19,
|
|
||||||
KeyCode::F20 => NamedKey::F20,
|
|
||||||
|
|
||||||
KeyCode::Insert => NamedKey::Insert,
|
|
||||||
KeyCode::Home => NamedKey::Home,
|
|
||||||
KeyCode::PageUp => NamedKey::PageUp,
|
|
||||||
KeyCode::Delete => NamedKey::Delete,
|
|
||||||
KeyCode::End => NamedKey::End,
|
|
||||||
KeyCode::PageDown => NamedKey::PageDown,
|
|
||||||
KeyCode::ArrowLeft => NamedKey::ArrowLeft,
|
|
||||||
KeyCode::ArrowRight => NamedKey::ArrowRight,
|
|
||||||
KeyCode::ArrowDown => NamedKey::ArrowDown,
|
|
||||||
KeyCode::ArrowUp => NamedKey::ArrowUp,
|
|
||||||
_ => return Key::Unidentified(NativeKey::MacOS(scancode)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn code_to_location(key: PhysicalKey) -> KeyLocation {
|
|
||||||
let code = match key {
|
|
||||||
PhysicalKey::Code(code) => code,
|
|
||||||
PhysicalKey::Unidentified(_) => return KeyLocation::Standard,
|
|
||||||
};
|
|
||||||
|
|
||||||
match code {
|
|
||||||
KeyCode::SuperRight => KeyLocation::Right,
|
|
||||||
KeyCode::SuperLeft => KeyLocation::Left,
|
|
||||||
KeyCode::ShiftLeft => KeyLocation::Left,
|
|
||||||
KeyCode::AltLeft => KeyLocation::Left,
|
|
||||||
KeyCode::ControlLeft => KeyLocation::Left,
|
|
||||||
KeyCode::ShiftRight => KeyLocation::Right,
|
|
||||||
KeyCode::AltRight => KeyLocation::Right,
|
|
||||||
KeyCode::ControlRight => KeyLocation::Right,
|
|
||||||
|
|
||||||
KeyCode::NumLock => KeyLocation::Numpad,
|
|
||||||
KeyCode::NumpadDecimal => KeyLocation::Numpad,
|
|
||||||
KeyCode::NumpadMultiply => KeyLocation::Numpad,
|
|
||||||
KeyCode::NumpadAdd => KeyLocation::Numpad,
|
|
||||||
KeyCode::NumpadDivide => KeyLocation::Numpad,
|
|
||||||
KeyCode::NumpadEnter => KeyLocation::Numpad,
|
|
||||||
KeyCode::NumpadSubtract => KeyLocation::Numpad,
|
|
||||||
KeyCode::NumpadEqual => KeyLocation::Numpad,
|
|
||||||
KeyCode::Numpad0 => KeyLocation::Numpad,
|
|
||||||
KeyCode::Numpad1 => KeyLocation::Numpad,
|
|
||||||
KeyCode::Numpad2 => KeyLocation::Numpad,
|
|
||||||
KeyCode::Numpad3 => KeyLocation::Numpad,
|
|
||||||
KeyCode::Numpad4 => KeyLocation::Numpad,
|
|
||||||
KeyCode::Numpad5 => KeyLocation::Numpad,
|
|
||||||
KeyCode::Numpad6 => KeyLocation::Numpad,
|
|
||||||
KeyCode::Numpad7 => KeyLocation::Numpad,
|
|
||||||
KeyCode::Numpad8 => KeyLocation::Numpad,
|
|
||||||
KeyCode::Numpad9 => KeyLocation::Numpad,
|
|
||||||
|
|
||||||
_ => KeyLocation::Standard,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// While F1-F20 have scancodes we can match on, we have to check against UTF-16
|
|
||||||
// constants for the rest.
|
|
||||||
// https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ
|
|
||||||
pub fn extra_function_key_to_code(scancode: u16, string: &str) -> PhysicalKey {
|
|
||||||
if let Some(ch) = string.encode_utf16().next() {
|
|
||||||
match ch {
|
|
||||||
0xf718 => PhysicalKey::Code(KeyCode::F21),
|
|
||||||
0xf719 => PhysicalKey::Code(KeyCode::F22),
|
|
||||||
0xf71a => PhysicalKey::Code(KeyCode::F23),
|
|
||||||
0xf71b => PhysicalKey::Code(KeyCode::F24),
|
|
||||||
_ => PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
|
|
||||||
let flags = event.modifierFlags();
|
|
||||||
let mut state = ModifiersState::empty();
|
|
||||||
let mut pressed_mods = ModifiersKeys::empty();
|
|
||||||
|
|
||||||
state.set(
|
|
||||||
ModifiersState::SHIFT,
|
|
||||||
flags.contains(NSEventModifierFlags::NSShiftKeyMask),
|
|
||||||
);
|
|
||||||
|
|
||||||
pressed_mods.set(ModifiersKeys::LSHIFT, event.lshift_pressed());
|
|
||||||
pressed_mods.set(ModifiersKeys::RSHIFT, event.rshift_pressed());
|
|
||||||
|
|
||||||
state.set(
|
|
||||||
ModifiersState::CONTROL,
|
|
||||||
flags.contains(NSEventModifierFlags::NSControlKeyMask),
|
|
||||||
);
|
|
||||||
|
|
||||||
pressed_mods.set(ModifiersKeys::LCONTROL, event.lctrl_pressed());
|
|
||||||
pressed_mods.set(ModifiersKeys::RCONTROL, event.rctrl_pressed());
|
|
||||||
|
|
||||||
state.set(
|
|
||||||
ModifiersState::ALT,
|
|
||||||
flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
|
|
||||||
);
|
|
||||||
|
|
||||||
pressed_mods.set(ModifiersKeys::LALT, event.lalt_pressed());
|
|
||||||
pressed_mods.set(ModifiersKeys::RALT, event.ralt_pressed());
|
|
||||||
|
|
||||||
state.set(
|
|
||||||
ModifiersState::SUPER,
|
|
||||||
flags.contains(NSEventModifierFlags::NSCommandKeyMask),
|
|
||||||
);
|
|
||||||
|
|
||||||
pressed_mods.set(ModifiersKeys::LSUPER, event.lcmd_pressed());
|
|
||||||
pressed_mods.set(ModifiersKeys::RSUPER, event.rcmd_pressed());
|
|
||||||
|
|
||||||
Modifiers {
|
|
||||||
state,
|
|
||||||
pressed_mods,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhysicalKeyExtScancode for PhysicalKey {
|
|
||||||
fn to_scancode(self) -> Option<u32> {
|
|
||||||
let code = match self {
|
|
||||||
PhysicalKey::Code(code) => code,
|
|
||||||
PhysicalKey::Unidentified(_) => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
match code {
|
|
||||||
KeyCode::KeyA => Some(0x00),
|
|
||||||
KeyCode::KeyS => Some(0x01),
|
|
||||||
KeyCode::KeyD => Some(0x02),
|
|
||||||
KeyCode::KeyF => Some(0x03),
|
|
||||||
KeyCode::KeyH => Some(0x04),
|
|
||||||
KeyCode::KeyG => Some(0x05),
|
|
||||||
KeyCode::KeyZ => Some(0x06),
|
|
||||||
KeyCode::KeyX => Some(0x07),
|
|
||||||
KeyCode::KeyC => Some(0x08),
|
|
||||||
KeyCode::KeyV => Some(0x09),
|
|
||||||
KeyCode::KeyB => Some(0x0b),
|
|
||||||
KeyCode::KeyQ => Some(0x0c),
|
|
||||||
KeyCode::KeyW => Some(0x0d),
|
|
||||||
KeyCode::KeyE => Some(0x0e),
|
|
||||||
KeyCode::KeyR => Some(0x0f),
|
|
||||||
KeyCode::KeyY => Some(0x10),
|
|
||||||
KeyCode::KeyT => Some(0x11),
|
|
||||||
KeyCode::Digit1 => Some(0x12),
|
|
||||||
KeyCode::Digit2 => Some(0x13),
|
|
||||||
KeyCode::Digit3 => Some(0x14),
|
|
||||||
KeyCode::Digit4 => Some(0x15),
|
|
||||||
KeyCode::Digit6 => Some(0x16),
|
|
||||||
KeyCode::Digit5 => Some(0x17),
|
|
||||||
KeyCode::Equal => Some(0x18),
|
|
||||||
KeyCode::Digit9 => Some(0x19),
|
|
||||||
KeyCode::Digit7 => Some(0x1a),
|
|
||||||
KeyCode::Minus => Some(0x1b),
|
|
||||||
KeyCode::Digit8 => Some(0x1c),
|
|
||||||
KeyCode::Digit0 => Some(0x1d),
|
|
||||||
KeyCode::BracketRight => Some(0x1e),
|
|
||||||
KeyCode::KeyO => Some(0x1f),
|
|
||||||
KeyCode::KeyU => Some(0x20),
|
|
||||||
KeyCode::BracketLeft => Some(0x21),
|
|
||||||
KeyCode::KeyI => Some(0x22),
|
|
||||||
KeyCode::KeyP => Some(0x23),
|
|
||||||
KeyCode::Enter => Some(0x24),
|
|
||||||
KeyCode::KeyL => Some(0x25),
|
|
||||||
KeyCode::KeyJ => Some(0x26),
|
|
||||||
KeyCode::Quote => Some(0x27),
|
|
||||||
KeyCode::KeyK => Some(0x28),
|
|
||||||
KeyCode::Semicolon => Some(0x29),
|
|
||||||
KeyCode::Backslash => Some(0x2a),
|
|
||||||
KeyCode::Comma => Some(0x2b),
|
|
||||||
KeyCode::Slash => Some(0x2c),
|
|
||||||
KeyCode::KeyN => Some(0x2d),
|
|
||||||
KeyCode::KeyM => Some(0x2e),
|
|
||||||
KeyCode::Period => Some(0x2f),
|
|
||||||
KeyCode::Tab => Some(0x30),
|
|
||||||
KeyCode::Space => Some(0x31),
|
|
||||||
KeyCode::Backquote => Some(0x32),
|
|
||||||
KeyCode::Backspace => Some(0x33),
|
|
||||||
KeyCode::Escape => Some(0x35),
|
|
||||||
KeyCode::SuperRight => Some(0x36),
|
|
||||||
KeyCode::SuperLeft => Some(0x37),
|
|
||||||
KeyCode::ShiftLeft => Some(0x38),
|
|
||||||
KeyCode::AltLeft => Some(0x3a),
|
|
||||||
KeyCode::ControlLeft => Some(0x3b),
|
|
||||||
KeyCode::ShiftRight => Some(0x3c),
|
|
||||||
KeyCode::AltRight => Some(0x3d),
|
|
||||||
KeyCode::ControlRight => Some(0x3e),
|
|
||||||
KeyCode::F17 => Some(0x40),
|
|
||||||
KeyCode::NumpadDecimal => Some(0x41),
|
|
||||||
KeyCode::NumpadMultiply => Some(0x43),
|
|
||||||
KeyCode::NumpadAdd => Some(0x45),
|
|
||||||
KeyCode::NumLock => Some(0x47),
|
|
||||||
KeyCode::AudioVolumeUp => Some(0x49),
|
|
||||||
KeyCode::AudioVolumeDown => Some(0x4a),
|
|
||||||
KeyCode::NumpadDivide => Some(0x4b),
|
|
||||||
KeyCode::NumpadEnter => Some(0x4c),
|
|
||||||
KeyCode::NumpadSubtract => Some(0x4e),
|
|
||||||
KeyCode::F18 => Some(0x4f),
|
|
||||||
KeyCode::F19 => Some(0x50),
|
|
||||||
KeyCode::NumpadEqual => Some(0x51),
|
|
||||||
KeyCode::Numpad0 => Some(0x52),
|
|
||||||
KeyCode::Numpad1 => Some(0x53),
|
|
||||||
KeyCode::Numpad2 => Some(0x54),
|
|
||||||
KeyCode::Numpad3 => Some(0x55),
|
|
||||||
KeyCode::Numpad4 => Some(0x56),
|
|
||||||
KeyCode::Numpad5 => Some(0x57),
|
|
||||||
KeyCode::Numpad6 => Some(0x58),
|
|
||||||
KeyCode::Numpad7 => Some(0x59),
|
|
||||||
KeyCode::F20 => Some(0x5a),
|
|
||||||
KeyCode::Numpad8 => Some(0x5b),
|
|
||||||
KeyCode::Numpad9 => Some(0x5c),
|
|
||||||
KeyCode::IntlYen => Some(0x5d),
|
|
||||||
KeyCode::F5 => Some(0x60),
|
|
||||||
KeyCode::F6 => Some(0x61),
|
|
||||||
KeyCode::F7 => Some(0x62),
|
|
||||||
KeyCode::F3 => Some(0x63),
|
|
||||||
KeyCode::F8 => Some(0x64),
|
|
||||||
KeyCode::F9 => Some(0x65),
|
|
||||||
KeyCode::F11 => Some(0x67),
|
|
||||||
KeyCode::F13 => Some(0x69),
|
|
||||||
KeyCode::F16 => Some(0x6a),
|
|
||||||
KeyCode::F14 => Some(0x6b),
|
|
||||||
KeyCode::F10 => Some(0x6d),
|
|
||||||
KeyCode::F12 => Some(0x6f),
|
|
||||||
KeyCode::F15 => Some(0x71),
|
|
||||||
KeyCode::Insert => Some(0x72),
|
|
||||||
KeyCode::Home => Some(0x73),
|
|
||||||
KeyCode::PageUp => Some(0x74),
|
|
||||||
KeyCode::Delete => Some(0x75),
|
|
||||||
KeyCode::F4 => Some(0x76),
|
|
||||||
KeyCode::End => Some(0x77),
|
|
||||||
KeyCode::F2 => Some(0x78),
|
|
||||||
KeyCode::PageDown => Some(0x79),
|
|
||||||
KeyCode::F1 => Some(0x7a),
|
|
||||||
KeyCode::ArrowLeft => Some(0x7b),
|
|
||||||
KeyCode::ArrowRight => Some(0x7c),
|
|
||||||
KeyCode::ArrowDown => Some(0x7d),
|
|
||||||
KeyCode::ArrowUp => Some(0x7e),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_scancode(scancode: u32) -> PhysicalKey {
|
|
||||||
PhysicalKey::Code(match scancode {
|
|
||||||
0x00 => KeyCode::KeyA,
|
|
||||||
0x01 => KeyCode::KeyS,
|
|
||||||
0x02 => KeyCode::KeyD,
|
|
||||||
0x03 => KeyCode::KeyF,
|
|
||||||
0x04 => KeyCode::KeyH,
|
|
||||||
0x05 => KeyCode::KeyG,
|
|
||||||
0x06 => KeyCode::KeyZ,
|
|
||||||
0x07 => KeyCode::KeyX,
|
|
||||||
0x08 => KeyCode::KeyC,
|
|
||||||
0x09 => KeyCode::KeyV,
|
|
||||||
//0x0a => World 1,
|
|
||||||
0x0b => KeyCode::KeyB,
|
|
||||||
0x0c => KeyCode::KeyQ,
|
|
||||||
0x0d => KeyCode::KeyW,
|
|
||||||
0x0e => KeyCode::KeyE,
|
|
||||||
0x0f => KeyCode::KeyR,
|
|
||||||
0x10 => KeyCode::KeyY,
|
|
||||||
0x11 => KeyCode::KeyT,
|
|
||||||
0x12 => KeyCode::Digit1,
|
|
||||||
0x13 => KeyCode::Digit2,
|
|
||||||
0x14 => KeyCode::Digit3,
|
|
||||||
0x15 => KeyCode::Digit4,
|
|
||||||
0x16 => KeyCode::Digit6,
|
|
||||||
0x17 => KeyCode::Digit5,
|
|
||||||
0x18 => KeyCode::Equal,
|
|
||||||
0x19 => KeyCode::Digit9,
|
|
||||||
0x1a => KeyCode::Digit7,
|
|
||||||
0x1b => KeyCode::Minus,
|
|
||||||
0x1c => KeyCode::Digit8,
|
|
||||||
0x1d => KeyCode::Digit0,
|
|
||||||
0x1e => KeyCode::BracketRight,
|
|
||||||
0x1f => KeyCode::KeyO,
|
|
||||||
0x20 => KeyCode::KeyU,
|
|
||||||
0x21 => KeyCode::BracketLeft,
|
|
||||||
0x22 => KeyCode::KeyI,
|
|
||||||
0x23 => KeyCode::KeyP,
|
|
||||||
0x24 => KeyCode::Enter,
|
|
||||||
0x25 => KeyCode::KeyL,
|
|
||||||
0x26 => KeyCode::KeyJ,
|
|
||||||
0x27 => KeyCode::Quote,
|
|
||||||
0x28 => KeyCode::KeyK,
|
|
||||||
0x29 => KeyCode::Semicolon,
|
|
||||||
0x2a => KeyCode::Backslash,
|
|
||||||
0x2b => KeyCode::Comma,
|
|
||||||
0x2c => KeyCode::Slash,
|
|
||||||
0x2d => KeyCode::KeyN,
|
|
||||||
0x2e => KeyCode::KeyM,
|
|
||||||
0x2f => KeyCode::Period,
|
|
||||||
0x30 => KeyCode::Tab,
|
|
||||||
0x31 => KeyCode::Space,
|
|
||||||
0x32 => KeyCode::Backquote,
|
|
||||||
0x33 => KeyCode::Backspace,
|
|
||||||
//0x34 => unknown,
|
|
||||||
0x35 => KeyCode::Escape,
|
|
||||||
0x36 => KeyCode::SuperRight,
|
|
||||||
0x37 => KeyCode::SuperLeft,
|
|
||||||
0x38 => KeyCode::ShiftLeft,
|
|
||||||
0x39 => KeyCode::CapsLock,
|
|
||||||
0x3a => KeyCode::AltLeft,
|
|
||||||
0x3b => KeyCode::ControlLeft,
|
|
||||||
0x3c => KeyCode::ShiftRight,
|
|
||||||
0x3d => KeyCode::AltRight,
|
|
||||||
0x3e => KeyCode::ControlRight,
|
|
||||||
0x3f => KeyCode::Fn,
|
|
||||||
0x40 => KeyCode::F17,
|
|
||||||
0x41 => KeyCode::NumpadDecimal,
|
|
||||||
//0x42 -> unknown,
|
|
||||||
0x43 => KeyCode::NumpadMultiply,
|
|
||||||
//0x44 => unknown,
|
|
||||||
0x45 => KeyCode::NumpadAdd,
|
|
||||||
//0x46 => unknown,
|
|
||||||
0x47 => KeyCode::NumLock,
|
|
||||||
//0x48 => KeyCode::NumpadClear,
|
|
||||||
|
|
||||||
// TODO: (Artur) for me, kVK_VolumeUp is 0x48
|
|
||||||
// macOS 10.11
|
|
||||||
// /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
|
|
||||||
0x49 => KeyCode::AudioVolumeUp,
|
|
||||||
0x4a => KeyCode::AudioVolumeDown,
|
|
||||||
0x4b => KeyCode::NumpadDivide,
|
|
||||||
0x4c => KeyCode::NumpadEnter,
|
|
||||||
//0x4d => unknown,
|
|
||||||
0x4e => KeyCode::NumpadSubtract,
|
|
||||||
0x4f => KeyCode::F18,
|
|
||||||
0x50 => KeyCode::F19,
|
|
||||||
0x51 => KeyCode::NumpadEqual,
|
|
||||||
0x52 => KeyCode::Numpad0,
|
|
||||||
0x53 => KeyCode::Numpad1,
|
|
||||||
0x54 => KeyCode::Numpad2,
|
|
||||||
0x55 => KeyCode::Numpad3,
|
|
||||||
0x56 => KeyCode::Numpad4,
|
|
||||||
0x57 => KeyCode::Numpad5,
|
|
||||||
0x58 => KeyCode::Numpad6,
|
|
||||||
0x59 => KeyCode::Numpad7,
|
|
||||||
0x5a => KeyCode::F20,
|
|
||||||
0x5b => KeyCode::Numpad8,
|
|
||||||
0x5c => KeyCode::Numpad9,
|
|
||||||
0x5d => KeyCode::IntlYen,
|
|
||||||
//0x5e => JIS Ro,
|
|
||||||
//0x5f => unknown,
|
|
||||||
0x60 => KeyCode::F5,
|
|
||||||
0x61 => KeyCode::F6,
|
|
||||||
0x62 => KeyCode::F7,
|
|
||||||
0x63 => KeyCode::F3,
|
|
||||||
0x64 => KeyCode::F8,
|
|
||||||
0x65 => KeyCode::F9,
|
|
||||||
//0x66 => JIS Eisuu (macOS),
|
|
||||||
0x67 => KeyCode::F11,
|
|
||||||
//0x68 => JIS Kanna (macOS),
|
|
||||||
0x69 => KeyCode::F13,
|
|
||||||
0x6a => KeyCode::F16,
|
|
||||||
0x6b => KeyCode::F14,
|
|
||||||
//0x6c => unknown,
|
|
||||||
0x6d => KeyCode::F10,
|
|
||||||
//0x6e => unknown,
|
|
||||||
0x6f => KeyCode::F12,
|
|
||||||
//0x70 => unknown,
|
|
||||||
0x71 => KeyCode::F15,
|
|
||||||
0x72 => KeyCode::Insert,
|
|
||||||
0x73 => KeyCode::Home,
|
|
||||||
0x74 => KeyCode::PageUp,
|
|
||||||
0x75 => KeyCode::Delete,
|
|
||||||
0x76 => KeyCode::F4,
|
|
||||||
0x77 => KeyCode::End,
|
|
||||||
0x78 => KeyCode::F2,
|
|
||||||
0x79 => KeyCode::PageDown,
|
|
||||||
0x7a => KeyCode::F1,
|
|
||||||
0x7b => KeyCode::ArrowLeft,
|
|
||||||
0x7c => KeyCode::ArrowRight,
|
|
||||||
0x7d => KeyCode::ArrowDown,
|
|
||||||
0x7e => KeyCode::ArrowUp,
|
|
||||||
//0x7f => unknown,
|
|
||||||
|
|
||||||
// 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as
|
|
||||||
// backquote (`) on Windows' US layout.
|
|
||||||
0xa => KeyCode::Backquote,
|
|
||||||
_ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
use core_graphics::display::CGDisplay;
|
|
||||||
use icrate::Foundation::{CGFloat, NSNotFound, NSPoint, NSRange, NSRect, NSUInteger};
|
|
||||||
|
|
||||||
use crate::dpi::LogicalPosition;
|
|
||||||
|
|
||||||
// Replace with `!` once stable
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Never {}
|
|
||||||
|
|
||||||
pub const EMPTY_RANGE: NSRange = NSRange {
|
|
||||||
location: NSNotFound as NSUInteger,
|
|
||||||
length: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
macro_rules! trace_scope {
|
|
||||||
($s:literal) => {
|
|
||||||
let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct TraceGuard {
|
|
||||||
module_path: &'static str,
|
|
||||||
called_from_fn: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TraceGuard {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn new(module_path: &'static str, called_from_fn: &'static str) -> Self {
|
|
||||||
trace!(target: module_path, "Triggered `{}`", called_from_fn);
|
|
||||||
Self {
|
|
||||||
module_path,
|
|
||||||
called_from_fn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for TraceGuard {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
trace!(target: self.module_path, "Completed `{}`", self.called_from_fn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For consistency with other platforms, this will...
|
|
||||||
// 1. translate the bottom-left window corner into the top-left window corner
|
|
||||||
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
|
|
||||||
#[allow(clippy::unnecessary_cast)]
|
|
||||||
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
|
|
||||||
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) as f64
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts from winit screen-coordinates to macOS screen-coordinates.
|
|
||||||
/// Winit: top-left is (0, 0) and y increasing downwards
|
|
||||||
/// macOS: bottom-left is (0, 0) and y increasing upwards
|
|
||||||
pub fn window_position(position: LogicalPosition<f64>) -> NSPoint {
|
|
||||||
NSPoint::new(
|
|
||||||
position.x as CGFloat,
|
|
||||||
CGDisplay::main().pixels_high() as CGFloat - position.y as CGFloat,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,483 +0,0 @@
|
|||||||
#![allow(clippy::unnecessary_cast)]
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::ptr::{self, NonNull};
|
|
||||||
|
|
||||||
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 super::{
|
|
||||||
app_state::AppState,
|
|
||||||
util,
|
|
||||||
window::{get_ns_theme, WinitWindow},
|
|
||||||
Fullscreen,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
window::WindowId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct State {
|
|
||||||
// This is set when WindowBuilder::with_fullscreen was set,
|
|
||||||
// see comments of `window_did_fail_to_enter_fullscreen`
|
|
||||||
initial_fullscreen: Cell<bool>,
|
|
||||||
|
|
||||||
// During `windowDidResize`, we use this to only send Moved if the position changed.
|
|
||||||
previous_position: Cell<Option<(f64, f64)>>,
|
|
||||||
|
|
||||||
// Used to prevent redundant events.
|
|
||||||
previous_scale_factor: Cell<f64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
unsafe impl ClassType for WinitWindowDelegate {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NSWindowDelegate + NSDraggingDestination protocols
|
|
||||||
unsafe impl WinitWindowDelegate {
|
|
||||||
#[method(windowShouldClose:)]
|
|
||||||
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
|
|
||||||
trace_scope!("windowShouldClose:");
|
|
||||||
self.queue_event(WindowEvent::CloseRequested);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(windowWillClose:)]
|
|
||||||
fn window_will_close(&self, _: Option<&AnyObject>) {
|
|
||||||
trace_scope!("windowWillClose:");
|
|
||||||
// `setDelegate:` retains the previous value and then autoreleases it
|
|
||||||
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.queue_event(WindowEvent::Destroyed);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(windowDidResize:)]
|
|
||||||
fn window_did_resize(&self, _: Option<&AnyObject>) {
|
|
||||||
trace_scope!("windowDidResize:");
|
|
||||||
// NOTE: WindowEvent::Resized is reported in frameDidChange.
|
|
||||||
self.emit_move_event();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(windowWillStartLiveResize:)]
|
|
||||||
fn window_will_start_live_resize(&self, _: Option<&AnyObject>) {
|
|
||||||
trace_scope!("windowWillStartLiveResize:");
|
|
||||||
|
|
||||||
let increments = self
|
|
||||||
.window
|
|
||||||
.lock_shared_state("window_will_enter_fullscreen")
|
|
||||||
.resize_increments;
|
|
||||||
self.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.));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This won't be triggered if the move was part of a resize.
|
|
||||||
#[method(windowDidMove:)]
|
|
||||||
fn window_did_move(&self, _: Option<&AnyObject>) {
|
|
||||||
trace_scope!("windowDidMove:");
|
|
||||||
self.emit_move_event();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(windowDidChangeBackingProperties:)]
|
|
||||||
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
|
|
||||||
trace_scope!("windowDidChangeBackingProperties:");
|
|
||||||
self.queue_static_scale_factor_changed_event();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(windowDidBecomeKey:)]
|
|
||||||
fn window_did_become_key(&self, _: Option<&AnyObject>) {
|
|
||||||
trace_scope!("windowDidBecomeKey:");
|
|
||||||
// TODO: center the cursor if the window had mouse grab when it
|
|
||||||
// lost focus
|
|
||||||
self.queue_event(WindowEvent::Focused(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(windowDidResignKey:)]
|
|
||||||
fn window_did_resign_key(&self, _: Option<&AnyObject>) {
|
|
||||||
trace_scope!("windowDidResignKey:");
|
|
||||||
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
|
|
||||||
// NSWindowDelegate will receive a didResignKey event despite no event
|
|
||||||
// being received when the modifiers are released. This is because
|
|
||||||
// flagsChanged events are received by the NSView instead of the
|
|
||||||
// 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.queue_event(WindowEvent::Focused(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invoked when the dragged image enters destination bounds or frame
|
|
||||||
#[method(draggingEntered:)]
|
|
||||||
fn dragging_entered(&self, sender: &NSObject) -> bool {
|
|
||||||
trace_scope!("draggingEntered:");
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
|
||||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
|
|
||||||
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
|
|
||||||
|
|
||||||
filenames.into_iter().for_each(|file| {
|
|
||||||
let path = PathBuf::from(file.to_string());
|
|
||||||
self.queue_event(WindowEvent::HoveredFile(path));
|
|
||||||
});
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invoked when the image is released
|
|
||||||
#[method(prepareForDragOperation:)]
|
|
||||||
fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool {
|
|
||||||
trace_scope!("prepareForDragOperation:");
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invoked after the released image has been removed from the screen
|
|
||||||
#[method(performDragOperation:)]
|
|
||||||
fn perform_drag_operation(&self, sender: &NSObject) -> bool {
|
|
||||||
trace_scope!("performDragOperation:");
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
|
||||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
|
|
||||||
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
|
|
||||||
|
|
||||||
filenames.into_iter().for_each(|file| {
|
|
||||||
let path = PathBuf::from(file.to_string());
|
|
||||||
self.queue_event(WindowEvent::DroppedFile(path));
|
|
||||||
});
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invoked when the dragging operation is complete
|
|
||||||
#[method(concludeDragOperation:)]
|
|
||||||
fn conclude_drag_operation(&self, _sender: Option<&NSObject>) {
|
|
||||||
trace_scope!("concludeDragOperation:");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invoked when the dragging operation is cancelled
|
|
||||||
#[method(draggingExited:)]
|
|
||||||
fn dragging_exited(&self, _sender: Option<&NSObject>) {
|
|
||||||
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),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Observe theme change
|
|
||||||
#[method(effectiveAppearanceDidChange:)]
|
|
||||||
fn effective_appearance_did_change(&self, sender: Option<&AnyObject>) {
|
|
||||||
trace_scope!("Triggered `effectiveAppearanceDidChange:`");
|
|
||||||
unsafe {
|
|
||||||
msg_send![
|
|
||||||
self,
|
|
||||||
performSelectorOnMainThread: sel!(effectiveAppearanceDidChangedOnMainThread:),
|
|
||||||
withObject: sender,
|
|
||||||
waitUntilDone: false,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(effectiveAppearanceDidChangedOnMainThread:)]
|
|
||||||
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) {
|
|
||||||
let theme = get_ns_theme();
|
|
||||||
let mut shared_state = self
|
|
||||||
.window
|
|
||||||
.lock_shared_state("effective_appearance_did_change");
|
|
||||||
let current_theme = shared_state.current_theme;
|
|
||||||
shared_state.current_theme = Some(theme);
|
|
||||||
drop(shared_state);
|
|
||||||
if current_theme != Some(theme) {
|
|
||||||
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,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
|
||||||
let event = Event::WindowEvent {
|
|
||||||
window_id: WindowId(self.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() {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.state.previous_scale_factor.set(scale_factor);
|
|
||||||
let suggested_size = self.view_size();
|
|
||||||
AppState::queue_static_scale_factor_changed_event(
|
|
||||||
self.window.clone(),
|
|
||||||
suggested_size.to_physical(scale_factor),
|
|
||||||
scale_factor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_move_event(&self) {
|
|
||||||
let rect = self.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();
|
|
||||||
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;
|
|
||||||
LogicalSize::new(size.width as f64, size.height as f64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
mod channel;
|
|
||||||
mod dispatcher;
|
|
||||||
mod waker;
|
|
||||||
mod wrapper;
|
|
||||||
|
|
||||||
use self::channel::{channel, AsyncReceiver, AsyncSender};
|
|
||||||
pub use self::dispatcher::{DispatchRunner, Dispatcher};
|
|
||||||
pub use self::waker::{Waker, WakerSpawner};
|
|
||||||
use self::wrapper::Wrapper;
|
|
||||||
@@ -1,364 +0,0 @@
|
|||||||
use std::{
|
|
||||||
cell::{Cell, RefCell},
|
|
||||||
ops::Deref,
|
|
||||||
rc::{Rc, Weak},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::cursor::{BadImage, CursorImage};
|
|
||||||
use cursor_icon::CursorIcon;
|
|
||||||
use wasm_bindgen::{closure::Closure, JsCast};
|
|
||||||
use wasm_bindgen_futures::JsFuture;
|
|
||||||
use web_sys::{
|
|
||||||
Blob, Document, HtmlCanvasElement, ImageBitmap, ImageBitmapOptions,
|
|
||||||
ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::backend::Style;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum WebCustomCursor {
|
|
||||||
Image(CursorImage),
|
|
||||||
Url {
|
|
||||||
url: String,
|
|
||||||
hotspot_x: u16,
|
|
||||||
hotspot_y: u16,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WebCustomCursor {
|
|
||||||
pub fn from_rgba(
|
|
||||||
rgba: Vec<u8>,
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
hotspot_x: u16,
|
|
||||||
hotspot_y: u16,
|
|
||||||
) -> Result<Self, BadImage> {
|
|
||||||
Ok(Self::Image(CursorImage::from_rgba(
|
|
||||||
rgba, width, height, hotspot_x, hotspot_y,
|
|
||||||
)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn build(
|
|
||||||
&self,
|
|
||||||
window: &Window,
|
|
||||||
document: &Document,
|
|
||||||
style: &Style,
|
|
||||||
previous: SelectedCursor,
|
|
||||||
cursor_visible: Rc<Cell<bool>>,
|
|
||||||
) -> SelectedCursor {
|
|
||||||
let previous = previous.into();
|
|
||||||
|
|
||||||
match self {
|
|
||||||
WebCustomCursor::Image(image) => SelectedCursor::Image(CursorImageState::from_image(
|
|
||||||
window,
|
|
||||||
document.clone(),
|
|
||||||
style.clone(),
|
|
||||||
image,
|
|
||||||
previous,
|
|
||||||
cursor_visible,
|
|
||||||
)),
|
|
||||||
WebCustomCursor::Url {
|
|
||||||
url,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y,
|
|
||||||
} => {
|
|
||||||
let value = previous.style_with_url(url, *hotspot_x, *hotspot_y);
|
|
||||||
|
|
||||||
if cursor_visible.get() {
|
|
||||||
style.set("cursor", &value);
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectedCursor::Url {
|
|
||||||
style: value,
|
|
||||||
previous,
|
|
||||||
url: url.clone(),
|
|
||||||
hotspot_x: *hotspot_x,
|
|
||||||
hotspot_y: *hotspot_y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum SelectedCursor {
|
|
||||||
Named(CursorIcon),
|
|
||||||
Url {
|
|
||||||
style: String,
|
|
||||||
previous: Previous,
|
|
||||||
url: String,
|
|
||||||
hotspot_x: u16,
|
|
||||||
hotspot_y: u16,
|
|
||||||
},
|
|
||||||
Image(Rc<RefCell<Option<CursorImageState>>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SelectedCursor {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Named(Default::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SelectedCursor {
|
|
||||||
pub fn set_style(&self, style: &Style) {
|
|
||||||
let value = match self {
|
|
||||||
SelectedCursor::Named(icon) => icon.name(),
|
|
||||||
SelectedCursor::Url { style, .. } => style,
|
|
||||||
SelectedCursor::Image(image) => {
|
|
||||||
let image = image.borrow();
|
|
||||||
let value = match image.deref().as_ref().unwrap() {
|
|
||||||
CursorImageState::Loading { previous, .. } => previous.style(),
|
|
||||||
CursorImageState::Failed(previous) => previous.style(),
|
|
||||||
CursorImageState::Ready { style, .. } => style,
|
|
||||||
};
|
|
||||||
return style.set("cursor", value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
style.set("cursor", value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Previous {
|
|
||||||
Named(CursorIcon),
|
|
||||||
Url {
|
|
||||||
style: String,
|
|
||||||
url: String,
|
|
||||||
hotspot_x: u16,
|
|
||||||
hotspot_y: u16,
|
|
||||||
},
|
|
||||||
Image {
|
|
||||||
style: String,
|
|
||||||
image: WebCursorImage,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Previous {
|
|
||||||
fn style(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Previous::Named(icon) => icon.name(),
|
|
||||||
Previous::Url { style: url, .. } => url,
|
|
||||||
Previous::Image { style, .. } => style,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn style_with_url(&self, new_url: &str, new_hotspot_x: u16, new_hotspot_y: u16) -> String {
|
|
||||||
match self {
|
|
||||||
Previous::Named(icon) => format!("url({new_url}) {new_hotspot_x} {new_hotspot_y}, {}", icon.name()),
|
|
||||||
Previous::Url {
|
|
||||||
url,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| Previous::Image {
|
|
||||||
image:
|
|
||||||
WebCursorImage {
|
|
||||||
data_url: url,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => format!(
|
|
||||||
"url({new_url}) {new_hotspot_x} {new_hotspot_y}, url({url}) {hotspot_x} {hotspot_y}, auto",
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SelectedCursor> for Previous {
|
|
||||||
fn from(value: SelectedCursor) -> Self {
|
|
||||||
match value {
|
|
||||||
SelectedCursor::Named(icon) => Self::Named(icon),
|
|
||||||
SelectedCursor::Url {
|
|
||||||
style,
|
|
||||||
url,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y,
|
|
||||||
..
|
|
||||||
} => Self::Url {
|
|
||||||
style,
|
|
||||||
url,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y,
|
|
||||||
},
|
|
||||||
SelectedCursor::Image(image) => {
|
|
||||||
match Rc::try_unwrap(image).unwrap().into_inner().unwrap() {
|
|
||||||
CursorImageState::Loading { previous, .. } => previous,
|
|
||||||
CursorImageState::Failed(previous) => previous,
|
|
||||||
CursorImageState::Ready {
|
|
||||||
style,
|
|
||||||
image: current,
|
|
||||||
..
|
|
||||||
} => Self::Image {
|
|
||||||
style,
|
|
||||||
image: current,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum CursorImageState {
|
|
||||||
Loading {
|
|
||||||
style: Style,
|
|
||||||
cursor_visible: Rc<Cell<bool>>,
|
|
||||||
previous: Previous,
|
|
||||||
hotspot_x: u16,
|
|
||||||
hotspot_y: u16,
|
|
||||||
},
|
|
||||||
Failed(Previous),
|
|
||||||
Ready {
|
|
||||||
style: String,
|
|
||||||
image: WebCursorImage,
|
|
||||||
previous: Previous,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CursorImageState {
|
|
||||||
fn from_image(
|
|
||||||
window: &Window,
|
|
||||||
document: Document,
|
|
||||||
style: Style,
|
|
||||||
image: &CursorImage,
|
|
||||||
previous: Previous,
|
|
||||||
cursor_visible: Rc<Cell<bool>>,
|
|
||||||
) -> Rc<RefCell<Option<Self>>> {
|
|
||||||
// Can't create array directly when backed by SharedArrayBuffer.
|
|
||||||
// Adapted from https://github.com/rust-windowing/softbuffer/blob/ab7688e2ed2e2eca51b3c4e1863a5bd7fe85800e/src/web.rs#L196-L223
|
|
||||||
#[cfg(target_feature = "atomics")]
|
|
||||||
let image_data = {
|
|
||||||
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)
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
#[cfg(not(target_feature = "atomics"))]
|
|
||||||
let image_data = ImageData::new_with_u8_clamped_array(
|
|
||||||
wasm_bindgen::Clamped(&image.rgba),
|
|
||||||
image.width as u32,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
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)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let state = Rc::new(RefCell::new(Some(Self::Loading {
|
|
||||||
style,
|
|
||||||
cursor_visible,
|
|
||||||
previous,
|
|
||||||
hotspot_x: image.hotspot_x,
|
|
||||||
hotspot_y: image.hotspot_y,
|
|
||||||
})));
|
|
||||||
|
|
||||||
wasm_bindgen_futures::spawn_local({
|
|
||||||
let weak = Rc::downgrade(&state);
|
|
||||||
let CursorImage { width, height, .. } = *image;
|
|
||||||
async move {
|
|
||||||
if weak.strong_count() == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bitmap: ImageBitmap = bitmap.await.unwrap().unchecked_into();
|
|
||||||
|
|
||||||
if weak.strong_count() == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let canvas: HtmlCanvasElement =
|
|
||||||
document.create_element("canvas").unwrap().unchecked_into();
|
|
||||||
#[allow(clippy::disallowed_methods)]
|
|
||||||
canvas.set_width(width as u32);
|
|
||||||
#[allow(clippy::disallowed_methods)]
|
|
||||||
canvas.set_height(height as u32);
|
|
||||||
|
|
||||||
let context: ImageBitmapRenderingContext = canvas
|
|
||||||
.get_context("bitmaprenderer")
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.unchecked_into();
|
|
||||||
context.transfer_from_image_bitmap(&bitmap);
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
static CURRENT_STATE: RefCell<Option<Weak<RefCell<Option<CursorImageState>>>>> = RefCell::new(None);
|
|
||||||
// `HTMLCanvasElement.toBlob()` can't be interrupted. So we have to use a
|
|
||||||
// `Closure` that doesn't need to be garbage-collected.
|
|
||||||
static CALLBACK: Closure<dyn Fn(Option<Blob>)> = Closure::new(|blob| {
|
|
||||||
CURRENT_STATE.with(|weak| {
|
|
||||||
let Some(state) = weak.borrow_mut().take().and_then(|weak| weak.upgrade()) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let mut state = state.borrow_mut();
|
|
||||||
// Extract old state.
|
|
||||||
let CursorImageState::Loading { style, cursor_visible, previous, hotspot_x, hotspot_y, .. } = state.take().unwrap() else {
|
|
||||||
unreachable!("found invalid state")
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(blob) = blob else {
|
|
||||||
*state = Some(CursorImageState::Failed(previous));
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let data_url = Url::create_object_url_with_blob(&blob).unwrap();
|
|
||||||
|
|
||||||
let value = previous.style_with_url(&data_url, hotspot_x, hotspot_y);
|
|
||||||
|
|
||||||
if cursor_visible.get() {
|
|
||||||
style.set("cursor", &value);
|
|
||||||
}
|
|
||||||
|
|
||||||
*state = Some(
|
|
||||||
CursorImageState::Ready {
|
|
||||||
style: value,
|
|
||||||
image: WebCursorImage{ data_url, hotspot_x, hotspot_y },
|
|
||||||
previous,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
CURRENT_STATE.with(|state| *state.borrow_mut() = Some(weak));
|
|
||||||
CALLBACK
|
|
||||||
.with(|callback| canvas.to_blob(callback.as_ref().unchecked_ref()).unwrap());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct WebCursorImage {
|
|
||||||
data_url: String,
|
|
||||||
hotspot_x: u16,
|
|
||||||
hotspot_y: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for WebCursorImage {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
Url::revoke_object_url(&self.data_url).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId(pub i32);
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub const unsafe fn dummy() -> Self {
|
|
||||||
Self(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,522 +0,0 @@
|
|||||||
use smol_str::SmolStr;
|
|
||||||
|
|
||||||
use crate::keyboard::{Key, KeyCode, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub(crate) struct KeyEventExtra;
|
|
||||||
|
|
||||||
impl Key {
|
|
||||||
pub(crate) fn from_key_attribute_value(kav: &str) -> Self {
|
|
||||||
Key::Named(match kav {
|
|
||||||
"Unidentified" => return Key::Unidentified(NativeKey::Web(SmolStr::new(kav))),
|
|
||||||
"Dead" => return Key::Dead(None),
|
|
||||||
"Alt" => NamedKey::Alt,
|
|
||||||
"AltGraph" => NamedKey::AltGraph,
|
|
||||||
"CapsLock" => NamedKey::CapsLock,
|
|
||||||
"Control" => NamedKey::Control,
|
|
||||||
"Fn" => NamedKey::Fn,
|
|
||||||
"FnLock" => NamedKey::FnLock,
|
|
||||||
"NumLock" => NamedKey::NumLock,
|
|
||||||
"ScrollLock" => NamedKey::ScrollLock,
|
|
||||||
"Shift" => NamedKey::Shift,
|
|
||||||
"Symbol" => NamedKey::Symbol,
|
|
||||||
"SymbolLock" => NamedKey::SymbolLock,
|
|
||||||
"Hyper" => NamedKey::Hyper,
|
|
||||||
"Meta" => NamedKey::Super,
|
|
||||||
"Enter" => NamedKey::Enter,
|
|
||||||
"Tab" => NamedKey::Tab,
|
|
||||||
" " => NamedKey::Space,
|
|
||||||
"ArrowDown" => NamedKey::ArrowDown,
|
|
||||||
"ArrowLeft" => NamedKey::ArrowLeft,
|
|
||||||
"ArrowRight" => NamedKey::ArrowRight,
|
|
||||||
"ArrowUp" => NamedKey::ArrowUp,
|
|
||||||
"End" => NamedKey::End,
|
|
||||||
"Home" => NamedKey::Home,
|
|
||||||
"PageDown" => NamedKey::PageDown,
|
|
||||||
"PageUp" => NamedKey::PageUp,
|
|
||||||
"Backspace" => NamedKey::Backspace,
|
|
||||||
"Clear" => NamedKey::Clear,
|
|
||||||
"Copy" => NamedKey::Copy,
|
|
||||||
"CrSel" => NamedKey::CrSel,
|
|
||||||
"Cut" => NamedKey::Cut,
|
|
||||||
"Delete" => NamedKey::Delete,
|
|
||||||
"EraseEof" => NamedKey::EraseEof,
|
|
||||||
"ExSel" => NamedKey::ExSel,
|
|
||||||
"Insert" => NamedKey::Insert,
|
|
||||||
"Paste" => NamedKey::Paste,
|
|
||||||
"Redo" => NamedKey::Redo,
|
|
||||||
"Undo" => NamedKey::Undo,
|
|
||||||
"Accept" => NamedKey::Accept,
|
|
||||||
"Again" => NamedKey::Again,
|
|
||||||
"Attn" => NamedKey::Attn,
|
|
||||||
"Cancel" => NamedKey::Cancel,
|
|
||||||
"ContextMenu" => NamedKey::ContextMenu,
|
|
||||||
"Escape" => NamedKey::Escape,
|
|
||||||
"Execute" => NamedKey::Execute,
|
|
||||||
"Find" => NamedKey::Find,
|
|
||||||
"Help" => NamedKey::Help,
|
|
||||||
"Pause" => NamedKey::Pause,
|
|
||||||
"Play" => NamedKey::Play,
|
|
||||||
"Props" => NamedKey::Props,
|
|
||||||
"Select" => NamedKey::Select,
|
|
||||||
"ZoomIn" => NamedKey::ZoomIn,
|
|
||||||
"ZoomOut" => NamedKey::ZoomOut,
|
|
||||||
"BrightnessDown" => NamedKey::BrightnessDown,
|
|
||||||
"BrightnessUp" => NamedKey::BrightnessUp,
|
|
||||||
"Eject" => NamedKey::Eject,
|
|
||||||
"LogOff" => NamedKey::LogOff,
|
|
||||||
"Power" => NamedKey::Power,
|
|
||||||
"PowerOff" => NamedKey::PowerOff,
|
|
||||||
"PrintScreen" => NamedKey::PrintScreen,
|
|
||||||
"Hibernate" => NamedKey::Hibernate,
|
|
||||||
"Standby" => NamedKey::Standby,
|
|
||||||
"WakeUp" => NamedKey::WakeUp,
|
|
||||||
"AllCandidates" => NamedKey::AllCandidates,
|
|
||||||
"Alphanumeric" => NamedKey::Alphanumeric,
|
|
||||||
"CodeInput" => NamedKey::CodeInput,
|
|
||||||
"Compose" => NamedKey::Compose,
|
|
||||||
"Convert" => NamedKey::Convert,
|
|
||||||
"FinalMode" => NamedKey::FinalMode,
|
|
||||||
"GroupFirst" => NamedKey::GroupFirst,
|
|
||||||
"GroupLast" => NamedKey::GroupLast,
|
|
||||||
"GroupNext" => NamedKey::GroupNext,
|
|
||||||
"GroupPrevious" => NamedKey::GroupPrevious,
|
|
||||||
"ModeChange" => NamedKey::ModeChange,
|
|
||||||
"NextCandidate" => NamedKey::NextCandidate,
|
|
||||||
"NonConvert" => NamedKey::NonConvert,
|
|
||||||
"PreviousCandidate" => NamedKey::PreviousCandidate,
|
|
||||||
"Process" => NamedKey::Process,
|
|
||||||
"SingleCandidate" => NamedKey::SingleCandidate,
|
|
||||||
"HangulMode" => NamedKey::HangulMode,
|
|
||||||
"HanjaMode" => NamedKey::HanjaMode,
|
|
||||||
"JunjaMode" => NamedKey::JunjaMode,
|
|
||||||
"Eisu" => NamedKey::Eisu,
|
|
||||||
"Hankaku" => NamedKey::Hankaku,
|
|
||||||
"Hiragana" => NamedKey::Hiragana,
|
|
||||||
"HiraganaKatakana" => NamedKey::HiraganaKatakana,
|
|
||||||
"KanaMode" => NamedKey::KanaMode,
|
|
||||||
"KanjiMode" => NamedKey::KanjiMode,
|
|
||||||
"Katakana" => NamedKey::Katakana,
|
|
||||||
"Romaji" => NamedKey::Romaji,
|
|
||||||
"Zenkaku" => NamedKey::Zenkaku,
|
|
||||||
"ZenkakuHankaku" => NamedKey::ZenkakuHankaku,
|
|
||||||
"Soft1" => NamedKey::Soft1,
|
|
||||||
"Soft2" => NamedKey::Soft2,
|
|
||||||
"Soft3" => NamedKey::Soft3,
|
|
||||||
"Soft4" => NamedKey::Soft4,
|
|
||||||
"ChannelDown" => NamedKey::ChannelDown,
|
|
||||||
"ChannelUp" => NamedKey::ChannelUp,
|
|
||||||
"Close" => NamedKey::Close,
|
|
||||||
"MailForward" => NamedKey::MailForward,
|
|
||||||
"MailReply" => NamedKey::MailReply,
|
|
||||||
"MailSend" => NamedKey::MailSend,
|
|
||||||
"MediaClose" => NamedKey::MediaClose,
|
|
||||||
"MediaFastForward" => NamedKey::MediaFastForward,
|
|
||||||
"MediaPause" => NamedKey::MediaPause,
|
|
||||||
"MediaPlay" => NamedKey::MediaPlay,
|
|
||||||
"MediaPlayPause" => NamedKey::MediaPlayPause,
|
|
||||||
"MediaRecord" => NamedKey::MediaRecord,
|
|
||||||
"MediaRewind" => NamedKey::MediaRewind,
|
|
||||||
"MediaStop" => NamedKey::MediaStop,
|
|
||||||
"MediaTrackNext" => NamedKey::MediaTrackNext,
|
|
||||||
"MediaTrackPrevious" => NamedKey::MediaTrackPrevious,
|
|
||||||
"New" => NamedKey::New,
|
|
||||||
"Open" => NamedKey::Open,
|
|
||||||
"Print" => NamedKey::Print,
|
|
||||||
"Save" => NamedKey::Save,
|
|
||||||
"SpellCheck" => NamedKey::SpellCheck,
|
|
||||||
"Key11" => NamedKey::Key11,
|
|
||||||
"Key12" => NamedKey::Key12,
|
|
||||||
"AudioBalanceLeft" => NamedKey::AudioBalanceLeft,
|
|
||||||
"AudioBalanceRight" => NamedKey::AudioBalanceRight,
|
|
||||||
"AudioBassBoostDown" => NamedKey::AudioBassBoostDown,
|
|
||||||
"AudioBassBoostToggle" => NamedKey::AudioBassBoostToggle,
|
|
||||||
"AudioBassBoostUp" => NamedKey::AudioBassBoostUp,
|
|
||||||
"AudioFaderFront" => NamedKey::AudioFaderFront,
|
|
||||||
"AudioFaderRear" => NamedKey::AudioFaderRear,
|
|
||||||
"AudioSurroundModeNext" => NamedKey::AudioSurroundModeNext,
|
|
||||||
"AudioTrebleDown" => NamedKey::AudioTrebleDown,
|
|
||||||
"AudioTrebleUp" => NamedKey::AudioTrebleUp,
|
|
||||||
"AudioVolumeDown" => NamedKey::AudioVolumeDown,
|
|
||||||
"AudioVolumeUp" => NamedKey::AudioVolumeUp,
|
|
||||||
"AudioVolumeMute" => NamedKey::AudioVolumeMute,
|
|
||||||
"MicrophoneToggle" => NamedKey::MicrophoneToggle,
|
|
||||||
"MicrophoneVolumeDown" => NamedKey::MicrophoneVolumeDown,
|
|
||||||
"MicrophoneVolumeUp" => NamedKey::MicrophoneVolumeUp,
|
|
||||||
"MicrophoneVolumeMute" => NamedKey::MicrophoneVolumeMute,
|
|
||||||
"SpeechCorrectionList" => NamedKey::SpeechCorrectionList,
|
|
||||||
"SpeechInputToggle" => NamedKey::SpeechInputToggle,
|
|
||||||
"LaunchApplication1" => NamedKey::LaunchApplication1,
|
|
||||||
"LaunchApplication2" => NamedKey::LaunchApplication2,
|
|
||||||
"LaunchCalendar" => NamedKey::LaunchCalendar,
|
|
||||||
"LaunchContacts" => NamedKey::LaunchContacts,
|
|
||||||
"LaunchMail" => NamedKey::LaunchMail,
|
|
||||||
"LaunchMediaPlayer" => NamedKey::LaunchMediaPlayer,
|
|
||||||
"LaunchMusicPlayer" => NamedKey::LaunchMusicPlayer,
|
|
||||||
"LaunchPhone" => NamedKey::LaunchPhone,
|
|
||||||
"LaunchScreenSaver" => NamedKey::LaunchScreenSaver,
|
|
||||||
"LaunchSpreadsheet" => NamedKey::LaunchSpreadsheet,
|
|
||||||
"LaunchWebBrowser" => NamedKey::LaunchWebBrowser,
|
|
||||||
"LaunchWebCam" => NamedKey::LaunchWebCam,
|
|
||||||
"LaunchWordProcessor" => NamedKey::LaunchWordProcessor,
|
|
||||||
"BrowserBack" => NamedKey::BrowserBack,
|
|
||||||
"BrowserFavorites" => NamedKey::BrowserFavorites,
|
|
||||||
"BrowserForward" => NamedKey::BrowserForward,
|
|
||||||
"BrowserHome" => NamedKey::BrowserHome,
|
|
||||||
"BrowserRefresh" => NamedKey::BrowserRefresh,
|
|
||||||
"BrowserSearch" => NamedKey::BrowserSearch,
|
|
||||||
"BrowserStop" => NamedKey::BrowserStop,
|
|
||||||
"AppSwitch" => NamedKey::AppSwitch,
|
|
||||||
"Call" => NamedKey::Call,
|
|
||||||
"Camera" => NamedKey::Camera,
|
|
||||||
"CameraFocus" => NamedKey::CameraFocus,
|
|
||||||
"EndCall" => NamedKey::EndCall,
|
|
||||||
"GoBack" => NamedKey::GoBack,
|
|
||||||
"GoHome" => NamedKey::GoHome,
|
|
||||||
"HeadsetHook" => NamedKey::HeadsetHook,
|
|
||||||
"LastNumberRedial" => NamedKey::LastNumberRedial,
|
|
||||||
"Notification" => NamedKey::Notification,
|
|
||||||
"MannerMode" => NamedKey::MannerMode,
|
|
||||||
"VoiceDial" => NamedKey::VoiceDial,
|
|
||||||
"TV" => NamedKey::TV,
|
|
||||||
"TV3DMode" => NamedKey::TV3DMode,
|
|
||||||
"TVAntennaCable" => NamedKey::TVAntennaCable,
|
|
||||||
"TVAudioDescription" => NamedKey::TVAudioDescription,
|
|
||||||
"TVAudioDescriptionMixDown" => NamedKey::TVAudioDescriptionMixDown,
|
|
||||||
"TVAudioDescriptionMixUp" => NamedKey::TVAudioDescriptionMixUp,
|
|
||||||
"TVContentsMenu" => NamedKey::TVContentsMenu,
|
|
||||||
"TVDataService" => NamedKey::TVDataService,
|
|
||||||
"TVInput" => NamedKey::TVInput,
|
|
||||||
"TVInputComponent1" => NamedKey::TVInputComponent1,
|
|
||||||
"TVInputComponent2" => NamedKey::TVInputComponent2,
|
|
||||||
"TVInputComposite1" => NamedKey::TVInputComposite1,
|
|
||||||
"TVInputComposite2" => NamedKey::TVInputComposite2,
|
|
||||||
"TVInputHDMI1" => NamedKey::TVInputHDMI1,
|
|
||||||
"TVInputHDMI2" => NamedKey::TVInputHDMI2,
|
|
||||||
"TVInputHDMI3" => NamedKey::TVInputHDMI3,
|
|
||||||
"TVInputHDMI4" => NamedKey::TVInputHDMI4,
|
|
||||||
"TVInputVGA1" => NamedKey::TVInputVGA1,
|
|
||||||
"TVMediaContext" => NamedKey::TVMediaContext,
|
|
||||||
"TVNetwork" => NamedKey::TVNetwork,
|
|
||||||
"TVNumberEntry" => NamedKey::TVNumberEntry,
|
|
||||||
"TVPower" => NamedKey::TVPower,
|
|
||||||
"TVRadioService" => NamedKey::TVRadioService,
|
|
||||||
"TVSatellite" => NamedKey::TVSatellite,
|
|
||||||
"TVSatelliteBS" => NamedKey::TVSatelliteBS,
|
|
||||||
"TVSatelliteCS" => NamedKey::TVSatelliteCS,
|
|
||||||
"TVSatelliteToggle" => NamedKey::TVSatelliteToggle,
|
|
||||||
"TVTerrestrialAnalog" => NamedKey::TVTerrestrialAnalog,
|
|
||||||
"TVTerrestrialDigital" => NamedKey::TVTerrestrialDigital,
|
|
||||||
"TVTimer" => NamedKey::TVTimer,
|
|
||||||
"AVRInput" => NamedKey::AVRInput,
|
|
||||||
"AVRPower" => NamedKey::AVRPower,
|
|
||||||
"ColorF0Red" => NamedKey::ColorF0Red,
|
|
||||||
"ColorF1Green" => NamedKey::ColorF1Green,
|
|
||||||
"ColorF2Yellow" => NamedKey::ColorF2Yellow,
|
|
||||||
"ColorF3Blue" => NamedKey::ColorF3Blue,
|
|
||||||
"ColorF4Grey" => NamedKey::ColorF4Grey,
|
|
||||||
"ColorF5Brown" => NamedKey::ColorF5Brown,
|
|
||||||
"ClosedCaptionToggle" => NamedKey::ClosedCaptionToggle,
|
|
||||||
"Dimmer" => NamedKey::Dimmer,
|
|
||||||
"DisplaySwap" => NamedKey::DisplaySwap,
|
|
||||||
"DVR" => NamedKey::DVR,
|
|
||||||
"Exit" => NamedKey::Exit,
|
|
||||||
"FavoriteClear0" => NamedKey::FavoriteClear0,
|
|
||||||
"FavoriteClear1" => NamedKey::FavoriteClear1,
|
|
||||||
"FavoriteClear2" => NamedKey::FavoriteClear2,
|
|
||||||
"FavoriteClear3" => NamedKey::FavoriteClear3,
|
|
||||||
"FavoriteRecall0" => NamedKey::FavoriteRecall0,
|
|
||||||
"FavoriteRecall1" => NamedKey::FavoriteRecall1,
|
|
||||||
"FavoriteRecall2" => NamedKey::FavoriteRecall2,
|
|
||||||
"FavoriteRecall3" => NamedKey::FavoriteRecall3,
|
|
||||||
"FavoriteStore0" => NamedKey::FavoriteStore0,
|
|
||||||
"FavoriteStore1" => NamedKey::FavoriteStore1,
|
|
||||||
"FavoriteStore2" => NamedKey::FavoriteStore2,
|
|
||||||
"FavoriteStore3" => NamedKey::FavoriteStore3,
|
|
||||||
"Guide" => NamedKey::Guide,
|
|
||||||
"GuideNextDay" => NamedKey::GuideNextDay,
|
|
||||||
"GuidePreviousDay" => NamedKey::GuidePreviousDay,
|
|
||||||
"Info" => NamedKey::Info,
|
|
||||||
"InstantReplay" => NamedKey::InstantReplay,
|
|
||||||
"Link" => NamedKey::Link,
|
|
||||||
"ListProgram" => NamedKey::ListProgram,
|
|
||||||
"LiveContent" => NamedKey::LiveContent,
|
|
||||||
"Lock" => NamedKey::Lock,
|
|
||||||
"MediaApps" => NamedKey::MediaApps,
|
|
||||||
"MediaAudioTrack" => NamedKey::MediaAudioTrack,
|
|
||||||
"MediaLast" => NamedKey::MediaLast,
|
|
||||||
"MediaSkipBackward" => NamedKey::MediaSkipBackward,
|
|
||||||
"MediaSkipForward" => NamedKey::MediaSkipForward,
|
|
||||||
"MediaStepBackward" => NamedKey::MediaStepBackward,
|
|
||||||
"MediaStepForward" => NamedKey::MediaStepForward,
|
|
||||||
"MediaTopMenu" => NamedKey::MediaTopMenu,
|
|
||||||
"NavigateIn" => NamedKey::NavigateIn,
|
|
||||||
"NavigateNext" => NamedKey::NavigateNext,
|
|
||||||
"NavigateOut" => NamedKey::NavigateOut,
|
|
||||||
"NavigatePrevious" => NamedKey::NavigatePrevious,
|
|
||||||
"NextFavoriteChannel" => NamedKey::NextFavoriteChannel,
|
|
||||||
"NextUserProfile" => NamedKey::NextUserProfile,
|
|
||||||
"OnDemand" => NamedKey::OnDemand,
|
|
||||||
"Pairing" => NamedKey::Pairing,
|
|
||||||
"PinPDown" => NamedKey::PinPDown,
|
|
||||||
"PinPMove" => NamedKey::PinPMove,
|
|
||||||
"PinPToggle" => NamedKey::PinPToggle,
|
|
||||||
"PinPUp" => NamedKey::PinPUp,
|
|
||||||
"PlaySpeedDown" => NamedKey::PlaySpeedDown,
|
|
||||||
"PlaySpeedReset" => NamedKey::PlaySpeedReset,
|
|
||||||
"PlaySpeedUp" => NamedKey::PlaySpeedUp,
|
|
||||||
"RandomToggle" => NamedKey::RandomToggle,
|
|
||||||
"RcLowBattery" => NamedKey::RcLowBattery,
|
|
||||||
"RecordSpeedNext" => NamedKey::RecordSpeedNext,
|
|
||||||
"RfBypass" => NamedKey::RfBypass,
|
|
||||||
"ScanChannelsToggle" => NamedKey::ScanChannelsToggle,
|
|
||||||
"ScreenModeNext" => NamedKey::ScreenModeNext,
|
|
||||||
"Settings" => NamedKey::Settings,
|
|
||||||
"SplitScreenToggle" => NamedKey::SplitScreenToggle,
|
|
||||||
"STBInput" => NamedKey::STBInput,
|
|
||||||
"STBPower" => NamedKey::STBPower,
|
|
||||||
"Subtitle" => NamedKey::Subtitle,
|
|
||||||
"Teletext" => NamedKey::Teletext,
|
|
||||||
"VideoModeNext" => NamedKey::VideoModeNext,
|
|
||||||
"Wink" => NamedKey::Wink,
|
|
||||||
"ZoomToggle" => NamedKey::ZoomToggle,
|
|
||||||
"F1" => NamedKey::F1,
|
|
||||||
"F2" => NamedKey::F2,
|
|
||||||
"F3" => NamedKey::F3,
|
|
||||||
"F4" => NamedKey::F4,
|
|
||||||
"F5" => NamedKey::F5,
|
|
||||||
"F6" => NamedKey::F6,
|
|
||||||
"F7" => NamedKey::F7,
|
|
||||||
"F8" => NamedKey::F8,
|
|
||||||
"F9" => NamedKey::F9,
|
|
||||||
"F10" => NamedKey::F10,
|
|
||||||
"F11" => NamedKey::F11,
|
|
||||||
"F12" => NamedKey::F12,
|
|
||||||
"F13" => NamedKey::F13,
|
|
||||||
"F14" => NamedKey::F14,
|
|
||||||
"F15" => NamedKey::F15,
|
|
||||||
"F16" => NamedKey::F16,
|
|
||||||
"F17" => NamedKey::F17,
|
|
||||||
"F18" => NamedKey::F18,
|
|
||||||
"F19" => NamedKey::F19,
|
|
||||||
"F20" => NamedKey::F20,
|
|
||||||
"F21" => NamedKey::F21,
|
|
||||||
"F22" => NamedKey::F22,
|
|
||||||
"F23" => NamedKey::F23,
|
|
||||||
"F24" => NamedKey::F24,
|
|
||||||
"F25" => NamedKey::F25,
|
|
||||||
"F26" => NamedKey::F26,
|
|
||||||
"F27" => NamedKey::F27,
|
|
||||||
"F28" => NamedKey::F28,
|
|
||||||
"F29" => NamedKey::F29,
|
|
||||||
"F30" => NamedKey::F30,
|
|
||||||
"F31" => NamedKey::F31,
|
|
||||||
"F32" => NamedKey::F32,
|
|
||||||
"F33" => NamedKey::F33,
|
|
||||||
"F34" => NamedKey::F34,
|
|
||||||
"F35" => NamedKey::F35,
|
|
||||||
string => return Key::Character(SmolStr::new(string)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhysicalKey {
|
|
||||||
pub fn from_key_code_attribute_value(kcav: &str) -> Self {
|
|
||||||
PhysicalKey::Code(match kcav {
|
|
||||||
"Backquote" => KeyCode::Backquote,
|
|
||||||
"Backslash" => KeyCode::Backslash,
|
|
||||||
"BracketLeft" => KeyCode::BracketLeft,
|
|
||||||
"BracketRight" => KeyCode::BracketRight,
|
|
||||||
"Comma" => KeyCode::Comma,
|
|
||||||
"Digit0" => KeyCode::Digit0,
|
|
||||||
"Digit1" => KeyCode::Digit1,
|
|
||||||
"Digit2" => KeyCode::Digit2,
|
|
||||||
"Digit3" => KeyCode::Digit3,
|
|
||||||
"Digit4" => KeyCode::Digit4,
|
|
||||||
"Digit5" => KeyCode::Digit5,
|
|
||||||
"Digit6" => KeyCode::Digit6,
|
|
||||||
"Digit7" => KeyCode::Digit7,
|
|
||||||
"Digit8" => KeyCode::Digit8,
|
|
||||||
"Digit9" => KeyCode::Digit9,
|
|
||||||
"Equal" => KeyCode::Equal,
|
|
||||||
"IntlBackslash" => KeyCode::IntlBackslash,
|
|
||||||
"IntlRo" => KeyCode::IntlRo,
|
|
||||||
"IntlYen" => KeyCode::IntlYen,
|
|
||||||
"KeyA" => KeyCode::KeyA,
|
|
||||||
"KeyB" => KeyCode::KeyB,
|
|
||||||
"KeyC" => KeyCode::KeyC,
|
|
||||||
"KeyD" => KeyCode::KeyD,
|
|
||||||
"KeyE" => KeyCode::KeyE,
|
|
||||||
"KeyF" => KeyCode::KeyF,
|
|
||||||
"KeyG" => KeyCode::KeyG,
|
|
||||||
"KeyH" => KeyCode::KeyH,
|
|
||||||
"KeyI" => KeyCode::KeyI,
|
|
||||||
"KeyJ" => KeyCode::KeyJ,
|
|
||||||
"KeyK" => KeyCode::KeyK,
|
|
||||||
"KeyL" => KeyCode::KeyL,
|
|
||||||
"KeyM" => KeyCode::KeyM,
|
|
||||||
"KeyN" => KeyCode::KeyN,
|
|
||||||
"KeyO" => KeyCode::KeyO,
|
|
||||||
"KeyP" => KeyCode::KeyP,
|
|
||||||
"KeyQ" => KeyCode::KeyQ,
|
|
||||||
"KeyR" => KeyCode::KeyR,
|
|
||||||
"KeyS" => KeyCode::KeyS,
|
|
||||||
"KeyT" => KeyCode::KeyT,
|
|
||||||
"KeyU" => KeyCode::KeyU,
|
|
||||||
"KeyV" => KeyCode::KeyV,
|
|
||||||
"KeyW" => KeyCode::KeyW,
|
|
||||||
"KeyX" => KeyCode::KeyX,
|
|
||||||
"KeyY" => KeyCode::KeyY,
|
|
||||||
"KeyZ" => KeyCode::KeyZ,
|
|
||||||
"Minus" => KeyCode::Minus,
|
|
||||||
"Period" => KeyCode::Period,
|
|
||||||
"Quote" => KeyCode::Quote,
|
|
||||||
"Semicolon" => KeyCode::Semicolon,
|
|
||||||
"Slash" => KeyCode::Slash,
|
|
||||||
"AltLeft" => KeyCode::AltLeft,
|
|
||||||
"AltRight" => KeyCode::AltRight,
|
|
||||||
"Backspace" => KeyCode::Backspace,
|
|
||||||
"CapsLock" => KeyCode::CapsLock,
|
|
||||||
"ContextMenu" => KeyCode::ContextMenu,
|
|
||||||
"ControlLeft" => KeyCode::ControlLeft,
|
|
||||||
"ControlRight" => KeyCode::ControlRight,
|
|
||||||
"Enter" => KeyCode::Enter,
|
|
||||||
"MetaLeft" => KeyCode::SuperLeft,
|
|
||||||
"MetaRight" => KeyCode::SuperRight,
|
|
||||||
"ShiftLeft" => KeyCode::ShiftLeft,
|
|
||||||
"ShiftRight" => KeyCode::ShiftRight,
|
|
||||||
"Space" => KeyCode::Space,
|
|
||||||
"Tab" => KeyCode::Tab,
|
|
||||||
"Convert" => KeyCode::Convert,
|
|
||||||
"KanaMode" => KeyCode::KanaMode,
|
|
||||||
"Lang1" => KeyCode::Lang1,
|
|
||||||
"Lang2" => KeyCode::Lang2,
|
|
||||||
"Lang3" => KeyCode::Lang3,
|
|
||||||
"Lang4" => KeyCode::Lang4,
|
|
||||||
"Lang5" => KeyCode::Lang5,
|
|
||||||
"NonConvert" => KeyCode::NonConvert,
|
|
||||||
"Delete" => KeyCode::Delete,
|
|
||||||
"End" => KeyCode::End,
|
|
||||||
"Help" => KeyCode::Help,
|
|
||||||
"Home" => KeyCode::Home,
|
|
||||||
"Insert" => KeyCode::Insert,
|
|
||||||
"PageDown" => KeyCode::PageDown,
|
|
||||||
"PageUp" => KeyCode::PageUp,
|
|
||||||
"ArrowDown" => KeyCode::ArrowDown,
|
|
||||||
"ArrowLeft" => KeyCode::ArrowLeft,
|
|
||||||
"ArrowRight" => KeyCode::ArrowRight,
|
|
||||||
"ArrowUp" => KeyCode::ArrowUp,
|
|
||||||
"NumLock" => KeyCode::NumLock,
|
|
||||||
"Numpad0" => KeyCode::Numpad0,
|
|
||||||
"Numpad1" => KeyCode::Numpad1,
|
|
||||||
"Numpad2" => KeyCode::Numpad2,
|
|
||||||
"Numpad3" => KeyCode::Numpad3,
|
|
||||||
"Numpad4" => KeyCode::Numpad4,
|
|
||||||
"Numpad5" => KeyCode::Numpad5,
|
|
||||||
"Numpad6" => KeyCode::Numpad6,
|
|
||||||
"Numpad7" => KeyCode::Numpad7,
|
|
||||||
"Numpad8" => KeyCode::Numpad8,
|
|
||||||
"Numpad9" => KeyCode::Numpad9,
|
|
||||||
"NumpadAdd" => KeyCode::NumpadAdd,
|
|
||||||
"NumpadBackspace" => KeyCode::NumpadBackspace,
|
|
||||||
"NumpadClear" => KeyCode::NumpadClear,
|
|
||||||
"NumpadClearEntry" => KeyCode::NumpadClearEntry,
|
|
||||||
"NumpadComma" => KeyCode::NumpadComma,
|
|
||||||
"NumpadDecimal" => KeyCode::NumpadDecimal,
|
|
||||||
"NumpadDivide" => KeyCode::NumpadDivide,
|
|
||||||
"NumpadEnter" => KeyCode::NumpadEnter,
|
|
||||||
"NumpadEqual" => KeyCode::NumpadEqual,
|
|
||||||
"NumpadHash" => KeyCode::NumpadHash,
|
|
||||||
"NumpadMemoryAdd" => KeyCode::NumpadMemoryAdd,
|
|
||||||
"NumpadMemoryClear" => KeyCode::NumpadMemoryClear,
|
|
||||||
"NumpadMemoryRecall" => KeyCode::NumpadMemoryRecall,
|
|
||||||
"NumpadMemoryStore" => KeyCode::NumpadMemoryStore,
|
|
||||||
"NumpadMemorySubtract" => KeyCode::NumpadMemorySubtract,
|
|
||||||
"NumpadMultiply" => KeyCode::NumpadMultiply,
|
|
||||||
"NumpadParenLeft" => KeyCode::NumpadParenLeft,
|
|
||||||
"NumpadParenRight" => KeyCode::NumpadParenRight,
|
|
||||||
"NumpadStar" => KeyCode::NumpadStar,
|
|
||||||
"NumpadSubtract" => KeyCode::NumpadSubtract,
|
|
||||||
"Escape" => KeyCode::Escape,
|
|
||||||
"Fn" => KeyCode::Fn,
|
|
||||||
"FnLock" => KeyCode::FnLock,
|
|
||||||
"PrintScreen" => KeyCode::PrintScreen,
|
|
||||||
"ScrollLock" => KeyCode::ScrollLock,
|
|
||||||
"Pause" => KeyCode::Pause,
|
|
||||||
"BrowserBack" => KeyCode::BrowserBack,
|
|
||||||
"BrowserFavorites" => KeyCode::BrowserFavorites,
|
|
||||||
"BrowserForward" => KeyCode::BrowserForward,
|
|
||||||
"BrowserHome" => KeyCode::BrowserHome,
|
|
||||||
"BrowserRefresh" => KeyCode::BrowserRefresh,
|
|
||||||
"BrowserSearch" => KeyCode::BrowserSearch,
|
|
||||||
"BrowserStop" => KeyCode::BrowserStop,
|
|
||||||
"Eject" => KeyCode::Eject,
|
|
||||||
"LaunchApp1" => KeyCode::LaunchApp1,
|
|
||||||
"LaunchApp2" => KeyCode::LaunchApp2,
|
|
||||||
"LaunchMail" => KeyCode::LaunchMail,
|
|
||||||
"MediaPlayPause" => KeyCode::MediaPlayPause,
|
|
||||||
"MediaSelect" => KeyCode::MediaSelect,
|
|
||||||
"MediaStop" => KeyCode::MediaStop,
|
|
||||||
"MediaTrackNext" => KeyCode::MediaTrackNext,
|
|
||||||
"MediaTrackPrevious" => KeyCode::MediaTrackPrevious,
|
|
||||||
"Power" => KeyCode::Power,
|
|
||||||
"Sleep" => KeyCode::Sleep,
|
|
||||||
"AudioVolumeDown" => KeyCode::AudioVolumeDown,
|
|
||||||
"AudioVolumeMute" => KeyCode::AudioVolumeMute,
|
|
||||||
"AudioVolumeUp" => KeyCode::AudioVolumeUp,
|
|
||||||
"WakeUp" => KeyCode::WakeUp,
|
|
||||||
"Hyper" => KeyCode::Hyper,
|
|
||||||
"Turbo" => KeyCode::Turbo,
|
|
||||||
"Abort" => KeyCode::Abort,
|
|
||||||
"Resume" => KeyCode::Resume,
|
|
||||||
"Suspend" => KeyCode::Suspend,
|
|
||||||
"Again" => KeyCode::Again,
|
|
||||||
"Copy" => KeyCode::Copy,
|
|
||||||
"Cut" => KeyCode::Cut,
|
|
||||||
"Find" => KeyCode::Find,
|
|
||||||
"Open" => KeyCode::Open,
|
|
||||||
"Paste" => KeyCode::Paste,
|
|
||||||
"Props" => KeyCode::Props,
|
|
||||||
"Select" => KeyCode::Select,
|
|
||||||
"Undo" => KeyCode::Undo,
|
|
||||||
"Hiragana" => KeyCode::Hiragana,
|
|
||||||
"Katakana" => KeyCode::Katakana,
|
|
||||||
"F1" => KeyCode::F1,
|
|
||||||
"F2" => KeyCode::F2,
|
|
||||||
"F3" => KeyCode::F3,
|
|
||||||
"F4" => KeyCode::F4,
|
|
||||||
"F5" => KeyCode::F5,
|
|
||||||
"F6" => KeyCode::F6,
|
|
||||||
"F7" => KeyCode::F7,
|
|
||||||
"F8" => KeyCode::F8,
|
|
||||||
"F9" => KeyCode::F9,
|
|
||||||
"F10" => KeyCode::F10,
|
|
||||||
"F11" => KeyCode::F11,
|
|
||||||
"F12" => KeyCode::F12,
|
|
||||||
"F13" => KeyCode::F13,
|
|
||||||
"F14" => KeyCode::F14,
|
|
||||||
"F15" => KeyCode::F15,
|
|
||||||
"F16" => KeyCode::F16,
|
|
||||||
"F17" => KeyCode::F17,
|
|
||||||
"F18" => KeyCode::F18,
|
|
||||||
"F19" => KeyCode::F19,
|
|
||||||
"F20" => KeyCode::F20,
|
|
||||||
"F21" => KeyCode::F21,
|
|
||||||
"F22" => KeyCode::F22,
|
|
||||||
"F23" => KeyCode::F23,
|
|
||||||
"F24" => KeyCode::F24,
|
|
||||||
"F25" => KeyCode::F25,
|
|
||||||
"F26" => KeyCode::F26,
|
|
||||||
"F27" => KeyCode::F27,
|
|
||||||
"F28" => KeyCode::F28,
|
|
||||||
"F29" => KeyCode::F29,
|
|
||||||
"F30" => KeyCode::F30,
|
|
||||||
"F31" => KeyCode::F31,
|
|
||||||
"F32" => KeyCode::F32,
|
|
||||||
"F33" => KeyCode::F33,
|
|
||||||
"F34" => KeyCode::F34,
|
|
||||||
"F35" => KeyCode::F35,
|
|
||||||
_ => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,5 +31,6 @@ fn ids_send() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn custom_cursor_send() {
|
fn custom_cursor_send() {
|
||||||
|
needs_send::<winit::window::CustomCursorBuilder>();
|
||||||
needs_send::<winit::window::CustomCursor>();
|
needs_send::<winit::window::CustomCursor>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ fn window_builder_sync() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn custom_cursor_sync() {
|
fn custom_cursor_sync() {
|
||||||
|
needs_sync::<winit::window::CustomCursorBuilder>();
|
||||||
needs_sync::<winit::window::CustomCursor>();
|
needs_sync::<winit::window::CustomCursor>();
|
||||||
}
|
}
|
||||||
|
|||||||
32
winit-core/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
[package]
|
||||||
|
name = "winit-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The winit contributors"]
|
||||||
|
description = "Base types for windowing libraries"
|
||||||
|
edition = "2021"
|
||||||
|
keywords = ["windowing"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/rust-windowing/winit"
|
||||||
|
documentation = "https://docs.rs/winit"
|
||||||
|
categories = ["gui"]
|
||||||
|
rust-version = "1.70.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
std = ["alloc"]
|
||||||
|
alloc = []
|
||||||
|
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bitflags.workspace = true
|
||||||
|
cursor-icon.workspace = true
|
||||||
|
mint = { version = "0.5.6", optional = true }
|
||||||
|
serde = { workspace = true, optional = true }
|
||||||
|
smol_str.workspace = true
|
||||||
|
|
||||||
|
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||||
|
web-time.workspace = true
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cfg_aliases.workspace = true
|
||||||
3
winit-core/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# winit-core - Base types for a windowing system
|
||||||
|
|
||||||
|
TODO
|
||||||
@@ -8,7 +8,7 @@ fn main() {
|
|||||||
cfg_aliases! {
|
cfg_aliases! {
|
||||||
// Systems.
|
// Systems.
|
||||||
android_platform: { target_os = "android" },
|
android_platform: { target_os = "android" },
|
||||||
wasm_platform: { all(target_family = "wasm", not(target_os = "emscripten")) },
|
web_platform: { all(target_family = "wasm", target_os = "unknown") },
|
||||||
macos_platform: { target_os = "macos" },
|
macos_platform: { target_os = "macos" },
|
||||||
ios_platform: { target_os = "ios" },
|
ios_platform: { target_os = "ios" },
|
||||||
windows_platform: { target_os = "windows" },
|
windows_platform: { target_os = "windows" },
|
||||||
@@ -17,8 +17,8 @@ fn main() {
|
|||||||
redox: { target_os = "redox" },
|
redox: { target_os = "redox" },
|
||||||
|
|
||||||
// Native displays.
|
// Native displays.
|
||||||
x11_platform: { all(feature = "x11", free_unix, not(wasm), not(redox)) },
|
x11_platform: { all(feature = "x11", free_unix, not(redox)) },
|
||||||
wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) },
|
wayland_platform: { all(feature = "wayland", free_unix, not(redox)) },
|
||||||
orbital_platform: { redox },
|
orbital_platform: { redox },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,13 +97,16 @@
|
|||||||
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
||||||
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
||||||
//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged
|
//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged
|
||||||
//! [`window.scale_factor()`]: crate::window::Window::scale_factor
|
//! [`window.scale_factor()`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.scale_factor
|
||||||
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
|
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
|
||||||
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
|
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
|
||||||
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
|
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
|
||||||
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
|
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
|
||||||
//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
|
//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub trait Pixel: Copy + Into<f64> {
|
pub trait Pixel: Copy + Into<f64> {
|
||||||
fn from_f64(f: f64) -> Self;
|
fn from_f64(f: f64) -> Self;
|
||||||
fn cast<P: Pixel>(self) -> P {
|
fn cast<P: Pixel>(self) -> P {
|
||||||
54
winit-core/src/error.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
//! Common error types.
|
||||||
|
|
||||||
|
use std::{error, fmt};
|
||||||
|
|
||||||
|
/// The error type for when the requested operation is not supported by the backend.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NotSupportedError {
|
||||||
|
_marker: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NotSupportedError {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NotSupportedError {
|
||||||
|
/// Create a new [`NotSupportedError`].
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> NotSupportedError {
|
||||||
|
NotSupportedError { _marker: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for NotSupportedError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
|
f.debug_struct("NotSupportedError").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for NotSupportedError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
|
f.pad("the requested operation is not supported by Winit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for NotSupportedError {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#![allow(clippy::redundant_clone)]
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Eat attributes for testing
|
||||||
|
#[test]
|
||||||
|
fn ensure_fmt_does_not_panic() {
|
||||||
|
let _ = format!(
|
||||||
|
"{:?}, {}",
|
||||||
|
NotSupportedError::new(),
|
||||||
|
NotSupportedError::new().clone()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,62 +1,64 @@
|
|||||||
//! The [`Event`] enum and assorted supporting types.
|
//! Incoming notifications from the GUI system.
|
||||||
//!
|
|
||||||
//! These are sent to the closure given to [`EventLoop::run(...)`], where they get
|
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||||
//! processed and used to modify the program state. For more details, see the root-level documentation.
|
use crate::event_loop::AsyncRequestSerial;
|
||||||
//!
|
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
|
||||||
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
|
use crate::window::{ActivationToken, Theme, WindowId};
|
||||||
//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this:
|
|
||||||
//!
|
use std::fmt;
|
||||||
//! ```rust,ignore
|
|
||||||
//! let mut start_cause = StartCause::Init;
|
|
||||||
//!
|
|
||||||
//! while !elwt.exiting() {
|
|
||||||
//! event_handler(NewEvents(start_cause), elwt);
|
|
||||||
//!
|
|
||||||
//! for e in (window events, user events, device events) {
|
|
||||||
//! event_handler(e, elwt);
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! for w in (redraw windows) {
|
|
||||||
//! event_handler(RedrawRequested(w), elwt);
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! event_handler(AboutToWait, elwt);
|
|
||||||
//! start_cause = wait_if_necessary();
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! event_handler(LoopExiting, elwt);
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
|
|
||||||
//! describes what happens in what order.
|
|
||||||
//!
|
|
||||||
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
|
|
||||||
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Mutex, Weak};
|
use std::sync::{Mutex, Weak};
|
||||||
#[cfg(not(wasm_platform))]
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use smol_str::SmolStr;
|
#[cfg(not(web_platform))]
|
||||||
#[cfg(wasm_platform)]
|
use std::time::Instant;
|
||||||
|
#[cfg(web_platform)]
|
||||||
use web_time::Instant;
|
use web_time::Instant;
|
||||||
|
|
||||||
use crate::error::ExternalError;
|
#[cfg(feature = "serde")]
|
||||||
#[cfg(doc)]
|
pub use serde::{Deserialize, Serialize};
|
||||||
use crate::window::Window;
|
|
||||||
use crate::{
|
use smol_str::SmolStr;
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
|
||||||
event_loop::AsyncRequestSerial,
|
/// Identifier of an input device.
|
||||||
keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState},
|
///
|
||||||
platform_impl,
|
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
|
||||||
window::{ActivationToken, Theme, WindowId},
|
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
|
||||||
};
|
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct DeviceId(u64);
|
||||||
|
|
||||||
|
impl DeviceId {
|
||||||
|
/// Returns a dummy id, useful for unit testing.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The only guarantee made about the return value of this function is that
|
||||||
|
/// it will always be equal to itself and to future values returned by this function.
|
||||||
|
/// No other guarantees are made. This may be equal to a real `DeviceId`.
|
||||||
|
///
|
||||||
|
/// **Passing this into a winit function will result in undefined behavior.**
|
||||||
|
pub const unsafe fn dummy() -> Self {
|
||||||
|
DeviceId(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for DeviceId {
|
||||||
|
fn from(value: u64) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DeviceId> for u64 {
|
||||||
|
fn from(value: DeviceId) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Describes a generic event.
|
/// Describes a generic event.
|
||||||
///
|
///
|
||||||
/// See the module-level docs for more information on the event loop manages each event.
|
/// See the module-level docs for more information on the event loop manages each event.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Event<T: 'static> {
|
pub enum Event<T: 'static, KeyExtra> {
|
||||||
/// Emitted when new events arrive from the OS to be processed.
|
/// Emitted when new events arrive from the OS to be processed.
|
||||||
///
|
///
|
||||||
/// This event type is useful as a place to put code that should be done before you start
|
/// This event type is useful as a place to put code that should be done before you start
|
||||||
@@ -68,7 +70,7 @@ pub enum Event<T: 'static> {
|
|||||||
/// Emitted when the OS sends an event to a winit window.
|
/// Emitted when the OS sends an event to a winit window.
|
||||||
WindowEvent {
|
WindowEvent {
|
||||||
window_id: WindowId,
|
window_id: WindowId,
|
||||||
event: WindowEvent,
|
event: WindowEvent<KeyExtra>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Emitted when the OS sends an event to a device.
|
/// Emitted when the OS sends an event to a device.
|
||||||
@@ -77,7 +79,7 @@ pub enum Event<T: 'static> {
|
|||||||
event: DeviceEvent,
|
event: DeviceEvent,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event)
|
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](https://docs.rs/winit/latest/winit/event_loop/struct.EventLoopProxy.html#method.send_event)
|
||||||
UserEvent(T),
|
UserEvent(T),
|
||||||
|
|
||||||
/// Emitted when the application has been suspended.
|
/// Emitted when the application has been suspended.
|
||||||
@@ -253,9 +255,9 @@ pub enum Event<T: 'static> {
|
|||||||
MemoryWarning,
|
MemoryWarning,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Event<T> {
|
impl<T, Extra> Event<T, Extra> {
|
||||||
#[allow(clippy::result_large_err)]
|
#[allow(clippy::result_large_err)]
|
||||||
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
|
pub fn map_nonuser_event<U>(self) -> Result<Event<U, Extra>, Event<T, Extra>> {
|
||||||
use self::Event::*;
|
use self::Event::*;
|
||||||
match self {
|
match self {
|
||||||
UserEvent(_) => Err(self),
|
UserEvent(_) => Err(self),
|
||||||
@@ -302,8 +304,10 @@ pub enum StartCause {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Describes an event from a [`Window`].
|
/// Describes an event from a [`Window`].
|
||||||
|
///
|
||||||
|
/// [`Window`]: https://docs.rs/winit/latest/winit/window/struct.Window.html
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum WindowEvent {
|
pub enum WindowEvent<KeyExtra> {
|
||||||
/// The activation token was delivered back and now could be used.
|
/// The activation token was delivered back and now could be used.
|
||||||
///
|
///
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -365,7 +369,7 @@ pub enum WindowEvent {
|
|||||||
/// events which are not marked as `is_synthetic`.
|
/// events which are not marked as `is_synthetic`.
|
||||||
KeyboardInput {
|
KeyboardInput {
|
||||||
device_id: DeviceId,
|
device_id: DeviceId,
|
||||||
event: KeyEvent,
|
event: KeyEvent<KeyExtra>,
|
||||||
|
|
||||||
/// If `true`, the event was generated synthetically by winit
|
/// If `true`, the event was generated synthetically by winit
|
||||||
/// in one of the following circumstances:
|
/// in one of the following circumstances:
|
||||||
@@ -389,6 +393,8 @@ pub enum WindowEvent {
|
|||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
/// - **iOS / Android / Web / Orbital:** Unsupported.
|
/// - **iOS / Android / Web / Orbital:** Unsupported.
|
||||||
|
///
|
||||||
|
/// [`Window::set_ime_allowed`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_allowed
|
||||||
Ime(Ime),
|
Ime(Ime),
|
||||||
|
|
||||||
/// The cursor has moved on the window.
|
/// The cursor has moved on the window.
|
||||||
@@ -445,21 +451,23 @@ pub enum WindowEvent {
|
|||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Touchpad magnification event with two-finger pinch gesture.
|
/// Two-finger pinch gesture, often used for magnification.
|
||||||
///
|
|
||||||
/// Positive delta values indicate magnification (zooming in) and
|
|
||||||
/// negative delta values indicate shrinking (zooming out).
|
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
/// - Only available on **macOS**.
|
/// - Only available on **macOS** and **iOS**.
|
||||||
TouchpadMagnify {
|
/// - On iOS, not recognized by default. It must be enabled when needed.
|
||||||
|
PinchGesture {
|
||||||
device_id: DeviceId,
|
device_id: DeviceId,
|
||||||
|
/// Positive values indicate magnification (zooming in) and negative
|
||||||
|
/// values indicate shrinking (zooming out).
|
||||||
|
///
|
||||||
|
/// This value may be NaN.
|
||||||
delta: f64,
|
delta: f64,
|
||||||
phase: TouchPhase,
|
phase: TouchPhase,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Smart magnification event.
|
/// Double tap gesture.
|
||||||
///
|
///
|
||||||
/// On a Mac, smart magnification is triggered by a double tap with two fingers
|
/// On a Mac, smart magnification is triggered by a double tap with two fingers
|
||||||
/// on the trackpad and is commonly used to zoom on a certain object
|
/// on the trackpad and is commonly used to zoom on a certain object
|
||||||
@@ -475,18 +483,20 @@ pub enum WindowEvent {
|
|||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
/// - Only available on **macOS 10.8** and later.
|
/// - Only available on **macOS 10.8** and later, and **iOS**.
|
||||||
SmartMagnify { device_id: DeviceId },
|
/// - On iOS, not recognized by default. It must be enabled when needed.
|
||||||
|
DoubleTapGesture { device_id: DeviceId },
|
||||||
|
|
||||||
/// Touchpad rotation event with two-finger rotation gesture.
|
/// Two-finger rotation gesture.
|
||||||
///
|
///
|
||||||
/// Positive delta values indicate rotation counterclockwise and
|
/// Positive delta values indicate rotation counterclockwise and
|
||||||
/// negative delta values indicate rotation clockwise.
|
/// negative delta values indicate rotation clockwise.
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
/// - Only available on **macOS**.
|
/// - Only available on **macOS** and **iOS**.
|
||||||
TouchpadRotate {
|
/// - On iOS, not recognized by default. It must be enabled when needed.
|
||||||
|
RotationGesture {
|
||||||
device_id: DeviceId,
|
device_id: DeviceId,
|
||||||
delta: f32,
|
delta: f32,
|
||||||
phase: TouchPhase,
|
phase: TouchPhase,
|
||||||
@@ -574,7 +584,7 @@ pub enum WindowEvent {
|
|||||||
/// ### Others
|
/// ### Others
|
||||||
///
|
///
|
||||||
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
||||||
/// - **Android / Windows / Orbital:** Unsupported.
|
/// - **Android / Wayland / Windows / Orbital:** Unsupported.
|
||||||
///
|
///
|
||||||
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||||
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||||
@@ -590,33 +600,11 @@ pub enum WindowEvent {
|
|||||||
///
|
///
|
||||||
/// Winit will aggregate duplicate redraw requests into a single event, to
|
/// Winit will aggregate duplicate redraw requests into a single event, to
|
||||||
/// help avoid duplicating rendering work.
|
/// help avoid duplicating rendering work.
|
||||||
|
///
|
||||||
|
/// [`Window::request_redraw`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.request_redraw
|
||||||
RedrawRequested,
|
RedrawRequested,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identifier of an input device.
|
|
||||||
///
|
|
||||||
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
|
|
||||||
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
|
|
||||||
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
/// Returns a dummy id, useful for unit testing.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The only guarantee made about the return value of this function is that
|
|
||||||
/// it will always be equal to itself and to future values returned by this function.
|
|
||||||
/// No other guarantees are made. This may be equal to a real `DeviceId`.
|
|
||||||
///
|
|
||||||
/// **Passing this into a winit function will result in undefined behavior.**
|
|
||||||
pub const unsafe fn dummy() -> Self {
|
|
||||||
#[allow(unused_unsafe)]
|
|
||||||
DeviceId(unsafe { platform_impl::DeviceId::dummy() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents raw hardware events that are not associated with any particular window.
|
/// Represents raw hardware events that are not associated with any particular window.
|
||||||
///
|
///
|
||||||
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
|
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
|
||||||
@@ -677,7 +665,7 @@ pub struct RawKeyEvent {
|
|||||||
|
|
||||||
/// Describes a keyboard input targeting a window.
|
/// Describes a keyboard input targeting a window.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct KeyEvent {
|
pub struct KeyEvent<Extra> {
|
||||||
/// Represents the position of a key independent of the currently active layout.
|
/// Represents the position of a key independent of the currently active layout.
|
||||||
///
|
///
|
||||||
/// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode).
|
/// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode).
|
||||||
@@ -727,7 +715,7 @@ pub struct KeyEvent {
|
|||||||
/// - **Web:** Dead keys might be reported as the real key instead
|
/// - **Web:** Dead keys might be reported as the real key instead
|
||||||
/// of `Dead` depending on the browser/OS.
|
/// of `Dead` depending on the browser/OS.
|
||||||
///
|
///
|
||||||
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
|
/// [`key_without_modifiers`]: https://docs.rs/winit/latest/winit/platform/modifier_supplement/trait.KeyEventExtModifierSupplement.html#tymethod.key_without_modifiers
|
||||||
pub logical_key: keyboard::Key,
|
pub logical_key: keyboard::Key,
|
||||||
|
|
||||||
/// Contains the text produced by this keypress.
|
/// Contains the text produced by this keypress.
|
||||||
@@ -784,7 +772,7 @@ pub struct KeyEvent {
|
|||||||
/// modifiers applied.
|
/// modifiers applied.
|
||||||
///
|
///
|
||||||
/// On Android, iOS, Redox and Web, this type is a no-op.
|
/// On Android, iOS, Redox and Web, this type is a no-op.
|
||||||
pub(crate) platform_specific: platform_impl::KeyEventExtra,
|
pub extra: Extra,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes keyboard modifiers event.
|
/// Describes keyboard modifiers event.
|
||||||
@@ -799,6 +787,15 @@ pub struct Modifiers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Modifiers {
|
impl Modifiers {
|
||||||
|
/// Only `winit` should instantiate this!
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn new(state: ModifiersState, pressed_mods: ModifiersKeys) -> Self {
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
pressed_mods,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The state of the modifiers.
|
/// The state of the modifiers.
|
||||||
pub fn state(&self) -> ModifiersState {
|
pub fn state(&self) -> ModifiersState {
|
||||||
self.state
|
self.state
|
||||||
@@ -898,6 +895,8 @@ impl From<ModifiersState> for Modifiers {
|
|||||||
/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
|
/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
|
||||||
/// Ime::Commit("啊不")
|
/// Ime::Commit("啊不")
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`Window::set_ime_cursor_area`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_cursor_area
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum Ime {
|
pub enum Ime {
|
||||||
@@ -906,6 +905,8 @@ pub enum Ime {
|
|||||||
/// After getting this event you could receive [`Preedit`](Self::Preedit) and
|
/// After getting this event you could receive [`Preedit`](Self::Preedit) and
|
||||||
/// [`Commit`](Self::Commit) events. You should also start performing IME related requests
|
/// [`Commit`](Self::Commit) events. You should also start performing IME related requests
|
||||||
/// like [`Window::set_ime_cursor_area`].
|
/// like [`Window::set_ime_cursor_area`].
|
||||||
|
///
|
||||||
|
/// [`Window::set_ime_cursor_area`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_cursor_area
|
||||||
Enabled,
|
Enabled,
|
||||||
|
|
||||||
/// Notifies when a new composing text should be set at the cursor position.
|
/// Notifies when a new composing text should be set at the cursor position.
|
||||||
@@ -928,6 +929,8 @@ pub enum Ime {
|
|||||||
/// [`Commit`](Self::Commit) events until the next [`Enabled`](Self::Enabled) event. You should
|
/// [`Commit`](Self::Commit) events until the next [`Enabled`](Self::Enabled) event. You should
|
||||||
/// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear pending
|
/// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear pending
|
||||||
/// preedit text.
|
/// preedit text.
|
||||||
|
///
|
||||||
|
/// [`Window::set_ime_cursor_area`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_cursor_area
|
||||||
Disabled,
|
Disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1116,7 +1119,7 @@ pub struct InnerSizeWriter {
|
|||||||
|
|
||||||
impl InnerSizeWriter {
|
impl InnerSizeWriter {
|
||||||
#[cfg(not(orbital_platform))]
|
#[cfg(not(orbital_platform))]
|
||||||
pub(crate) fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
|
pub fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
|
||||||
Self { new_inner_size }
|
Self { new_inner_size }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1124,14 +1127,19 @@ impl InnerSizeWriter {
|
|||||||
pub fn request_inner_size(
|
pub fn request_inner_size(
|
||||||
&mut self,
|
&mut self,
|
||||||
new_inner_size: PhysicalSize<u32>,
|
new_inner_size: PhysicalSize<u32>,
|
||||||
) -> Result<(), ExternalError> {
|
) -> Result<(), InnerSizeIgnored> {
|
||||||
if let Some(inner) = self.new_inner_size.upgrade() {
|
if let Some(inner) = self.new_inner_size.upgrade() {
|
||||||
*inner.lock().unwrap() = new_inner_size;
|
*inner.lock().unwrap() = new_inner_size;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ExternalError::Ignored)
|
Err(InnerSizeIgnored { _private: () })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the underlying size.
|
||||||
|
pub fn get(&self) -> PhysicalSize<u32> {
|
||||||
|
*self.new_inner_size.upgrade().unwrap().lock().unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for InnerSizeWriter {
|
impl PartialEq for InnerSizeWriter {
|
||||||
@@ -1140,6 +1148,25 @@ impl PartialEq for InnerSizeWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Could not write the inner size.
|
||||||
|
pub struct InnerSizeIgnored {
|
||||||
|
_private: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for InnerSizeIgnored {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("InnerSizeIgnored").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for InnerSizeIgnored {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("tried to write inner size after")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for InnerSizeIgnored {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::event;
|
use crate::event;
|
||||||
@@ -1199,13 +1226,13 @@ mod tests {
|
|||||||
state: event::ElementState::Pressed,
|
state: event::ElementState::Pressed,
|
||||||
button: event::MouseButton::Other(0),
|
button: event::MouseButton::Other(0),
|
||||||
});
|
});
|
||||||
with_window_event(TouchpadMagnify {
|
with_window_event(PinchGesture {
|
||||||
device_id: did,
|
device_id: did,
|
||||||
delta: 0.0,
|
delta: 0.0,
|
||||||
phase: event::TouchPhase::Started,
|
phase: event::TouchPhase::Started,
|
||||||
});
|
});
|
||||||
with_window_event(SmartMagnify { device_id: did });
|
with_window_event(DoubleTapGesture { device_id: did });
|
||||||
with_window_event(TouchpadRotate {
|
with_window_event(RotationGesture {
|
||||||
device_id: did,
|
device_id: did,
|
||||||
delta: 0.0,
|
delta: 0.0,
|
||||||
phase: event::TouchPhase::Started,
|
phase: event::TouchPhase::Started,
|
||||||
@@ -1265,7 +1292,7 @@ mod tests {
|
|||||||
#[allow(clippy::redundant_clone)]
|
#[allow(clippy::redundant_clone)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_event_clone() {
|
fn test_event_clone() {
|
||||||
foreach_event!(|event: event::Event<()>| {
|
foreach_event!(|event: event::Event<(), ()>| {
|
||||||
let event2 = event.clone();
|
let event2 = event.clone();
|
||||||
assert_eq!(event, event2);
|
assert_eq!(event, event2);
|
||||||
})
|
})
|
||||||
@@ -1273,7 +1300,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_map_nonuser_event() {
|
fn test_map_nonuser_event() {
|
||||||
foreach_event!(|event: event::Event<()>| {
|
foreach_event!(|event: event::Event<(), ()>| {
|
||||||
let is_user = matches!(event, event::Event::UserEvent(()));
|
let is_user = matches!(event, event::Event::UserEvent(()));
|
||||||
let event2 = event.map_nonuser_event::<()>();
|
let event2 = event.map_nonuser_event::<()>();
|
||||||
if is_user {
|
if is_user {
|
||||||
@@ -1307,7 +1334,7 @@ mod tests {
|
|||||||
#[allow(clippy::clone_on_copy)]
|
#[allow(clippy::clone_on_copy)]
|
||||||
#[test]
|
#[test]
|
||||||
fn ensure_attrs_do_not_panic() {
|
fn ensure_attrs_do_not_panic() {
|
||||||
foreach_event!(|event: event::Event<()>| {
|
foreach_event!(|event: event::Event<(), ()>| {
|
||||||
let _ = format!("{:?}", event);
|
let _ = format!("{:?}", event);
|
||||||
});
|
});
|
||||||
let _ = event::StartCause::Init.clone();
|
let _ = event::StartCause::Init.clone();
|
||||||
80
winit-core/src/event_loop.rs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
//! Types needed to define the event loop.
|
||||||
|
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
#[cfg(not(web_platform))]
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
#[cfg(web_platform)]
|
||||||
|
use web_time::{Duration, Instant};
|
||||||
|
|
||||||
|
/// A unique identifier of the winit's async request.
|
||||||
|
///
|
||||||
|
/// This could be used to identify the async request once it's done
|
||||||
|
/// and a specific action must be taken.
|
||||||
|
///
|
||||||
|
/// One of the handling scenarious could be to maintain a working list
|
||||||
|
/// containing [`AsyncRequestSerial`] and some closure associated with it.
|
||||||
|
/// Then once event is arriving the working list is being traversed and a job
|
||||||
|
/// executed and removed from the list.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct AsyncRequestSerial {
|
||||||
|
serial: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncRequestSerial {
|
||||||
|
/// Get the next serial in the sequence.
|
||||||
|
pub fn get() -> Self {
|
||||||
|
static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0);
|
||||||
|
// NOTE: we rely on wrap around here, while the user may just request
|
||||||
|
// in the loop u64::MAX times that's issue is considered on them.
|
||||||
|
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
|
||||||
|
Self { serial }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set through [`EventLoopWindowTarget::set_control_flow()`].
|
||||||
|
///
|
||||||
|
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
|
||||||
|
///
|
||||||
|
/// Defaults to [`Wait`].
|
||||||
|
///
|
||||||
|
/// [`Wait`]: Self::Wait
|
||||||
|
///
|
||||||
|
/// [`EventLoopWindowTarget::set_control_flow()`]: https://docs.rs/winit/latest/winit/event_loop/struct.EventLoopWindowTarget.html#method.set_control_flow
|
||||||
|
/// [`Event::AboutToWait`]: crate::event::Event::AboutToWait
|
||||||
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub enum ControlFlow {
|
||||||
|
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
||||||
|
/// whether or not new events are available to process.
|
||||||
|
Poll,
|
||||||
|
|
||||||
|
/// When the current loop iteration finishes, suspend the thread until another event arrives.
|
||||||
|
#[default]
|
||||||
|
Wait,
|
||||||
|
|
||||||
|
/// When the current loop iteration finishes, suspend the thread until either another event
|
||||||
|
/// arrives or the given time is reached.
|
||||||
|
///
|
||||||
|
/// Useful for implementing efficient timers. Applications which want to render at the display's
|
||||||
|
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API
|
||||||
|
/// to reduce odds of missed frames.
|
||||||
|
///
|
||||||
|
/// [`Poll`]: Self::Poll
|
||||||
|
WaitUntil(Instant),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlFlow {
|
||||||
|
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
|
||||||
|
///
|
||||||
|
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
|
||||||
|
/// instead set to [`Wait`].
|
||||||
|
///
|
||||||
|
/// [`WaitUntil`]: Self::WaitUntil
|
||||||
|
/// [`Wait`]: Self::Wait
|
||||||
|
pub fn wait_duration(timeout: Duration) -> Self {
|
||||||
|
match Instant::now().checked_add(timeout) {
|
||||||
|
Some(instant) => Self::WaitUntil(instant),
|
||||||
|
None => Self::Wait,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,9 @@
|
|||||||
//
|
//
|
||||||
// --------- END OF W3C SHORT NOTICE ---------------------------------------------------------------
|
// --------- END OF W3C SHORT NOTICE ---------------------------------------------------------------
|
||||||
|
|
||||||
|
use bitflags::bitflags;
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
pub use smol_str::SmolStr;
|
pub use smol_str::SmolStr;
|
||||||
|
|
||||||
/// Contains the platform-native physical key identifier
|
/// Contains the platform-native physical key identifier
|
||||||
@@ -1451,6 +1454,29 @@ pub enum NamedKey {
|
|||||||
F35,
|
F35,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: the exact modifier key is not used to represent modifiers state in the
|
||||||
|
// first place due to a fact that modifiers state could be changed without any
|
||||||
|
// key being pressed and on some platforms like Wayland/X11 which key resulted
|
||||||
|
// in modifiers change is hidden, also, not that it really matters.
|
||||||
|
//
|
||||||
|
// The reason this API is even exposed is mostly to provide a way for users
|
||||||
|
// to treat modifiers differently based on their position, which is required
|
||||||
|
// on macOS due to their AltGr/Option situation.
|
||||||
|
bitflags! {
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ModifiersKeys: u8 {
|
||||||
|
const LSHIFT = 0b0000_0001;
|
||||||
|
const RSHIFT = 0b0000_0010;
|
||||||
|
const LCONTROL = 0b0000_0100;
|
||||||
|
const RCONTROL = 0b0000_1000;
|
||||||
|
const LALT = 0b0001_0000;
|
||||||
|
const RALT = 0b0010_0000;
|
||||||
|
const LSUPER = 0b0100_0000;
|
||||||
|
const RSUPER = 0b1000_0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Key represents the meaning of a keypress.
|
/// Key represents the meaning of a keypress.
|
||||||
///
|
///
|
||||||
/// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
|
/// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
|
||||||
@@ -1562,7 +1588,7 @@ impl NamedKey {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use winit::keyboard::NamedKey;
|
/// use winit_core::keyboard::NamedKey;
|
||||||
///
|
///
|
||||||
/// assert_eq!(NamedKey::Enter.to_text(), Some("\r"));
|
/// assert_eq!(NamedKey::Enter.to_text(), Some("\r"));
|
||||||
/// assert_eq!(NamedKey::F20.to_text(), None);
|
/// assert_eq!(NamedKey::F20.to_text(), None);
|
||||||
@@ -1585,7 +1611,7 @@ impl Key {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use winit::keyboard::{NamedKey, Key};
|
/// use winit_core::keyboard::{NamedKey, Key};
|
||||||
///
|
///
|
||||||
/// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
|
/// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
|
||||||
/// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
|
/// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
|
||||||
@@ -1724,28 +1750,6 @@ pub enum ModifiersKeyState {
|
|||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: the exact modifier key is not used to represent modifiers state in the
|
|
||||||
// first place due to a fact that modifiers state could be changed without any
|
|
||||||
// key being pressed and on some platforms like Wayland/X11 which key resulted
|
|
||||||
// in modifiers change is hidden, also, not that it really matters.
|
|
||||||
//
|
|
||||||
// The reason this API is even exposed is mostly to provide a way for users
|
|
||||||
// to treat modifiers differently based on their position, which is required
|
|
||||||
// on macOS due to their AltGr/Option situation.
|
|
||||||
bitflags! {
|
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct ModifiersKeys: u8 {
|
|
||||||
const LSHIFT = 0b0000_0001;
|
|
||||||
const RSHIFT = 0b0000_0010;
|
|
||||||
const LCONTROL = 0b0000_0100;
|
|
||||||
const RCONTROL = 0b0000_1000;
|
|
||||||
const LALT = 0b0001_0000;
|
|
||||||
const RALT = 0b0010_0000;
|
|
||||||
const LSUPER = 0b0100_0000;
|
|
||||||
const RSUPER = 0b1000_0000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
mod modifiers_serde {
|
mod modifiers_serde {
|
||||||
use super::ModifiersState;
|
use super::ModifiersState;
|
||||||
18
winit-core/src/lib.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//! Base types for a windowing library.
|
||||||
|
//!
|
||||||
|
//! This crate contains types, traits and basic functions from [`winit`] that are platform
|
||||||
|
//! independent. It is intended to allow for other crates to build abstractions around [`winit`]
|
||||||
|
//! without needing to pull in all of [`winit`]'s dependencies, as well as to provide an
|
||||||
|
//! interface for alternative backends for [`winit`] to be constructed.
|
||||||
|
//!
|
||||||
|
//! [`winit`]: https://docs.rs/winit
|
||||||
|
|
||||||
|
#[cfg(any(not(feature = "std"), not(feature = "alloc")))]
|
||||||
|
compile_error! { "no-std and no-alloc usage are not yet supported" }
|
||||||
|
|
||||||
|
pub mod dpi;
|
||||||
|
pub mod error;
|
||||||
|
pub mod event;
|
||||||
|
pub mod event_loop;
|
||||||
|
pub mod keyboard;
|
||||||
|
pub mod window;
|
||||||
229
winit-core/src/window.rs
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
//! Types used in window construction.
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError};
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
pub use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Identifier of a window. Unique for each window.
|
||||||
|
///
|
||||||
|
/// Can be obtained with [`window.id()`](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.id).
|
||||||
|
///
|
||||||
|
/// Whenever you receive an event specific to a window, this event contains a `WindowId` which you
|
||||||
|
/// can then compare to the ids of your windows.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct WindowId(u64);
|
||||||
|
|
||||||
|
impl WindowId {
|
||||||
|
/// Returns a dummy id, useful for unit testing.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The only guarantee made about the return value of this function is that
|
||||||
|
/// it will always be equal to itself and to future values returned by this function.
|
||||||
|
/// No other guarantees are made. This may be equal to a real [`WindowId`].
|
||||||
|
///
|
||||||
|
/// **Passing this into a winit function will result in undefined behavior.**
|
||||||
|
pub const unsafe fn dummy() -> Self {
|
||||||
|
WindowId(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<WindowId> for u64 {
|
||||||
|
fn from(window_id: WindowId) -> Self {
|
||||||
|
window_id.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for WindowId {
|
||||||
|
fn from(raw_id: u64) -> Self {
|
||||||
|
Self(raw_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The behavior of cursor grabbing.
|
||||||
|
///
|
||||||
|
/// Use this enum with [`Window::set_cursor_grab`] to grab the cursor.
|
||||||
|
///
|
||||||
|
/// [`Window::set_cursor_grab`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_cursor_grab
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum CursorGrabMode {
|
||||||
|
/// No grabbing of the cursor is performed.
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// The cursor is confined to the window area.
|
||||||
|
///
|
||||||
|
/// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you
|
||||||
|
/// want to do so.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
|
||||||
|
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
|
||||||
|
///
|
||||||
|
/// [`ExternalError::NotSupported`]: https://docs.rs/winit/latest/winit/error/enum.ExternalError.html#variant.NotSupported
|
||||||
|
Confined,
|
||||||
|
|
||||||
|
/// The cursor is locked inside the window area to the certain position.
|
||||||
|
///
|
||||||
|
/// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you
|
||||||
|
/// want to do so.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
|
||||||
|
/// - **iOS / Android / Orbital:** Always returns an [`ExternalError::NotSupported`].
|
||||||
|
///
|
||||||
|
/// [`ExternalError::NotSupported`]: https://docs.rs/winit/latest/winit/error/enum.ExternalError.html#variant.NotSupported
|
||||||
|
Locked,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines the orientation that a window resize will be performed.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ResizeDirection {
|
||||||
|
East,
|
||||||
|
North,
|
||||||
|
NorthEast,
|
||||||
|
NorthWest,
|
||||||
|
South,
|
||||||
|
SouthEast,
|
||||||
|
SouthWest,
|
||||||
|
West,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ResizeDirection> for CursorIcon {
|
||||||
|
fn from(direction: ResizeDirection) -> Self {
|
||||||
|
use ResizeDirection::*;
|
||||||
|
match direction {
|
||||||
|
East => CursorIcon::EResize,
|
||||||
|
North => CursorIcon::NResize,
|
||||||
|
NorthEast => CursorIcon::NeResize,
|
||||||
|
NorthWest => CursorIcon::NwResize,
|
||||||
|
South => CursorIcon::SResize,
|
||||||
|
SouthEast => CursorIcon::SeResize,
|
||||||
|
SouthWest => CursorIcon::SwResize,
|
||||||
|
West => CursorIcon::WResize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The theme variant to use.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum Theme {
|
||||||
|
/// Use the light variant.
|
||||||
|
Light,
|
||||||
|
|
||||||
|
/// Use the dark variant.
|
||||||
|
Dark,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`].
|
||||||
|
///
|
||||||
|
/// [`Critical`]: Self::Critical
|
||||||
|
/// [`Informational`]: Self::Informational
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
pub enum UserAttentionType {
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **macOS:** Bounces the dock icon until the application is in focus.
|
||||||
|
/// - **Windows:** Flashes both the window and the taskbar button until the application is in focus.
|
||||||
|
Critical,
|
||||||
|
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **macOS:** Bounces the dock icon once.
|
||||||
|
/// - **Windows:** Flashes the taskbar button until the application is in focus.
|
||||||
|
#[default]
|
||||||
|
Informational,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags::bitflags! {
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct WindowButtons: u32 {
|
||||||
|
const CLOSE = 1 << 0;
|
||||||
|
const MINIMIZE = 1 << 1;
|
||||||
|
const MAXIMIZE = 1 << 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A window level groups windows with respect to their z-position.
|
||||||
|
///
|
||||||
|
/// The relative ordering between windows in different window levels is fixed.
|
||||||
|
/// The z-order of a window within the same window level may change dynamically on user interaction.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **iOS / Android / Web / Wayland:** Unsupported.
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum WindowLevel {
|
||||||
|
/// The window will always be below normal windows.
|
||||||
|
///
|
||||||
|
/// This is useful for a widget-based app.
|
||||||
|
AlwaysOnBottom,
|
||||||
|
|
||||||
|
/// The default.
|
||||||
|
#[default]
|
||||||
|
Normal,
|
||||||
|
|
||||||
|
/// The window will always be on top of normal windows.
|
||||||
|
AlwaysOnTop,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic IME purposes for use in [`Window::set_ime_purpose`].
|
||||||
|
///
|
||||||
|
/// The purpose may improve UX by optimizing the IME for the specific use case,
|
||||||
|
/// if winit can express the purpose to the platform and the platform reacts accordingly.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported.
|
||||||
|
///
|
||||||
|
/// [`Window::set_ime_purpose`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_purpose
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ImePurpose {
|
||||||
|
/// No special hints for the IME (default).
|
||||||
|
Normal,
|
||||||
|
/// The IME is used for password input.
|
||||||
|
Password,
|
||||||
|
/// The IME is used to input into a terminal.
|
||||||
|
///
|
||||||
|
/// For example, that could alter OSK on Wayland to show extra buttons.
|
||||||
|
Terminal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ImePurpose {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An stringly-typed token used to activate the [`Window`].
|
||||||
|
///
|
||||||
|
/// [`Window`]: https://docs.rs/winit/latest/winit/window/struct.Window.html
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct ActivationToken {
|
||||||
|
pub(crate) token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActivationToken {
|
||||||
|
/// Create a new [`ActivationToken`].
|
||||||
|
pub fn new(token: String) -> Self {
|
||||||
|
Self { token }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the underlying token.
|
||||||
|
pub fn token(&self) -> &str {
|
||||||
|
&self.token
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert into the underlying token.
|
||||||
|
pub fn into_token(self) -> String {
|
||||||
|
self.token
|
||||||
|
}
|
||||||
|
}
|
||||||
261
winit/Cargo.toml
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
[package]
|
||||||
|
name = "winit"
|
||||||
|
version = "0.29.10"
|
||||||
|
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||||
|
description = "Cross-platform window creation library."
|
||||||
|
edition = "2021"
|
||||||
|
keywords = ["windowing"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/rust-windowing/winit"
|
||||||
|
documentation = "https://docs.rs/winit"
|
||||||
|
categories = ["gui"]
|
||||||
|
rust-version = "1.70.0"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
features = [
|
||||||
|
"rwh_04",
|
||||||
|
"rwh_05",
|
||||||
|
"rwh_06",
|
||||||
|
"serde",
|
||||||
|
"mint",
|
||||||
|
# Enabled to get docs to compile
|
||||||
|
"android-native-activity",
|
||||||
|
]
|
||||||
|
default-target = "x86_64-unknown-linux-gnu"
|
||||||
|
# These are all tested in CI
|
||||||
|
targets = [
|
||||||
|
# Windows
|
||||||
|
"i686-pc-windows-msvc",
|
||||||
|
"x86_64-pc-windows-msvc",
|
||||||
|
# macOS
|
||||||
|
"x86_64-apple-darwin",
|
||||||
|
# Unix (X11 & Wayland)
|
||||||
|
"i686-unknown-linux-gnu",
|
||||||
|
"x86_64-unknown-linux-gnu",
|
||||||
|
# iOS
|
||||||
|
"x86_64-apple-ios",
|
||||||
|
# Android
|
||||||
|
"aarch64-linux-android",
|
||||||
|
# Web
|
||||||
|
"wasm32-unknown-unknown",
|
||||||
|
]
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||||
|
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
|
||||||
|
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
|
||||||
|
wayland-dlopen = ["wayland-backend/dlopen"]
|
||||||
|
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
|
||||||
|
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
|
||||||
|
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
|
||||||
|
android-native-activity = ["android-activity/native-activity"]
|
||||||
|
android-game-activity = ["android-activity/game-activity"]
|
||||||
|
mint = ["winit-core/mint"]
|
||||||
|
serde = ["dep:serde", "winit-core/serde", "cursor-icon/serde", "smol_str/serde"]
|
||||||
|
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
|
||||||
|
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
|
||||||
|
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cfg_aliases.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bitflags.workspace = true
|
||||||
|
cursor-icon.workspace = true
|
||||||
|
log = "0.4"
|
||||||
|
once_cell = "1.12"
|
||||||
|
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
|
||||||
|
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true }
|
||||||
|
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
|
||||||
|
serde = { workspace = true, optional = true }
|
||||||
|
smol_str.workspace = true
|
||||||
|
winit-core.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
image = { version = "0.24.0", default-features = false, features = ["png"] }
|
||||||
|
simple_logger = { version = "4.2.0", default_features = false }
|
||||||
|
winit = { path = ".", features = ["rwh_05"] }
|
||||||
|
|
||||||
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
|
||||||
|
softbuffer = { version = "0.3.0", default-features = false, features = ["x11", "x11-dlopen", "wayland", "wayland-dlopen"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
|
android-activity = "0.5.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.5.0"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
core-graphics = "0.23.1"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies.icrate]
|
||||||
|
version = "0.1.0"
|
||||||
|
features = [
|
||||||
|
"dispatch",
|
||||||
|
"Foundation",
|
||||||
|
"Foundation_NSArray",
|
||||||
|
"Foundation_NSAttributedString",
|
||||||
|
"Foundation_NSMutableAttributedString",
|
||||||
|
"Foundation_NSData",
|
||||||
|
"Foundation_NSDictionary",
|
||||||
|
"Foundation_NSString",
|
||||||
|
"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.1.0"
|
||||||
|
features = [
|
||||||
|
"dispatch",
|
||||||
|
"Foundation",
|
||||||
|
"Foundation_NSArray",
|
||||||
|
"Foundation_NSString",
|
||||||
|
"Foundation_NSProcessInfo",
|
||||||
|
"Foundation_NSThread",
|
||||||
|
"Foundation_NSSet",
|
||||||
|
]
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
unicode-segmentation = "1.7.1"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||||
|
version = "0.48"
|
||||||
|
features = [
|
||||||
|
"Win32_Devices_HumanInterfaceDevice",
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_Globalization",
|
||||||
|
"Win32_Graphics_Dwm",
|
||||||
|
"Win32_Graphics_Gdi",
|
||||||
|
"Win32_Media",
|
||||||
|
"Win32_System_Com_StructuredStorage",
|
||||||
|
"Win32_System_Com",
|
||||||
|
"Win32_System_LibraryLoader",
|
||||||
|
"Win32_System_Ole",
|
||||||
|
"Win32_System_SystemInformation",
|
||||||
|
"Win32_System_SystemServices",
|
||||||
|
"Win32_System_Threading",
|
||||||
|
"Win32_System_WindowsProgramming",
|
||||||
|
"Win32_UI_Accessibility",
|
||||||
|
"Win32_UI_Controls",
|
||||||
|
"Win32_UI_HiDpi",
|
||||||
|
"Win32_UI_Input_Ime",
|
||||||
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
|
"Win32_UI_Input_Pointer",
|
||||||
|
"Win32_UI_Input_Touch",
|
||||||
|
"Win32_UI_Shell",
|
||||||
|
"Win32_UI_TextServices",
|
||||||
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
]
|
||||||
|
|
||||||
|
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
|
||||||
|
ahash = { version = "0.8.3", features = ["no-rng"], optional = true }
|
||||||
|
bytemuck = { version = "1.13.1", default-features = false, optional = true }
|
||||||
|
calloop = "0.12.3"
|
||||||
|
libc = "0.2.64"
|
||||||
|
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.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.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]
|
||||||
|
orbclient = { version = "0.3.47", default-features = false }
|
||||||
|
redox_syscall = "0.4.1"
|
||||||
|
|
||||||
|
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
|
||||||
|
package = "web-sys"
|
||||||
|
version = "0.3.64"
|
||||||
|
features = [
|
||||||
|
'AbortController',
|
||||||
|
'AbortSignal',
|
||||||
|
'Blob',
|
||||||
|
'console',
|
||||||
|
'CssStyleDeclaration',
|
||||||
|
'Document',
|
||||||
|
'DomException',
|
||||||
|
'DomRect',
|
||||||
|
'DomRectReadOnly',
|
||||||
|
'Element',
|
||||||
|
'Event',
|
||||||
|
'EventTarget',
|
||||||
|
'FocusEvent',
|
||||||
|
'HtmlCanvasElement',
|
||||||
|
'HtmlElement',
|
||||||
|
'HtmlImageElement',
|
||||||
|
'ImageBitmap',
|
||||||
|
'ImageBitmapOptions',
|
||||||
|
'ImageBitmapRenderingContext',
|
||||||
|
'ImageData',
|
||||||
|
'IntersectionObserver',
|
||||||
|
'IntersectionObserverEntry',
|
||||||
|
'KeyboardEvent',
|
||||||
|
'MediaQueryList',
|
||||||
|
'MessageChannel',
|
||||||
|
'MessagePort',
|
||||||
|
'Node',
|
||||||
|
'PageTransitionEvent',
|
||||||
|
'PointerEvent',
|
||||||
|
'PremultiplyAlpha',
|
||||||
|
'ResizeObserver',
|
||||||
|
'ResizeObserverBoxOptions',
|
||||||
|
'ResizeObserverEntry',
|
||||||
|
'ResizeObserverOptions',
|
||||||
|
'ResizeObserverSize',
|
||||||
|
'VisibilityState',
|
||||||
|
'Window',
|
||||||
|
'WheelEvent',
|
||||||
|
'Url',
|
||||||
|
]
|
||||||
|
|
||||||
|
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||||
|
js-sys = "0.3.64"
|
||||||
|
pin-project = "1"
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
wasm-bindgen-futures = "0.4"
|
||||||
|
web-time.workspace = true
|
||||||
|
|
||||||
|
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
|
||||||
|
atomic-waker = "1"
|
||||||
|
concurrent-queue = { version = "2", default-features = false }
|
||||||
|
|
||||||
|
[target.'cfg(target_family = "wasm")'.dev-dependencies]
|
||||||
|
console_log = "1"
|
||||||
|
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
doc-scrape-examples = true
|
||||||
|
name = "window"
|
||||||
1
winit/README.md
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../README.md
|
||||||
24
winit/build.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
use cfg_aliases::cfg_aliases;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// The script doesn't depend on our code
|
||||||
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
|
|
||||||
|
// Setup cfg aliases
|
||||||
|
cfg_aliases! {
|
||||||
|
// Systems.
|
||||||
|
android_platform: { target_os = "android" },
|
||||||
|
web_platform: { all(target_family = "wasm", target_os = "unknown") },
|
||||||
|
macos_platform: { target_os = "macos" },
|
||||||
|
ios_platform: { target_os = "ios" },
|
||||||
|
windows_platform: { target_os = "windows" },
|
||||||
|
apple: { any(target_os = "ios", target_os = "macos") },
|
||||||
|
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
|
||||||
|
redox: { target_os = "redox" },
|
||||||
|
|
||||||
|
// Native displays.
|
||||||
|
x11_platform: { all(feature = "x11", free_unix, not(redox)) },
|
||||||
|
wayland_platform: { all(feature = "wayland", free_unix, not(redox)) },
|
||||||
|
orbital_platform: { redox },
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,16 +18,16 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||||
raw_window_handle::HasRawWindowHandle,
|
raw_window_handle::HasRawWindowHandle,
|
||||||
window::{Window, WindowBuilder, WindowId},
|
window::{Window, WindowId},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn spawn_child_window(
|
fn spawn_child_window(
|
||||||
parent: &Window,
|
parent: &Window,
|
||||||
event_loop: &EventLoopWindowTarget<()>,
|
event_loop: &EventLoopWindowTarget,
|
||||||
windows: &mut HashMap<WindowId, Window>,
|
windows: &mut HashMap<WindowId, Window>,
|
||||||
) {
|
) {
|
||||||
let parent = parent.raw_window_handle().unwrap();
|
let parent = parent.raw_window_handle().unwrap();
|
||||||
let mut builder = WindowBuilder::new()
|
let mut builder = Window::builder()
|
||||||
.with_title("child window")
|
.with_title("child window")
|
||||||
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
|
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
|
||||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
||||||
@@ -44,7 +44,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
let mut windows = HashMap::new();
|
let mut windows = HashMap::new();
|
||||||
|
|
||||||
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
|
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
|
||||||
let parent_window = WindowBuilder::new()
|
let parent_window = Window::builder()
|
||||||
.with_title("parent window")
|
.with_title("parent window")
|
||||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
||||||
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32))
|
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32))
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#![allow(clippy::single_match)]
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
#[cfg(not(wasm_platform))]
|
#[cfg(not(web_platform))]
|
||||||
use std::time;
|
use std::time;
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(web_platform)]
|
||||||
use web_time as time;
|
use web_time as time;
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
use simple_logger::SimpleLogger;
|
||||||
@@ -11,7 +11,7 @@ use winit::{
|
|||||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::{ControlFlow, EventLoop},
|
||||||
keyboard::{Key, NamedKey},
|
keyboard::{Key, NamedKey},
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -37,7 +37,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
println!("Press 'Esc' to close the window.");
|
println!("Press 'Esc' to close the window.");
|
||||||
|
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
|
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
|||||||
use winit::{
|
use winit::{
|
||||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
window::{CursorIcon, WindowBuilder},
|
window::{CursorIcon, Window},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
let window = Window::builder().build(&event_loop).unwrap();
|
||||||
window.set_title("A fantastic window!");
|
window.set_title("A fantastic window!");
|
||||||
|
|
||||||
let mut cursor_idx = 0;
|
let mut cursor_idx = 0;
|
||||||
@@ -31,7 +31,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
|
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
|
||||||
window.set_cursor_icon(CURSORS[cursor_idx]);
|
window.set_cursor(CURSORS[cursor_idx]);
|
||||||
if cursor_idx < CURSORS.len() - 1 {
|
if cursor_idx < CURSORS.len() - 1 {
|
||||||
cursor_idx += 1;
|
cursor_idx += 1;
|
||||||
} else {
|
} else {
|
||||||
@@ -5,7 +5,7 @@ use winit::{
|
|||||||
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
|
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
keyboard::{Key, ModifiersState, NamedKey},
|
keyboard::{Key, ModifiersState, NamedKey},
|
||||||
window::{CursorGrabMode, WindowBuilder},
|
window::{CursorGrabMode, Window},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -15,7 +15,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
|
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
143
winit/examples/custom_cursors.rs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
#![allow(clippy::single_match, clippy::disallowed_methods)]
|
||||||
|
|
||||||
|
#[cfg(not(web_platform))]
|
||||||
|
use simple_logger::SimpleLogger;
|
||||||
|
use winit::{
|
||||||
|
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||||
|
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||||
|
keyboard::Key,
|
||||||
|
window::{CursorIcon, CustomCursor, Window},
|
||||||
|
};
|
||||||
|
#[cfg(web_platform)]
|
||||||
|
use {
|
||||||
|
std::sync::atomic::{AtomicU64, Ordering},
|
||||||
|
std::time::Duration,
|
||||||
|
winit::platform::web::CustomCursorExtWebSys,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(web_platform)]
|
||||||
|
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
fn decode_cursor(bytes: &[u8], window_target: &EventLoopWindowTarget) -> 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(web_platform))]
|
||||||
|
#[path = "util/fill.rs"]
|
||||||
|
mod fill;
|
||||||
|
|
||||||
|
fn main() -> Result<(), impl std::error::Error> {
|
||||||
|
#[cfg(not(web_platform))]
|
||||||
|
SimpleLogger::new()
|
||||||
|
.with_level(log::LevelFilter::Info)
|
||||||
|
.init()
|
||||||
|
.unwrap();
|
||||||
|
#[cfg(web_platform)]
|
||||||
|
console_log::init_with_level(log::Level::Debug).unwrap();
|
||||||
|
|
||||||
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
let builder = Window::builder().with_title("A fantastic window!");
|
||||||
|
#[cfg(web_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),
|
||||||
|
decode_cursor(include_bytes!("data/gradient.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_cursor(custom_cursors[cursor_idx].clone());
|
||||||
|
cursor_idx = (cursor_idx + 1) % 3;
|
||||||
|
}
|
||||||
|
Key::Character("2") => {
|
||||||
|
log::debug!("Setting cursor icon to default");
|
||||||
|
window.set_cursor(CursorIcon::default());
|
||||||
|
}
|
||||||
|
Key::Character("3") => {
|
||||||
|
cursor_visible = !cursor_visible;
|
||||||
|
log::debug!("Setting cursor visibility to {:?}", cursor_visible);
|
||||||
|
window.set_cursor_visible(cursor_visible);
|
||||||
|
}
|
||||||
|
#[cfg(web_platform)]
|
||||||
|
Key::Character("4") => {
|
||||||
|
log::debug!("Setting cursor to a random image from an URL");
|
||||||
|
window.set_cursor(
|
||||||
|
CustomCursor::from_url(
|
||||||
|
format!(
|
||||||
|
"https://picsum.photos/128?random={}",
|
||||||
|
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||||
|
),
|
||||||
|
64,
|
||||||
|
64,
|
||||||
|
)
|
||||||
|
.build(_elwt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[cfg(web_platform)]
|
||||||
|
Key::Character("5") => {
|
||||||
|
log::debug!("Setting cursor to an animation");
|
||||||
|
window.set_cursor(
|
||||||
|
CustomCursor::from_animation(
|
||||||
|
Duration::from_secs(3),
|
||||||
|
vec![
|
||||||
|
custom_cursors[0].clone(),
|
||||||
|
custom_cursors[1].clone(),
|
||||||
|
CustomCursor::from_url(
|
||||||
|
format!(
|
||||||
|
"https://picsum.photos/128?random={}",
|
||||||
|
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||||
|
),
|
||||||
|
64,
|
||||||
|
64,
|
||||||
|
)
|
||||||
|
.build(_elwt),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.build(_elwt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
WindowEvent::RedrawRequested => {
|
||||||
|
#[cfg(not(web_platform))]
|
||||||
|
fill::fill_window(&window);
|
||||||
|
}
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
#[cfg(not(web_platform))]
|
||||||
|
_elwt.exit();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
Event::AboutToWait => {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
#![allow(clippy::single_match)]
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
#[cfg(not(wasm_platform))]
|
#[cfg(not(web_platform))]
|
||||||
fn main() -> Result<(), impl std::error::Error> {
|
fn main() -> Result<(), impl std::error::Error> {
|
||||||
use simple_logger::SimpleLogger;
|
use simple_logger::SimpleLogger;
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
event_loop::EventLoopBuilder,
|
event_loop::EventLoop,
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -18,11 +18,9 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event()
|
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("A fantastic window!")
|
.with_title("A fantastic window!")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -56,7 +54,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(web_platform)]
|
||||||
fn main() {
|
fn main() {
|
||||||
panic!("This example is not supported on web.");
|
panic!("This example is not supported on web.");
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 159 B After Width: | Height: | Size: 159 B |
|
Before Width: | Height: | Size: 129 B After Width: | Height: | Size: 129 B |
BIN
winit/examples/data/gradient.png
Normal file
|
After Width: | Height: | Size: 229 B |
@@ -5,7 +5,7 @@ use winit::{
|
|||||||
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
|
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
keyboard::Key,
|
keyboard::Key,
|
||||||
window::{Window, WindowBuilder, WindowId},
|
window::{Window, WindowId},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -15,8 +15,8 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
|
let window_1 = Window::builder().build(&event_loop).unwrap();
|
||||||
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
|
let window_2 = Window::builder().build(&event_loop).unwrap();
|
||||||
|
|
||||||
let mut switched = false;
|
let mut switched = false;
|
||||||
let mut entered_id = window_2.id();
|
let mut entered_id = window_2.id();
|
||||||
@@ -3,14 +3,14 @@
|
|||||||
//! Example for focusing a window.
|
//! Example for focusing a window.
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
use simple_logger::SimpleLogger;
|
||||||
#[cfg(not(wasm_platform))]
|
#[cfg(not(web_platform))]
|
||||||
use std::time;
|
use std::time;
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(web_platform)]
|
||||||
use web_time as time;
|
use web_time as time;
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, StartCause, WindowEvent},
|
event::{Event, StartCause, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -20,7 +20,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("A fantastic window!")
|
.with_title("A fantastic window!")
|
||||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
#![allow(clippy::single_match)]
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
use simple_logger::SimpleLogger;
|
||||||
use winit::dpi::PhysicalSize;
|
use winit::dpi::LogicalSize;
|
||||||
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
|
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
|
||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::EventLoop;
|
||||||
use winit::keyboard::{Key, NamedKey};
|
use winit::keyboard::{Key, NamedKey};
|
||||||
use winit::window::{Fullscreen, WindowBuilder};
|
use winit::window::{Fullscreen, Window};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use winit::platform::macos::WindowExtMacOS;
|
use winit::platform::macos::WindowExtMacOS;
|
||||||
@@ -22,7 +22,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
let mut with_min_size = false;
|
let mut with_min_size = false;
|
||||||
let mut with_max_size = false;
|
let mut with_max_size = false;
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("Hello world!")
|
.with_title("Hello world!")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -126,7 +126,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
"i" => {
|
"i" => {
|
||||||
with_min_size = !with_min_size;
|
with_min_size = !with_min_size;
|
||||||
let min_size = if with_min_size {
|
let min_size = if with_min_size {
|
||||||
Some(PhysicalSize::new(100, 100))
|
Some(LogicalSize::new(100, 100))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -139,7 +139,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
"a" => {
|
"a" => {
|
||||||
with_max_size = !with_max_size;
|
with_max_size = !with_max_size;
|
||||||
let max_size = if with_max_size {
|
let max_size = if with_max_size {
|
||||||
Some(PhysicalSize::new(200, 200))
|
Some(LogicalSize::new(200, 200))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -5,7 +5,7 @@ use winit::{
|
|||||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
keyboard::Key,
|
keyboard::Key,
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -15,7 +15,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("Your faithful window")
|
.with_title("Your faithful window")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -7,7 +7,7 @@ use winit::{
|
|||||||
event::{ElementState, Event, Ime, WindowEvent},
|
event::{ElementState, Event, Ime, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
keyboard::NamedKey,
|
keyboard::NamedKey,
|
||||||
window::{ImePurpose, WindowBuilder},
|
window::{ImePurpose, Window},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -26,7 +26,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
|
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
|
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -8,7 +8,7 @@ use winit::{
|
|||||||
keyboard::{Key, ModifiersState},
|
keyboard::{Key, ModifiersState},
|
||||||
// WARNING: This is not available on all platforms (for example on the web).
|
// WARNING: This is not available on all platforms (for example on the web).
|
||||||
platform::modifier_supplement::KeyEventExtModifierSupplement,
|
platform::modifier_supplement::KeyEventExtModifierSupplement,
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||||
@@ -24,7 +24,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
simple_logger::SimpleLogger::new().init().unwrap();
|
simple_logger::SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_inner_size(LogicalSize::new(400.0, 200.0))
|
.with_inner_size(LogicalSize::new(400.0, 200.0))
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -3,12 +3,12 @@
|
|||||||
use simple_logger::SimpleLogger;
|
use simple_logger::SimpleLogger;
|
||||||
use winit::dpi::{PhysicalPosition, PhysicalSize};
|
use winit::dpi::{PhysicalPosition, PhysicalSize};
|
||||||
use winit::monitor::MonitorHandle;
|
use winit::monitor::MonitorHandle;
|
||||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
use winit::{event_loop::EventLoop, window::Window};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
let window = Window::builder().build(&event_loop).unwrap();
|
||||||
|
|
||||||
if let Some(mon) = window.primary_monitor() {
|
if let Some(mon) = window.primary_monitor() {
|
||||||
print_info("Primary output", mon);
|
print_info("Primary output", mon);
|
||||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
|||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("Mouse Wheel events")
|
.with_title("Mouse Wheel events")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#![allow(clippy::single_match)]
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
#[cfg(not(wasm_platform))]
|
#[cfg(not(web_platform))]
|
||||||
fn main() -> Result<(), impl std::error::Error> {
|
fn main() -> Result<(), impl std::error::Error> {
|
||||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
keyboard::{Key, ModifiersState, NamedKey},
|
keyboard::{Key, ModifiersState, NamedKey},
|
||||||
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel},
|
window::{CursorGrabMode, CursorIcon, Fullscreen, Window, WindowLevel},
|
||||||
};
|
};
|
||||||
|
|
||||||
const WINDOW_COUNT: usize = 3;
|
const WINDOW_COUNT: usize = 3;
|
||||||
@@ -20,7 +20,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
|
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
|
||||||
for _ in 0..WINDOW_COUNT {
|
for _ in 0..WINDOW_COUNT {
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_inner_size(WINDOW_SIZE)
|
.with_inner_size(WINDOW_SIZE)
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -84,7 +84,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
"1" => window.set_window_level(WindowLevel::AlwaysOnTop),
|
"1" => window.set_window_level(WindowLevel::AlwaysOnTop),
|
||||||
"2" => window.set_window_level(WindowLevel::AlwaysOnBottom),
|
"2" => window.set_window_level(WindowLevel::AlwaysOnBottom),
|
||||||
"3" => window.set_window_level(WindowLevel::Normal),
|
"3" => window.set_window_level(WindowLevel::Normal),
|
||||||
"c" => window.set_cursor_icon(match state {
|
"c" => window.set_cursor(match state {
|
||||||
true => CursorIcon::Progress,
|
true => CursorIcon::Progress,
|
||||||
false => CursorIcon::Default,
|
false => CursorIcon::Default,
|
||||||
}),
|
}),
|
||||||
@@ -96,21 +96,13 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
)),
|
)),
|
||||||
(false, _) => None,
|
(false, _) => None,
|
||||||
}),
|
}),
|
||||||
"l" if state => {
|
ch @ ("g" | "l") => {
|
||||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked)
|
let mode = match (ch, state) {
|
||||||
{
|
("l", true) => CursorGrabMode::Locked,
|
||||||
println!("error: {err}");
|
("g", true) => CursorGrabMode::Confined,
|
||||||
}
|
(_, _) => CursorGrabMode::None,
|
||||||
}
|
};
|
||||||
"g" if state => {
|
if let Err(err) = window.set_cursor_grab(mode) {
|
||||||
if let Err(err) =
|
|
||||||
window.set_cursor_grab(CursorGrabMode::Confined)
|
|
||||||
{
|
|
||||||
println!("error: {err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"g" | "l" if !state => {
|
|
||||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
|
|
||||||
println!("error: {err}");
|
println!("error: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,10 +115,6 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
println!("-> inner_size : {:?}", window.inner_size());
|
println!("-> inner_size : {:?}", window.inner_size());
|
||||||
println!("-> fullscreen : {:?}", window.fullscreen());
|
println!("-> fullscreen : {:?}", window.fullscreen());
|
||||||
}
|
}
|
||||||
"l" => window.set_min_inner_size(match state {
|
|
||||||
true => Some(WINDOW_SIZE),
|
|
||||||
false => None,
|
|
||||||
}),
|
|
||||||
"m" => window.set_maximized(state),
|
"m" => window.set_maximized(state),
|
||||||
"p" => window.set_outer_position({
|
"p" => window.set_outer_position({
|
||||||
let mut position = window.outer_position().unwrap();
|
let mut position = window.outer_position().unwrap();
|
||||||
@@ -140,12 +128,26 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
"s" => {
|
"s" => {
|
||||||
let _ = window.request_inner_size(match state {
|
let _ = window.request_inner_size(match state {
|
||||||
true => PhysicalSize::new(
|
true => PhysicalSize::new(
|
||||||
WINDOW_SIZE.width + 100,
|
WINDOW_SIZE.width + 50,
|
||||||
WINDOW_SIZE.height + 100,
|
WINDOW_SIZE.height + 50,
|
||||||
),
|
),
|
||||||
false => WINDOW_SIZE,
|
false => WINDOW_SIZE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
"k" => window.set_min_inner_size(match state {
|
||||||
|
true => Some(PhysicalSize::new(
|
||||||
|
WINDOW_SIZE.width - 100,
|
||||||
|
WINDOW_SIZE.height - 100,
|
||||||
|
)),
|
||||||
|
false => None,
|
||||||
|
}),
|
||||||
|
"o" => window.set_max_inner_size(match state {
|
||||||
|
true => Some(PhysicalSize::new(
|
||||||
|
WINDOW_SIZE.width + 100,
|
||||||
|
WINDOW_SIZE.height + 100,
|
||||||
|
)),
|
||||||
|
false => None,
|
||||||
|
}),
|
||||||
"w" => {
|
"w" => {
|
||||||
if let Size::Physical(size) = WINDOW_SIZE.into() {
|
if let Size::Physical(size) = WINDOW_SIZE.into() {
|
||||||
window
|
window
|
||||||
@@ -203,7 +205,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(web_platform)]
|
||||||
fn main() {
|
fn main() {
|
||||||
panic!("Example not supported on Wasm");
|
panic!("Example not supported on Web");
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
|||||||
use winit::{
|
use winit::{
|
||||||
event::{ElementState, Event, WindowEvent},
|
event::{ElementState, Event, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("A fantastic window!")
|
.with_title("A fantastic window!")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#![allow(clippy::single_match)]
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
#[cfg(not(wasm_platform))]
|
#[cfg(not(web_platform))]
|
||||||
fn main() -> Result<(), impl std::error::Error> {
|
fn main() -> Result<(), impl std::error::Error> {
|
||||||
use std::{sync::Arc, thread, time};
|
use std::{sync::Arc, thread, time};
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = {
|
let window = {
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("A fantastic window!")
|
.with_title("A fantastic window!")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -53,7 +53,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(web_platform)]
|
||||||
fn main() {
|
fn main() {
|
||||||
unimplemented!() // `Window` can't be sent between threads
|
unimplemented!() // `Window` can't be sent between threads
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ use winit::{
|
|||||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
keyboard::{KeyCode, PhysicalKey},
|
keyboard::{KeyCode, PhysicalKey},
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
|
|
||||||
let mut resizable = false;
|
let mut resizable = false;
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("Hit space to toggle resizability.")
|
.with_title("Hit space to toggle resizability.")
|
||||||
.with_inner_size(LogicalSize::new(600.0, 300.0))
|
.with_inner_size(LogicalSize::new(600.0, 300.0))
|
||||||
.with_min_inner_size(LogicalSize::new(400.0, 200.0))
|
.with_min_inner_size(LogicalSize::new(400.0, 200.0))
|
||||||
@@ -14,7 +14,7 @@ mod example {
|
|||||||
use winit::platform::startup_notify::{
|
use winit::platform::startup_notify::{
|
||||||
EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify,
|
EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify,
|
||||||
};
|
};
|
||||||
use winit::window::{Window, WindowBuilder, WindowId};
|
use winit::window::{Window, WindowId};
|
||||||
|
|
||||||
pub(super) fn main() -> Result<(), impl std::error::Error> {
|
pub(super) fn main() -> Result<(), impl std::error::Error> {
|
||||||
// Create the event loop and get the activation token.
|
// Create the event loop and get the activation token.
|
||||||
@@ -84,8 +84,7 @@ mod example {
|
|||||||
if current_token.is_some() || create_first_window {
|
if current_token.is_some() || create_first_window {
|
||||||
// Create the initial window.
|
// Create the initial window.
|
||||||
let window = {
|
let window = {
|
||||||
let mut builder =
|
let mut builder = Window::builder().with_title(format!("Window {}", counter));
|
||||||
WindowBuilder::new().with_title(format!("Window {}", counter));
|
|
||||||
|
|
||||||
if let Some(token) = current_token.take() {
|
if let Some(token) = current_token.take() {
|
||||||
println!("Creating a window with token {token:?}");
|
println!("Creating a window with token {token:?}");
|
||||||
@@ -5,7 +5,7 @@ use winit::{
|
|||||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
keyboard::Key,
|
keyboard::Key,
|
||||||
window::{Theme, WindowBuilder},
|
window::{Theme, Window},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -15,7 +15,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("A fantastic window!")
|
.with_title("A fantastic window!")
|
||||||
.with_theme(Some(Theme::Dark))
|
.with_theme(Some(Theme::Dark))
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
#![allow(clippy::single_match)]
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
#[cfg(not(wasm_platform))]
|
#[cfg(not(web_platform))]
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(web_platform)]
|
||||||
use web_time::Instant;
|
use web_time::Instant;
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
use simple_logger::SimpleLogger;
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, StartCause, WindowEvent},
|
event::{Event, StartCause, WindowEvent},
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::{ControlFlow, EventLoop},
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -20,7 +20,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("A fantastic window!")
|
.with_title("A fantastic window!")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -2,7 +2,7 @@ use simple_logger::SimpleLogger;
|
|||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -12,32 +12,44 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("Touchpad gestures")
|
.with_title("Touchpad gestures")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
{
|
||||||
|
use winit::platform::ios::WindowExtIOS;
|
||||||
|
window.recognize_doubletap_gesture(true);
|
||||||
|
window.recognize_pinch_gesture(true);
|
||||||
|
window.recognize_rotation_gesture(true);
|
||||||
|
}
|
||||||
|
|
||||||
println!("Only supported on macOS at the moment.");
|
println!("Only supported on macOS/iOS at the moment.");
|
||||||
|
|
||||||
|
let mut zoom = 0.0;
|
||||||
|
let mut rotated = 0.0;
|
||||||
|
|
||||||
event_loop.run(move |event, elwt| {
|
event_loop.run(move |event, elwt| {
|
||||||
if let Event::WindowEvent { event, .. } = event {
|
if let Event::WindowEvent { event, .. } = event {
|
||||||
match event {
|
match event {
|
||||||
WindowEvent::CloseRequested => elwt.exit(),
|
WindowEvent::CloseRequested => elwt.exit(),
|
||||||
WindowEvent::TouchpadMagnify { delta, .. } => {
|
WindowEvent::PinchGesture { delta, .. } => {
|
||||||
|
zoom += delta;
|
||||||
if delta > 0.0 {
|
if delta > 0.0 {
|
||||||
println!("Zoomed in {delta}");
|
println!("Zoomed in {delta:.5} (now: {zoom:.5})");
|
||||||
} else {
|
} else {
|
||||||
println!("Zoomed out {delta}");
|
println!("Zoomed out {delta:.5} (now: {zoom:.5})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::SmartMagnify { .. } => {
|
WindowEvent::DoubleTapGesture { .. } => {
|
||||||
println!("Smart zoom");
|
println!("Smart zoom");
|
||||||
}
|
}
|
||||||
WindowEvent::TouchpadRotate { delta, .. } => {
|
WindowEvent::RotationGesture { delta, .. } => {
|
||||||
|
rotated += delta;
|
||||||
if delta > 0.0 {
|
if delta > 0.0 {
|
||||||
println!("Rotated counterclockwise {delta}");
|
println!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})");
|
||||||
} else {
|
} else {
|
||||||
println!("Rotated clockwise {delta}");
|
println!("Rotated clockwise {delta:.5} (now: {rotated:.5})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => {
|
||||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
|||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_decorations(false)
|
.with_decorations(false)
|
||||||
.with_transparent(true)
|
.with_transparent(true)
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
116
winit/examples/util/fill.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
//! Fill the window buffer with a solid color.
|
||||||
|
//!
|
||||||
|
//! Launching a window without drawing to it has unpredictable results varying from platform to
|
||||||
|
//! platform. In order to have well-defined examples, this module provides an easy way to
|
||||||
|
//! fill the window buffer with a solid color.
|
||||||
|
//!
|
||||||
|
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
|
||||||
|
//! also be used to fill the window buffer, but they are more complicated to use.
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub use platform::cleanup_window;
|
||||||
|
pub use platform::fill_window;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
|
||||||
|
mod platform {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
use softbuffer::{Context, Surface};
|
||||||
|
use winit::window::Window;
|
||||||
|
use winit::window::WindowId;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
// NOTE: You should never do things like that, create context and drop it before
|
||||||
|
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
||||||
|
// ManuallyDrop to prevent destructors from running.
|
||||||
|
//
|
||||||
|
// A static, thread-local map of graphics contexts to open windows.
|
||||||
|
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The graphics context used to draw to a window.
|
||||||
|
struct GraphicsContext {
|
||||||
|
/// The global softbuffer context.
|
||||||
|
context: Context,
|
||||||
|
|
||||||
|
/// The hash map of window IDs to surfaces.
|
||||||
|
surfaces: HashMap<WindowId, Surface>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphicsContext {
|
||||||
|
fn new(w: &Window) -> Self {
|
||||||
|
Self {
|
||||||
|
context: unsafe { Context::new(w) }.expect("Failed to create a softbuffer context"),
|
||||||
|
surfaces: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_surface(&mut self, window: &Window) -> &mut Surface {
|
||||||
|
self.surfaces.entry(window.id()).or_insert_with(|| {
|
||||||
|
unsafe { Surface::new(&self.context, window) }
|
||||||
|
.expect("Failed to create a softbuffer surface")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy_surface(&mut self, window: &Window) {
|
||||||
|
self.surfaces.remove(&window.id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub 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
|
||||||
|
.get_or_insert_with(|| GraphicsContext::new(window))
|
||||||
|
.create_surface(window);
|
||||||
|
|
||||||
|
// Fill a buffer with a solid color.
|
||||||
|
const DARK_GRAY: u32 = 0xFF181818;
|
||||||
|
|
||||||
|
surface
|
||||||
|
.resize(width, height)
|
||||||
|
.expect("Failed to resize the softbuffer surface");
|
||||||
|
|
||||||
|
let mut buffer = surface
|
||||||
|
.buffer_mut()
|
||||||
|
.expect("Failed to get the softbuffer buffer");
|
||||||
|
buffer.fill(DARK_GRAY);
|
||||||
|
buffer
|
||||||
|
.present()
|
||||||
|
.expect("Failed to present the softbuffer buffer");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn cleanup_window(window: &Window) {
|
||||||
|
GC.with(|gc| {
|
||||||
|
let mut gc = gc.borrow_mut();
|
||||||
|
if let Some(context) = gc.as_mut() {
|
||||||
|
context.destroy_surface(window);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
|
||||||
|
mod platform {
|
||||||
|
pub fn fill_window(_window: &winit::window::Window) {
|
||||||
|
// No-op on mobile platforms.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn cleanup_window(_window: &winit::window::Window) {
|
||||||
|
// No-op on mobile platforms.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,13 +4,13 @@ use winit::{
|
|||||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
keyboard::Key,
|
keyboard::Key,
|
||||||
window::{Fullscreen, WindowBuilder},
|
window::{Fullscreen, Window},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() -> Result<(), impl std::error::Error> {
|
pub fn main() -> Result<(), impl std::error::Error> {
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let builder = WindowBuilder::new().with_title("A fantastic window!");
|
let builder = Window::builder().with_title("A fantastic window!");
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(wasm_platform)]
|
||||||
let builder = {
|
let builder = {
|
||||||
use winit::platform::web::WindowBuilderExtWebSys;
|
use winit::platform::web::WindowBuilderExtWebSys;
|
||||||
@@ -18,11 +18,11 @@ pub fn main() -> Result<(), impl std::error::Error> {
|
|||||||
};
|
};
|
||||||
let window = builder.build(&event_loop).unwrap();
|
let window = builder.build(&event_loop).unwrap();
|
||||||
|
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(web_platform)]
|
||||||
let log_list = wasm::insert_canvas_and_create_log_list(&window);
|
let log_list = wasm::insert_canvas_and_create_log_list(&window);
|
||||||
|
|
||||||
event_loop.run(move |event, elwt| {
|
event_loop.run(move |event, elwt| {
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(web_platform)]
|
||||||
wasm::log_event(&log_list, &event);
|
wasm::log_event(&log_list, &event);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
@@ -57,7 +57,7 @@ pub fn main() -> Result<(), impl std::error::Error> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(web_platform)]
|
||||||
mod wasm {
|
mod wasm {
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ pub fn main() {
|
|||||||
println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
|
println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(web_platform)]
|
||||||
mod wasm {
|
mod wasm {
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
@@ -14,7 +14,7 @@ mod wasm {
|
|||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
platform::web::WindowBuilderExtWebSys,
|
platform::web::WindowBuilderExtWebSys,
|
||||||
window::{Window, WindowBuilder},
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
const EXPLANATION: &str = "
|
const EXPLANATION: &str = "
|
||||||
@@ -33,7 +33,7 @@ This example demonstrates the desired future functionality which will possibly b
|
|||||||
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
|
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("A fantastic window!")
|
.with_title("A fantastic window!")
|
||||||
// When running in a non-wasm environment this would set the window size to 100x100.
|
// When running in a non-wasm environment this would set the window size to 100x100.
|
||||||
// However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page.
|
// However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page.
|
||||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
|||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
window::WindowBuilder,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("A fantastic window!")
|
.with_title("A fantastic window!")
|
||||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
@@ -8,7 +8,7 @@ use winit::{
|
|||||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||||
event_loop::{DeviceEvents, EventLoop},
|
event_loop::{DeviceEvents, EventLoop},
|
||||||
keyboard::Key,
|
keyboard::Key,
|
||||||
window::{WindowBuilder, WindowButtons},
|
window::{Window, WindowButtons},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("A fantastic window!")
|
.with_title("A fantastic window!")
|
||||||
.with_inner_size(LogicalSize::new(300.0, 300.0))
|
.with_inner_size(LogicalSize::new(300.0, 300.0))
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
@@ -8,7 +8,7 @@ use winit::{
|
|||||||
event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent},
|
event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent},
|
||||||
event_loop::{DeviceEvents, EventLoop},
|
event_loop::{DeviceEvents, EventLoop},
|
||||||
keyboard::{Key, KeyCode, PhysicalKey},
|
keyboard::{Key, KeyCode, PhysicalKey},
|
||||||
window::{Fullscreen, WindowBuilder},
|
window::{Fullscreen, Window},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
SimpleLogger::new().init().unwrap();
|
SimpleLogger::new().init().unwrap();
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = Window::builder()
|
||||||
.with_title("A fantastic window!")
|
.with_title("A fantastic window!")
|
||||||
.with_inner_size(LogicalSize::new(100.0, 100.0))
|
.with_inner_size(LogicalSize::new(100.0, 100.0))
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||