mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
77 Commits
madsmtm/ob
...
v0.29.1-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f1aaa652d | ||
|
|
00b5de0a68 | ||
|
|
80d1e49354 | ||
|
|
07dd45f8e3 | ||
|
|
4e6ce00ec5 | ||
|
|
65c2482d74 | ||
|
|
ba2bfd064f | ||
|
|
08ad3f19e2 | ||
|
|
e3fbfd6792 | ||
|
|
c40af0062b | ||
|
|
511bf53889 | ||
|
|
7451c4b88c | ||
|
|
42ecef7b31 | ||
|
|
5d9ce7f5f4 | ||
|
|
ef5b71d658 | ||
|
|
4ab36f336c | ||
|
|
2791cbd65e | ||
|
|
03bf83f45e | ||
|
|
02870202cb | ||
|
|
c268922def | ||
|
|
61b921c466 | ||
|
|
794d0c1f73 | ||
|
|
8ce58c7053 | ||
|
|
cff9b01052 | ||
|
|
7e9dc147d8 | ||
|
|
d7827b36d3 | ||
|
|
5b90a4e194 | ||
|
|
281077a0d8 | ||
|
|
d21395bb3f | ||
|
|
f69616ac2c | ||
|
|
645b1ff00f | ||
|
|
3925281652 | ||
|
|
3bf0fa9ec8 | ||
|
|
7de2bc7ae6 | ||
|
|
3f44eb1fd9 | ||
|
|
456c735bfe | ||
|
|
973e6ad400 | ||
|
|
07652c76fb | ||
|
|
7d93c34e42 | ||
|
|
a2e1a0ac19 | ||
|
|
f1a64b3155 | ||
|
|
f8ffa314d0 | ||
|
|
e28974bc04 | ||
|
|
93f5f1ac3c | ||
|
|
a02c680a87 | ||
|
|
6bb62d0b13 | ||
|
|
fae4cbd2aa | ||
|
|
7a954c7e08 | ||
|
|
164dce2b8a | ||
|
|
0ba4283c29 | ||
|
|
62b4ba8b50 | ||
|
|
afebe2e7d1 | ||
|
|
0efcfaf5a9 | ||
|
|
d86ce9de9f | ||
|
|
7fa7cea700 | ||
|
|
36ebad3246 | ||
|
|
912c45e9f7 | ||
|
|
e2c71a4422 | ||
|
|
b50d9a0228 | ||
|
|
692f15c49f | ||
|
|
5366694db2 | ||
|
|
7962271faa | ||
|
|
78b5f2feb8 | ||
|
|
4baab2d93e | ||
|
|
79385ecd1f | ||
|
|
8d18043a3c | ||
|
|
297c3f80eb | ||
|
|
1d80005b91 | ||
|
|
fab0f62c5a | ||
|
|
d83188befd | ||
|
|
b9d89e97ed | ||
|
|
5fa4b8f003 | ||
|
|
7a4ce631bd | ||
|
|
8d5f82f0c0 | ||
|
|
08fe32eac3 | ||
|
|
1cddc96a0b | ||
|
|
84ef89eb1c |
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust_version: ['1.64.0', stable, nightly]
|
||||
rust_version: ['1.65.0', stable, nightly]
|
||||
platform:
|
||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
shell: bash
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.64.0'
|
||||
matrix.rust_version != '1.65.0'
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
||||
|
||||
- name: Run tests
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.64.0'
|
||||
matrix.rust_version != '1.65.0'
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
||||
|
||||
- name: Lint with clippy
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
shell: bash
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.64.0'
|
||||
matrix.rust_version != '1.65.0'
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
|
||||
- name: Run tests with serde enabled
|
||||
shell: bash
|
||||
@@ -147,5 +147,5 @@ jobs:
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.64.0'
|
||||
matrix.rust_version != '1.65.0'
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
|
||||
|
||||
65
CHANGELOG.md
65
CHANGELOG.md
@@ -8,6 +8,71 @@ And please only add new entries to the top of this list, right below the `# Unre
|
||||
|
||||
# Unreleased
|
||||
|
||||
# 0.29.1-beta
|
||||
|
||||
- **Breaking:** Bump `ndk` version to `0.8.0-beta.0`, ndk-sys to `v0.5.0-beta.0`, `android-activity` to `0.5.0-beta.1`.
|
||||
- **Breaking:** Bump MSRV from `1.64` to `1.65`.
|
||||
- Make iOS windows usable from other threads.
|
||||
- Reexport `raw-window-handle` in `window` module.
|
||||
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
|
||||
- **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>`
|
||||
- On X11, set `visual_id` in returned `raw-window-handle`.
|
||||
- **Breaking:** on Wayland, dispatching user created wayland queue won't wake up the loop unless winit has event to send back.
|
||||
- Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead:
|
||||
- `platform::windows::HINSTANCE`.
|
||||
- `WindowExtWindows::hinstance`.
|
||||
- `WindowExtWindows::hwnd`.
|
||||
- `WindowExtIOS::ui_window`.
|
||||
- `WindowExtIOS::ui_view_controller`.
|
||||
- `WindowExtIOS::ui_view`.
|
||||
- `WindowExtMacOS::ns_window`.
|
||||
- `WindowExtMacOS::ns_view`.
|
||||
- `EventLoopWindowTargetExtWayland::wayland_display`.
|
||||
- `WindowExtWayland::wayland_surface`.
|
||||
- `WindowExtWayland::wayland_display`.
|
||||
- `WindowExtX11::xlib_window`.
|
||||
- `WindowExtX11::xlib_display`.
|
||||
- `WindowExtX11::xlib_screen_id`.
|
||||
- `WindowExtX11::xcb_connection`.
|
||||
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events.
|
||||
- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor.
|
||||
- Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
|
||||
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
|
||||
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
|
||||
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
|
||||
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
|
||||
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
|
||||
- On Wayland, make double clicking and moving the CSD frame more reliable.
|
||||
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
|
||||
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
|
||||
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
|
||||
- Implement `PartialOrd` and `Ord` for `Key`, `KeyCode`, `NativeKey`, and `NativeKeyCode`.
|
||||
- Add `ElementState::is_pressed`.
|
||||
- On Web, implement `WindowEvent::Occluded`.
|
||||
- On Web, fix touch location to be as accurate as mouse position.
|
||||
- On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position.
|
||||
- On Web, add Fullscreen API compatibility for Safari.
|
||||
- On Web, implement `Window::set_(min|max)_inner_size()`.
|
||||
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
|
||||
- On Web, fix some `WindowBuilder` methods doing nothing.
|
||||
- On Web, implement `Window::focus_window()`.
|
||||
- On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`.
|
||||
- On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
|
||||
- On Windows, add `drag_resize_window` method support.
|
||||
- **Breaking** `run() ->!` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
|
||||
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_ondemand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
|
||||
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
|
||||
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
|
||||
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- Added `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
|
||||
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
|
||||
- On iOS, add force data to touch events when using the Apple Pencil.
|
||||
|
||||
# 0.29.0-beta.0
|
||||
|
||||
- On Web, allow event loops to be recreated with `spawn`.
|
||||
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
|
||||
- On Android, changed default behavior of Android to ignore volume keys letting the operating system handle them.
|
||||
|
||||
@@ -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:
|
||||
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.64.
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.65.
|
||||
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
|
||||
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
|
||||
|
||||
53
Cargo.toml
53
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.28.6"
|
||||
version = "0.29.1-beta"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2021"
|
||||
@@ -10,7 +10,7 @@ readme = "README.md"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
rust-version = "1.64.0"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["serde"]
|
||||
@@ -36,7 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
x11 = ["x11-dl", "percent-encoding", "xkbcommon-dl/x11"]
|
||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
|
||||
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv", "memmap2"]
|
||||
wayland-dlopen = ["wayland-backend/dlopen"]
|
||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
|
||||
@@ -55,7 +55,7 @@ cursor-icon = "1.0.0"
|
||||
log = "0.4"
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
once_cell = "1.12"
|
||||
raw_window_handle = { package = "raw-window-handle", version = "0.5" }
|
||||
raw_window_handle = { package = "raw-window-handle", version = "0.5", features = ["std"] }
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
smol_str = "0.2.0"
|
||||
|
||||
@@ -67,18 +67,45 @@ simple_logger = { version = "2.1.0", default_features = false }
|
||||
softbuffer = "0.3.0"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
|
||||
android-activity = "0.4.0"
|
||||
ndk = "0.7.0"
|
||||
ndk-sys = "0.4.0"
|
||||
# Coordinate the next winit release android-activity 0.5 release
|
||||
android-activity = "0.5.0-beta.1"
|
||||
ndk = "0.8.0-beta.0"
|
||||
ndk-sys = "0.5.0-beta.0"
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
core-foundation = "0.9.3"
|
||||
objc2 = ">=0.3.0-beta.3, <0.3.0-beta.4" # Allow `0.3.0-beta.3.patch-leaks`
|
||||
objc2 = "0.4.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.22.3"
|
||||
dispatch = "0.2.0"
|
||||
|
||||
[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"
|
||||
@@ -113,6 +140,7 @@ features = [
|
||||
]
|
||||
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
|
||||
bytemuck = { version = "1.13.1", default-features = false, optional = true }
|
||||
libc = "0.2.64"
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
fnv = { version = "1.0.3", optional = true }
|
||||
@@ -122,7 +150,9 @@ wayland-client = { version = "0.30.0", optional = true }
|
||||
wayland-backend = { version = "0.1.0", default_features = false, features = ["client_system"], optional = true }
|
||||
wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = true }
|
||||
calloop = "0.10.5"
|
||||
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "xinput", "xkb"], optional = true }
|
||||
xkbcommon-dl = "0.4.0"
|
||||
memmap2 = { version = "0.5.0", optional = true }
|
||||
|
||||
@@ -145,6 +175,8 @@ features = [
|
||||
'FocusEvent',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'IntersectionObserver',
|
||||
'IntersectionObserverEntry',
|
||||
'KeyboardEvent',
|
||||
'MediaQueryList',
|
||||
'Node',
|
||||
@@ -155,6 +187,7 @@ features = [
|
||||
'ResizeObserverEntry',
|
||||
'ResizeObserverOptions',
|
||||
'ResizeObserverSize',
|
||||
'VisibilityState',
|
||||
'Window',
|
||||
'WheelEvent'
|
||||
]
|
||||
|
||||
20
FEATURES.md
20
FEATURES.md
@@ -13,7 +13,9 @@ be used to create both games and applications. It supports the following main gr
|
||||
- iOS
|
||||
- Android
|
||||
- Web
|
||||
- via WASM
|
||||
- Chrome
|
||||
- Firefox
|
||||
- Safari 13.1+
|
||||
|
||||
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
|
||||
aim to support every single feature of every platform, but rather to abstract over the common features
|
||||
@@ -117,6 +119,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
|
||||
## Platform
|
||||
### Windows
|
||||
* Setting the name of the internal window class
|
||||
* Setting the taskbar icon
|
||||
* Setting the parent window
|
||||
* Setting a menu bar
|
||||
@@ -143,9 +146,6 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
|
||||
### iOS
|
||||
* `winit` has a minimum OS requirement of iOS 8
|
||||
* Get the `UIWindow` object pointer
|
||||
* Get the `UIViewController` object pointer
|
||||
* Get the `UIView` object pointer
|
||||
* Get the `UIScreen` object pointer
|
||||
* Setting the `UIView` hidpi factor
|
||||
* Valid orientations
|
||||
@@ -172,7 +172,7 @@ Legend:
|
||||
- ❓: Unknown status
|
||||
|
||||
### Windowing
|
||||
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |Redox OS|
|
||||
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|
||||
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|
||||
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|
||||
@@ -192,13 +192,13 @@ Legend:
|
||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
|
||||
|
||||
### System information
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|
||||
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|
||||
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|
||||
|
||||
### Input handling
|
||||
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|
||||
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|
||||
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||
@@ -215,19 +215,19 @@ Legend:
|
||||
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|
||||
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|
||||
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|
||||
|Resize with cursor |❌ |❌ |✔️ |❌ |**N/A**|**N/A**|**N/A** |**N/A** |
|
||||
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|
||||
|
||||
### Pending API Reworks
|
||||
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|
||||
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|
||||
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|
||||
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|
||||
|
||||
### Completed API Reworks
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||
|
||||
[#165]: https://github.com/rust-windowing/winit/issues/165
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.28.6"
|
||||
winit = "0.29.1-beta"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
|
||||
4
build.rs
4
build.rs
@@ -8,12 +8,12 @@ fn main() {
|
||||
cfg_aliases! {
|
||||
// Systems.
|
||||
android_platform: { target_os = "android" },
|
||||
wasm_platform: { target_family = "wasm" },
|
||||
wasm_platform: { all(target_family = "wasm", not(target_os = "emscripten")) },
|
||||
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)) },
|
||||
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
|
||||
redox: { target_os = "redox" },
|
||||
|
||||
// Native displays.
|
||||
|
||||
@@ -4,4 +4,9 @@ disallowed-methods = [
|
||||
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
|
||||
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
|
||||
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
|
||||
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
|
||||
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
|
||||
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
|
||||
]
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
mod fill;
|
||||
|
||||
#[cfg(any(x11_platform, macos_platform, windows_platform))]
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
use winit::{
|
||||
dpi::{LogicalPosition, LogicalSize, Position},
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||
window::raw_window_handle::HasRawWindowHandle,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ fn main() {
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
|
||||
let event_loop: EventLoop<()> = EventLoop::new();
|
||||
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
|
||||
let parent_window = WindowBuilder::new()
|
||||
.with_title("parent window")
|
||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
||||
@@ -46,7 +46,7 @@ fn main() {
|
||||
|
||||
println!("parent window: {parent_window:?})");
|
||||
|
||||
event_loop.run(move |event: Event<'_, ()>, event_loop, control_flow| {
|
||||
event_loop.run(move |event: Event<()>, event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
if let Event::WindowEvent { event, window_id } = event {
|
||||
|
||||
@@ -27,7 +27,7 @@ enum Mode {
|
||||
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
|
||||
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
|
||||
println!("Press '1' to switch to Wait mode.");
|
||||
@@ -36,7 +36,7 @@ fn main() {
|
||||
println!("Press 'R' to toggle request_redraw() calls.");
|
||||
println!("Press 'Esc' to close the window.");
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
|
||||
.build(&event_loop)
|
||||
@@ -95,18 +95,11 @@ fn main() {
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
Event::MainEventsCleared => {
|
||||
Event::AboutToWait => {
|
||||
if request_redraw && !wait_cancelled && !close_requested {
|
||||
window.request_redraw();
|
||||
}
|
||||
if close_requested {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_window_id) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
Event::RedrawEventsCleared => {
|
||||
|
||||
match mode {
|
||||
Mode::Wait => control_flow.set_wait(),
|
||||
Mode::WaitUntil => {
|
||||
@@ -119,8 +112,15 @@ fn main() {
|
||||
control_flow.set_poll();
|
||||
}
|
||||
};
|
||||
|
||||
if close_requested {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_window_id) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
window.set_title("A fantastic window!");
|
||||
@@ -54,7 +54,7 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
const CURSORS: &[CursorIcon] = &[
|
||||
|
||||
@@ -11,9 +11,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
|
||||
@@ -73,5 +73,5 @@ fn main() {
|
||||
Event::RedrawRequested(_) => fill::fill_window(&window),
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
@@ -18,7 +18,9 @@ fn main() {
|
||||
}
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
|
||||
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
@@ -52,7 +54,7 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
|
||||
@@ -11,9 +11,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
@@ -69,7 +69,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::keyboard::Key;
|
||||
@@ -12,12 +13,14 @@ use winit::platform::macos::WindowExtMacOS;
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let mut decorations = true;
|
||||
let mut minimized = false;
|
||||
let mut with_min_size = false;
|
||||
let mut with_max_size = false;
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello world!")
|
||||
@@ -46,6 +49,8 @@ fn main() {
|
||||
println!("- D\tToggle window decorations");
|
||||
println!("- X\tMaximize window");
|
||||
println!("- Z\tMinimize window");
|
||||
println!("- I\tToggle mIn size limit");
|
||||
println!("- A\tToggle mAx size limit");
|
||||
|
||||
event_loop.run(move |event, elwt, control_flow| {
|
||||
control_flow.set_wait();
|
||||
@@ -120,6 +125,32 @@ fn main() {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
"i" => {
|
||||
with_min_size = !with_min_size;
|
||||
let min_size = if with_min_size {
|
||||
Some(PhysicalSize::new(100, 100))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
window.set_min_inner_size(min_size);
|
||||
eprintln!(
|
||||
"Min: {with_min_size}: {min_size:?} => {:?}",
|
||||
window.inner_size()
|
||||
);
|
||||
}
|
||||
"a" => {
|
||||
with_max_size = !with_max_size;
|
||||
let max_size = if with_max_size {
|
||||
Some(PhysicalSize::new(200, 200))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
window.set_max_inner_size(max_size);
|
||||
eprintln!(
|
||||
"Max: {with_max_size}: {max_size:?} => {:?}",
|
||||
window.inner_size()
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
@@ -131,5 +162,5 @@ fn main() {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Your faithful window")
|
||||
@@ -87,5 +87,5 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new()
|
||||
.with_level(LevelFilter::Trace)
|
||||
.init()
|
||||
@@ -24,7 +24,7 @@ fn main() {
|
||||
println!("Press F2 to toggle IME. See the documentation of `set_ime_allowed` for more info");
|
||||
println!("Press F3 to cycle through IME purposes.");
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
|
||||
@@ -105,5 +105,5 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,12 +17,12 @@ fn main() {
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
simple_logger::SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(LogicalSize::new(400.0, 200.0))
|
||||
@@ -61,5 +61,5 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use winit::{event_loop::EventLoop, window::WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
if let Some(mon) = window.primary_monitor() {
|
||||
|
||||
@@ -10,9 +10,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Mouse Wheel events")
|
||||
@@ -64,5 +64,5 @@ In other words, the deltas indicate the direction in which to move the content (
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
@@ -17,7 +17,7 @@ fn main() {
|
||||
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
|
||||
for _ in 0..WINDOW_COUNT {
|
||||
let window = WindowBuilder::new()
|
||||
@@ -137,13 +137,15 @@ fn main() {
|
||||
}),
|
||||
"q" => window.request_redraw(),
|
||||
"r" => window.set_resizable(state),
|
||||
"s" => window.set_inner_size(match state {
|
||||
true => PhysicalSize::new(
|
||||
WINDOW_SIZE.width + 100,
|
||||
WINDOW_SIZE.height + 100,
|
||||
),
|
||||
false => WINDOW_SIZE,
|
||||
}),
|
||||
"s" => {
|
||||
let _ = window.request_inner_size(match state {
|
||||
true => PhysicalSize::new(
|
||||
WINDOW_SIZE.width + 100,
|
||||
WINDOW_SIZE.height + 100,
|
||||
),
|
||||
false => WINDOW_SIZE,
|
||||
});
|
||||
}
|
||||
"w" => {
|
||||
if let Size::Physical(size) = WINDOW_SIZE.into() {
|
||||
window
|
||||
@@ -193,9 +195,7 @@ fn main() {
|
||||
}
|
||||
_ => {
|
||||
if let Some(tx) = window_senders.get(&window_id) {
|
||||
if let Some(event) = event.to_static() {
|
||||
tx.send(event).unwrap();
|
||||
}
|
||||
tx.send(event).unwrap();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -13,9 +13,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
for _ in 0..3 {
|
||||
|
||||
@@ -10,9 +10,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
@@ -41,5 +41,5 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
use std::{sync::Arc, thread, time};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
@@ -15,7 +15,7 @@ fn main() {
|
||||
mod fill;
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = {
|
||||
let window = WindowBuilder::new()
|
||||
@@ -49,7 +49,7 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
|
||||
@@ -12,9 +12,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let mut resizable = false;
|
||||
|
||||
@@ -53,5 +53,5 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
126
examples/startup_notification.rs
Normal file
126
examples/startup_notification.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
//! Demonstrates the use of startup notifications on Linux.
|
||||
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
#[path = "./util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
mod example {
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::keyboard::Key;
|
||||
use winit::platform::startup_notify::{
|
||||
EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify,
|
||||
};
|
||||
use winit::window::{Window, WindowBuilder, WindowId};
|
||||
|
||||
pub(super) fn main() -> Result<(), impl std::error::Error> {
|
||||
// Create the event loop and get the activation token.
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let mut current_token = match event_loop.read_token_from_env() {
|
||||
Some(token) => Some(token),
|
||||
None => {
|
||||
println!("No startup notification token found in environment.");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let mut windows: HashMap<WindowId, Rc<Window>> = HashMap::new();
|
||||
let mut counter = 0;
|
||||
let mut create_first_window = false;
|
||||
|
||||
event_loop.run(move |event, elwt, flow| {
|
||||
match event {
|
||||
Event::Resumed => create_first_window = true,
|
||||
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
} => {
|
||||
if logical_key == Key::Character("n".into()) {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
// Request a new activation token on this window.
|
||||
// Once we get it we will use it to create a window.
|
||||
window
|
||||
.request_activation_token()
|
||||
.expect("Failed to request activation token.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::CloseRequested,
|
||||
} => {
|
||||
// Remove the window from the map.
|
||||
windows.remove(&window_id);
|
||||
if windows.is_empty() {
|
||||
flow.set_exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::ActivationTokenDone { token, .. },
|
||||
..
|
||||
} => {
|
||||
current_token = Some(token);
|
||||
}
|
||||
|
||||
Event::RedrawRequested(id) => {
|
||||
if let Some(window) = windows.get(&id) {
|
||||
super::fill::fill_window(window);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// See if we've passed the deadline.
|
||||
if current_token.is_some() || create_first_window {
|
||||
// Create the initial window.
|
||||
let window = {
|
||||
let mut builder =
|
||||
WindowBuilder::new().with_title(format!("Window {}", counter));
|
||||
|
||||
if let Some(token) = current_token.take() {
|
||||
println!("Creating a window with token {token:?}");
|
||||
builder = builder.with_activation_token(token);
|
||||
}
|
||||
|
||||
Rc::new(builder.build(elwt).unwrap())
|
||||
};
|
||||
|
||||
// Add the window to the map.
|
||||
windows.insert(window.id(), window.clone());
|
||||
|
||||
counter += 1;
|
||||
create_first_window = false;
|
||||
}
|
||||
|
||||
flow.set_wait();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
example::main()
|
||||
}
|
||||
|
||||
#[cfg(not(any(x11_platform, wayland_platform)))]
|
||||
fn main() {
|
||||
println!("This example is only supported on X11 and Wayland platforms.");
|
||||
}
|
||||
@@ -11,9 +11,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
@@ -75,5 +75,5 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
@@ -47,5 +47,5 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Touchpad gestures")
|
||||
@@ -47,5 +47,5 @@ fn main() {
|
||||
} else if let Event::RedrawRequested(_) = event {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_decorations(false)
|
||||
@@ -36,5 +36,5 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use winit::event_loop::EventLoop;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let monitor = match event_loop.primary_monitor() {
|
||||
Some(monitor) => monitor,
|
||||
None => {
|
||||
|
||||
@@ -7,13 +7,16 @@ use winit::{
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
pub fn main() -> Result<(), impl std::error::Error> {
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.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();
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
let log_list = wasm::insert_canvas_and_create_log_list(&window);
|
||||
@@ -29,7 +32,7 @@ pub fn main() {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::MainEventsCleared => {
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::WindowEvent {
|
||||
@@ -53,11 +56,14 @@ pub fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
mod wasm {
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use softbuffer::{Surface, SurfaceExtWeb};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use winit::{event::Event, window::Window};
|
||||
|
||||
@@ -66,24 +72,33 @@ mod wasm {
|
||||
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
|
||||
|
||||
#[allow(clippy::main_recursion)]
|
||||
super::main();
|
||||
let _ = super::main();
|
||||
}
|
||||
|
||||
pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element {
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
|
||||
let canvas = window.canvas().unwrap();
|
||||
let mut surface = Surface::from_canvas(canvas.clone()).unwrap();
|
||||
surface
|
||||
.resize(
|
||||
NonZeroU32::new(canvas.width()).unwrap(),
|
||||
NonZeroU32::new(canvas.height()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut buffer = surface.buffer_mut().unwrap();
|
||||
buffer.fill(0xFFF0000);
|
||||
buffer.present().unwrap();
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let body = document.body().unwrap();
|
||||
|
||||
// Set a background color for the canvas to make it easier to tell where the canvas is for debugging purposes.
|
||||
canvas
|
||||
.style()
|
||||
.set_property("background-color", "crimson")
|
||||
.unwrap();
|
||||
body.append_child(&canvas).unwrap();
|
||||
let style = &canvas.style();
|
||||
style.set_property("margin", "50px").unwrap();
|
||||
// Use to test interactions with border and padding.
|
||||
//style.set_property("border", "50px solid black").unwrap();
|
||||
//style.set_property("padding", "50px").unwrap();
|
||||
|
||||
let log_header = document.create_element("h2").unwrap();
|
||||
log_header.set_text_content(Some("Event Log"));
|
||||
@@ -100,11 +115,25 @@ mod wasm {
|
||||
// Getting access to browser logs requires a lot of setup on mobile devices.
|
||||
// So we implement this basic logging system into the page to give developers an easy alternative.
|
||||
// As a bonus its also kind of handy on desktop.
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
let event = match event {
|
||||
Event::WindowEvent { event, .. } => Some(format!("{event:?}")),
|
||||
Event::Resumed | Event::Suspended => Some(format!("{event:?}")),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(event) = event {
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let log = document.create_element("li").unwrap();
|
||||
log.set_text_content(Some(&format!("{event:?}")));
|
||||
|
||||
let date = js_sys::Date::new_0();
|
||||
log.set_text_content(Some(&format!(
|
||||
"{:02}:{:02}:{:02}.{:03}: {event}",
|
||||
date.get_hours(),
|
||||
date.get_minutes(),
|
||||
date.get_seconds(),
|
||||
date.get_milliseconds(),
|
||||
)));
|
||||
|
||||
log_list
|
||||
.insert_before(&log, log_list.first_child().as_ref())
|
||||
.unwrap();
|
||||
|
||||
@@ -13,6 +13,7 @@ mod wasm {
|
||||
dpi::PhysicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
platform::web::WindowBuilderExtWebSys,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -30,13 +31,14 @@ This example demonstrates the desired future functionality which will possibly b
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn run() {
|
||||
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
// 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.
|
||||
.with_inner_size(PhysicalSize::new(100, 100))
|
||||
.with_append(true)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
@@ -45,7 +47,7 @@ This example demonstrates the desired future functionality which will possibly b
|
||||
// Render once with the size info we currently have
|
||||
render_circle(&canvas, window.inner_size());
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
let _ = event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
@@ -72,7 +74,6 @@ This example demonstrates the desired future functionality which will possibly b
|
||||
canvas
|
||||
.style()
|
||||
.set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;");
|
||||
body.append_child(&canvas).unwrap();
|
||||
|
||||
let explanation = document.create_element("pre").unwrap();
|
||||
explanation.set_text_content(Some(EXPLANATION));
|
||||
|
||||
@@ -10,9 +10,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
@@ -29,13 +29,15 @@ fn main() {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::MainEventsCleared => {
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
// Notify the windowing system that we'll be presenting to the window.
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
@@ -71,5 +71,5 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
@@ -139,5 +139,5 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ const BORDER: f64 = 8.0;
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0))
|
||||
@@ -52,6 +52,8 @@ fn main() {
|
||||
} => {
|
||||
if let Some(dir) = cursor_location {
|
||||
let _res = window.drag_resize_window(dir);
|
||||
} else if !window.is_decorated() {
|
||||
let _res = window.drag_window();
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
@@ -72,7 +74,7 @@ fn main() {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> CursorIcon {
|
||||
|
||||
@@ -12,7 +12,7 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
|
||||
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
|
||||
@@ -23,7 +23,7 @@ fn main() {
|
||||
|
||||
let icon = load_icon(Path::new(path));
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("An iconic window!")
|
||||
@@ -48,7 +48,7 @@ fn main() {
|
||||
} else if let Event::RedrawRequested(_) = event {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
fn load_icon(path: &Path) -> Icon {
|
||||
|
||||
90
examples/window_ondemand.rs
Normal file
90
examples/window_ondemand.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
// Limit this example to only compatible platforms.
|
||||
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))]
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
use std::time::Duration;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
|
||||
use winit::{
|
||||
error::EventLoopError,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
platform::run_ondemand::EventLoopExtRunOnDemand,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[derive(Default)]
|
||||
struct App {
|
||||
window_id: Option<WindowId>,
|
||||
window: Option<Window>,
|
||||
}
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let mut event_loop = EventLoop::new().unwrap();
|
||||
|
||||
fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> {
|
||||
let mut app = App::default();
|
||||
|
||||
event_loop.run_ondemand(move |event, event_loop, control_flow| {
|
||||
control_flow.set_wait();
|
||||
println!("Run {idx}: {:?}", event);
|
||||
|
||||
if let Some(window) = &app.window {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window.id() == window_id => {
|
||||
println!("--------------------------------------------------------- Window {idx} CloseRequested");
|
||||
app.window = None;
|
||||
}
|
||||
Event::AboutToWait => window.request_redraw(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if let Some(id) = app.window_id {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::Destroyed,
|
||||
window_id,
|
||||
} if id == window_id => {
|
||||
println!("--------------------------------------------------------- Window {idx} Destroyed");
|
||||
app.window_id = None;
|
||||
control_flow.set_exit();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if let Event::Resumed = event {
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Fantastic window number one!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(event_loop)
|
||||
.unwrap();
|
||||
app.window_id = Some(window.id());
|
||||
app.window = Some(window);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
run_app(&mut event_loop, 1)?;
|
||||
|
||||
println!("--------------------------------------------------------- Finished first loop");
|
||||
println!("--------------------------------------------------------- Waiting 5 seconds");
|
||||
std::thread::sleep(Duration::from_secs(5));
|
||||
|
||||
let ret = run_app(&mut event_loop, 2);
|
||||
println!("--------------------------------------------------------- Finished second loop");
|
||||
ret
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))]
|
||||
fn main() {
|
||||
println!("This example is not supported on this platform");
|
||||
}
|
||||
@@ -18,8 +18,8 @@ mod fill;
|
||||
/// Prints the keyboard events characters received when option_is_alt is true versus false.
|
||||
/// A left mouse click will toggle option_is_alt.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
@@ -58,7 +58,7 @@ fn main() {
|
||||
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
|
||||
_ => (),
|
||||
},
|
||||
Event::MainEventsCleared => {
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
@@ -66,7 +66,7 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
|
||||
73
examples/window_pump_events.rs
Normal file
73
examples/window_pump_events.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
// Limit this example to only compatible platforms.
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
android_platform,
|
||||
))]
|
||||
fn main() -> std::process::ExitCode {
|
||||
use std::{process::ExitCode, thread::sleep, time::Duration};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
let mut event_loop = EventLoop::new().unwrap();
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
'main: loop {
|
||||
let timeout = Some(Duration::ZERO);
|
||||
let status = event_loop.pump_events(timeout, |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
// Print only Window events to reduce noise
|
||||
println!("{event:?}");
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
if let PumpStatus::Exit(exit_code) = status {
|
||||
break 'main ExitCode::from(exit_code as u8);
|
||||
}
|
||||
|
||||
// Sleep for 1/60 second to simulate application work
|
||||
//
|
||||
// Since `pump_events` doesn't block it will be important to
|
||||
// throttle the loop in the app somehow.
|
||||
println!("Update()");
|
||||
sleep(Duration::from_millis(16));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(ios_platform, wasm_platform, orbital_platform))]
|
||||
fn main() {
|
||||
println!("This platform doesn't support pump_events.");
|
||||
}
|
||||
@@ -11,9 +11,9 @@ use winit::{
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
@@ -54,11 +54,11 @@ fn main() {
|
||||
debug!("Had increments: {}", new_increments.is_none());
|
||||
window.set_resize_increments(new_increments);
|
||||
}
|
||||
Event::MainEventsCleared => window.request_redraw(),
|
||||
Event::AboutToWait => window.request_redraw(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
// Limit this example to only compatible platforms.
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
android_platform,
|
||||
orbital_platform,
|
||||
))]
|
||||
fn main() {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
platform::run_return::EventLoopExtRunReturn,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
let mut event_loop = EventLoop::new();
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut quit = false;
|
||||
|
||||
while !quit {
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
// Print only Window events to reduce noise
|
||||
println!("{event:?}");
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
quit = true;
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
// Sleep for 1/60 second to simulate rendering
|
||||
println!("rendering");
|
||||
sleep(Duration::from_millis(16));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(ios_platform, wasm_platform))]
|
||||
fn main() {
|
||||
println!("This platform doesn't support run_return.");
|
||||
}
|
||||
112
examples/window_tabbing.rs
Normal file
112
examples/window_tabbing.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::{collections::HashMap, num::NonZeroUsize};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use simple_logger::SimpleLogger;
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
println!("Opened a new window: {:?}", window.id());
|
||||
windows.insert(window.id(), window);
|
||||
|
||||
println!("Press N to open a new window.");
|
||||
|
||||
event_loop.run(move |event, event_loop, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("Window {window_id:?} has received the signal to close");
|
||||
|
||||
// This drops the window, causing it to close.
|
||||
windows.remove(&window_id);
|
||||
|
||||
if windows.is_empty() {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
WindowEvent::Resized(_) => {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
state: ElementState::Pressed,
|
||||
logical_key,
|
||||
..
|
||||
},
|
||||
is_synthetic: false,
|
||||
..
|
||||
} => match logical_key.as_ref() {
|
||||
Key::Character("t") => {
|
||||
let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier();
|
||||
let window = WindowBuilder::new()
|
||||
.with_tabbing_identifier(&tabbing_id)
|
||||
.build(event_loop)
|
||||
.unwrap();
|
||||
println!("Added a new tab: {:?}", window.id());
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
Key::Character("w") => {
|
||||
let _ = windows.remove(&window_id);
|
||||
}
|
||||
Key::ArrowRight => {
|
||||
windows.get(&window_id).unwrap().select_next_tab();
|
||||
}
|
||||
Key::ArrowLeft => {
|
||||
windows.get(&window_id).unwrap().select_previous_tab();
|
||||
}
|
||||
Key::Character(ch) => {
|
||||
if let Ok(index) = ch.parse::<NonZeroUsize>() {
|
||||
let index = index.get();
|
||||
// Select the last tab when pressing `9`.
|
||||
let window = windows.get(&window_id).unwrap();
|
||||
if index == 9 {
|
||||
window.select_tab_at_index(window.num_tabs() - 1)
|
||||
} else {
|
||||
window.select_tab_at_index(index - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(window_id) => {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
fill::fill_window(window);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn main() {
|
||||
println!("This example is only supported on MacOS");
|
||||
}
|
||||
69
examples/x11_embed.rs
Normal file
69
examples/x11_embed.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
//! A demonstration of embedding a winit window in an existing X11 application.
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
mod imple {
|
||||
use super::fill;
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
platform::x11::WindowBuilderExtX11,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// First argument should be a 32-bit X11 window ID.
|
||||
let parent_window_id = std::env::args()
|
||||
.nth(1)
|
||||
.ok_or("Expected a 32-bit X11 window ID as the first argument.")?
|
||||
.parse::<u32>()?;
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("An embedded window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.with_embed_parent_window(parent_window_id)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
// Notify the windowing system that we'll be presenting to the window.
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(x11_platform))]
|
||||
mod imple {
|
||||
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("This example is only supported on X11 platforms.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
imple::entry()
|
||||
}
|
||||
418
src/dpi.rs
418
src/dpi.rs
@@ -581,3 +581,421 @@ impl<P: Pixel> From<LogicalPosition<P>> for Position {
|
||||
Position::Logical(position.cast())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::dpi;
|
||||
use std::collections::HashSet;
|
||||
|
||||
macro_rules! test_pixel_int_impl {
|
||||
($($name:ident => $ty:ty),*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
use dpi::Pixel;
|
||||
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::from_f64(37.0),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::from_f64(37.4),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::from_f64(37.5),
|
||||
38,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::from_f64(37.9),
|
||||
38,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u8>(37),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u16>(37),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u32>(37),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<i8>(37),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<i16>(37),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<i32>(37),
|
||||
37,
|
||||
);
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
test_pixel_int_impl! {
|
||||
test_pixel_int_u8 => u8,
|
||||
test_pixel_int_u16 => u16,
|
||||
test_pixel_int_u32 => u32,
|
||||
test_pixel_int_i8 => i8,
|
||||
test_pixel_int_i16 => i16
|
||||
}
|
||||
|
||||
macro_rules! assert_approx_eq {
|
||||
($a:expr, $b:expr $(,)?) => {
|
||||
assert!(
|
||||
($a - $b).abs() < 0.001,
|
||||
"{} is not approximately equal to {}",
|
||||
$a,
|
||||
$b
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! test_pixel_float_impl {
|
||||
($($name:ident => $ty:ty),*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
use dpi::Pixel;
|
||||
|
||||
assert_approx_eq!(
|
||||
<$ty as Pixel>::from_f64(37.0),
|
||||
37.0,
|
||||
);
|
||||
assert_approx_eq!(
|
||||
<$ty as Pixel>::from_f64(37.4),
|
||||
37.4,
|
||||
);
|
||||
assert_approx_eq!(
|
||||
<$ty as Pixel>::from_f64(37.5),
|
||||
37.5,
|
||||
);
|
||||
assert_approx_eq!(
|
||||
<$ty as Pixel>::from_f64(37.9),
|
||||
37.9,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u8>(37.0),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u8>(37.4),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u8>(37.5),
|
||||
38,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u16>(37.0),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u16>(37.4),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u16>(37.5),
|
||||
38,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u32>(37.0),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u32>(37.4),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<u32>(37.5),
|
||||
38,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<i8>(37.0),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<i8>(37.4),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<i8>(37.5),
|
||||
38,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<i16>(37.0),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<i16>(37.4),
|
||||
37,
|
||||
);
|
||||
assert_eq!(
|
||||
<$ty as Pixel>::cast::<i16>(37.5),
|
||||
38,
|
||||
);
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
test_pixel_float_impl! {
|
||||
test_pixel_float_f32 => f32,
|
||||
test_pixel_float_f64 => f64
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_scale_factor() {
|
||||
assert!(dpi::validate_scale_factor(1.0));
|
||||
assert!(dpi::validate_scale_factor(2.0));
|
||||
assert!(dpi::validate_scale_factor(3.0));
|
||||
assert!(dpi::validate_scale_factor(1.5));
|
||||
assert!(dpi::validate_scale_factor(0.5));
|
||||
|
||||
assert!(!dpi::validate_scale_factor(0.0));
|
||||
assert!(!dpi::validate_scale_factor(-1.0));
|
||||
assert!(!dpi::validate_scale_factor(f64::INFINITY));
|
||||
assert!(!dpi::validate_scale_factor(f64::NAN));
|
||||
assert!(!dpi::validate_scale_factor(f64::NEG_INFINITY));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_logical_position() {
|
||||
let log_pos = dpi::LogicalPosition::new(1.0, 2.0);
|
||||
assert_eq!(
|
||||
log_pos.to_physical::<u32>(1.0),
|
||||
dpi::PhysicalPosition::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
log_pos.to_physical::<u32>(2.0),
|
||||
dpi::PhysicalPosition::new(2, 4)
|
||||
);
|
||||
assert_eq!(log_pos.cast::<u32>(), dpi::LogicalPosition::new(1, 2));
|
||||
assert_eq!(
|
||||
log_pos,
|
||||
dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(1.0, 2.0), 1.0)
|
||||
);
|
||||
assert_eq!(
|
||||
log_pos,
|
||||
dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(2.0, 4.0), 2.0)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::LogicalPosition::from((2.0, 2.0)),
|
||||
dpi::LogicalPosition::new(2.0, 2.0)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::LogicalPosition::from([2.0, 3.0]),
|
||||
dpi::LogicalPosition::new(2.0, 3.0)
|
||||
);
|
||||
|
||||
let x: (f64, f64) = log_pos.into();
|
||||
assert_eq!(x, (1.0, 2.0));
|
||||
let x: [f64; 2] = log_pos.into();
|
||||
assert_eq!(x, [1.0, 2.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_physical_position() {
|
||||
assert_eq!(
|
||||
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(1.0, 2.0), 1.0),
|
||||
dpi::PhysicalPosition::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(2.0, 4.0), 0.5),
|
||||
dpi::PhysicalPosition::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::PhysicalPosition::from((2.0, 2.0)),
|
||||
dpi::PhysicalPosition::new(2.0, 2.0)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::PhysicalPosition::from([2.0, 3.0]),
|
||||
dpi::PhysicalPosition::new(2.0, 3.0)
|
||||
);
|
||||
|
||||
let x: (f64, f64) = dpi::PhysicalPosition::new(1, 2).into();
|
||||
assert_eq!(x, (1.0, 2.0));
|
||||
let x: [f64; 2] = dpi::PhysicalPosition::new(1, 2).into();
|
||||
assert_eq!(x, [1.0, 2.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_logical_size() {
|
||||
let log_size = dpi::LogicalSize::new(1.0, 2.0);
|
||||
assert_eq!(
|
||||
log_size.to_physical::<u32>(1.0),
|
||||
dpi::PhysicalSize::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
log_size.to_physical::<u32>(2.0),
|
||||
dpi::PhysicalSize::new(2, 4)
|
||||
);
|
||||
assert_eq!(log_size.cast::<u32>(), dpi::LogicalSize::new(1, 2));
|
||||
assert_eq!(
|
||||
log_size,
|
||||
dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(1.0, 2.0), 1.0)
|
||||
);
|
||||
assert_eq!(
|
||||
log_size,
|
||||
dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(2.0, 4.0), 2.0)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::LogicalSize::from((2.0, 2.0)),
|
||||
dpi::LogicalSize::new(2.0, 2.0)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::LogicalSize::from([2.0, 3.0]),
|
||||
dpi::LogicalSize::new(2.0, 3.0)
|
||||
);
|
||||
|
||||
let x: (f64, f64) = log_size.into();
|
||||
assert_eq!(x, (1.0, 2.0));
|
||||
let x: [f64; 2] = log_size.into();
|
||||
assert_eq!(x, [1.0, 2.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_physical_size() {
|
||||
assert_eq!(
|
||||
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(1.0, 2.0), 1.0),
|
||||
dpi::PhysicalSize::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(2.0, 4.0), 0.5),
|
||||
dpi::PhysicalSize::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::PhysicalSize::from((2.0, 2.0)),
|
||||
dpi::PhysicalSize::new(2.0, 2.0)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::PhysicalSize::from([2.0, 3.0]),
|
||||
dpi::PhysicalSize::new(2.0, 3.0)
|
||||
);
|
||||
|
||||
let x: (f64, f64) = dpi::PhysicalSize::new(1, 2).into();
|
||||
assert_eq!(x, (1.0, 2.0));
|
||||
let x: [f64; 2] = dpi::PhysicalSize::new(1, 2).into();
|
||||
assert_eq!(x, [1.0, 2.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_eq!(
|
||||
dpi::Size::new(dpi::PhysicalSize::new(1, 2)),
|
||||
dpi::Size::Physical(dpi::PhysicalSize::new(1, 2))
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)),
|
||||
dpi::Size::Logical(dpi::LogicalSize::new(1.0, 2.0))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(1.0),
|
||||
dpi::LogicalSize::new(1.0, 2.0)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(2.0),
|
||||
dpi::LogicalSize::new(0.5, 1.0)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0),
|
||||
dpi::LogicalSize::new(1.0, 2.0)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(1.0),
|
||||
dpi::PhysicalSize::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(2.0),
|
||||
dpi::PhysicalSize::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0),
|
||||
dpi::PhysicalSize::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0),
|
||||
dpi::PhysicalSize::new(2, 4)
|
||||
);
|
||||
|
||||
let small = dpi::Size::Physical((1, 2).into());
|
||||
let medium = dpi::Size::Logical((3, 4).into());
|
||||
let medium_physical = dpi::Size::new(medium.to_physical::<u32>(1.0));
|
||||
let large = dpi::Size::Physical((5, 6).into());
|
||||
assert_eq!(dpi::Size::clamp(medium, small, large, 1.0), medium_physical);
|
||||
assert_eq!(dpi::Size::clamp(small, medium, large, 1.0), medium_physical);
|
||||
assert_eq!(dpi::Size::clamp(large, small, medium, 1.0), medium_physical);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_position() {
|
||||
assert_eq!(
|
||||
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)),
|
||||
dpi::Position::Physical(dpi::PhysicalPosition::new(1, 2))
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)),
|
||||
dpi::Position::Logical(dpi::LogicalPosition::new(1.0, 2.0))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0),
|
||||
dpi::LogicalPosition::new(1.0, 2.0)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0),
|
||||
dpi::LogicalPosition::new(0.5, 1.0)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0),
|
||||
dpi::LogicalPosition::new(1.0, 2.0)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0),
|
||||
dpi::PhysicalPosition::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0),
|
||||
dpi::PhysicalPosition::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0),
|
||||
dpi::PhysicalPosition::new(1, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0),
|
||||
dpi::PhysicalPosition::new(2, 4)
|
||||
);
|
||||
}
|
||||
|
||||
// Eat coverage for the Debug impls et al
|
||||
#[test]
|
||||
fn ensure_attrs_do_not_panic() {
|
||||
let _ = format!("{:?}", dpi::LogicalPosition::<u32>::default().clone());
|
||||
HashSet::new().insert(dpi::LogicalPosition::<u32>::default());
|
||||
|
||||
let _ = format!("{:?}", dpi::PhysicalPosition::<u32>::default().clone());
|
||||
HashSet::new().insert(dpi::PhysicalPosition::<u32>::default());
|
||||
|
||||
let _ = format!("{:?}", dpi::LogicalSize::<u32>::default().clone());
|
||||
HashSet::new().insert(dpi::LogicalSize::<u32>::default());
|
||||
|
||||
let _ = format!("{:?}", dpi::PhysicalSize::<u32>::default().clone());
|
||||
HashSet::new().insert(dpi::PhysicalSize::<u32>::default());
|
||||
|
||||
let _ = format!("{:?}", dpi::Size::Physical((1, 2).into()).clone());
|
||||
let _ = format!("{:?}", dpi::Position::Physical((1, 2).into()).clone());
|
||||
}
|
||||
}
|
||||
|
||||
62
src/error.rs
62
src/error.rs
@@ -2,11 +2,14 @@ use std::{error, fmt};
|
||||
|
||||
use crate::platform_impl;
|
||||
|
||||
/// An error whose cause it outside Winit's control.
|
||||
// TODO: Rename
|
||||
/// An error that may be generated when requesting Winit state
|
||||
#[derive(Debug)]
|
||||
pub enum ExternalError {
|
||||
/// The operation is not supported by the backend.
|
||||
NotSupported(NotSupportedError),
|
||||
/// The operation was ignored.
|
||||
Ignored,
|
||||
/// The OS cannot perform the operation.
|
||||
Os(OsError),
|
||||
}
|
||||
@@ -25,6 +28,27 @@ pub struct OsError {
|
||||
error: platform_impl::OsError,
|
||||
}
|
||||
|
||||
/// A general error that may occur while running the Winit event loop
|
||||
#[derive(Debug)]
|
||||
pub enum EventLoopError {
|
||||
/// The operation is not supported by the backend.
|
||||
NotSupported(NotSupportedError),
|
||||
/// The OS cannot perform the operation.
|
||||
Os(OsError),
|
||||
/// The event loop can't be re-run while it's already running
|
||||
AlreadyRunning,
|
||||
/// The event loop can't be re-created.
|
||||
RecreationAttempt,
|
||||
/// Application has exit with an error status.
|
||||
ExitFailure(i32),
|
||||
}
|
||||
|
||||
impl From<OsError> for EventLoopError {
|
||||
fn from(value: OsError) -> Self {
|
||||
Self::Os(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl NotSupportedError {
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
@@ -60,6 +84,7 @@ impl fmt::Display for ExternalError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
ExternalError::NotSupported(e) => e.fmt(f),
|
||||
ExternalError::Ignored => write!(f, "Operation was ignored"),
|
||||
ExternalError::Os(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
@@ -77,6 +102,41 @@ impl fmt::Display for NotSupportedError {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EventLoopError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
EventLoopError::AlreadyRunning => write!(f, "EventLoop is already running"),
|
||||
EventLoopError::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
|
||||
EventLoopError::NotSupported(e) => e.fmt(f),
|
||||
EventLoopError::Os(e) => e.fmt(f),
|
||||
EventLoopError::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for OsError {}
|
||||
impl error::Error for ExternalError {}
|
||||
impl error::Error for NotSupportedError {}
|
||||
impl error::Error for EventLoopError {}
|
||||
|
||||
#[cfg(test)]
|
||||
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()
|
||||
);
|
||||
let _ = format!(
|
||||
"{:?}, {}",
|
||||
ExternalError::NotSupported(NotSupportedError::new()),
|
||||
ExternalError::NotSupported(NotSupportedError::new())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
639
src/event.rs
639
src/event.rs
@@ -16,17 +16,16 @@
|
||||
//! for e in (window events, user events, device events) {
|
||||
//! event_handler(e, ..., &mut control_flow);
|
||||
//! }
|
||||
//! event_handler(MainEventsCleared, ..., &mut control_flow);
|
||||
//!
|
||||
//! for w in (redraw windows) {
|
||||
//! event_handler(RedrawRequested(w), ..., &mut control_flow);
|
||||
//! }
|
||||
//! event_handler(RedrawEventsCleared, ..., &mut control_flow);
|
||||
//!
|
||||
//! event_handler(AboutToWait, ..., &mut control_flow);
|
||||
//! start_cause = wait_if_necessary(control_flow);
|
||||
//! }
|
||||
//!
|
||||
//! event_handler(LoopDestroyed, ..., &mut control_flow);
|
||||
//! event_handler(LoopExiting, ..., &mut control_flow);
|
||||
//! ```
|
||||
//!
|
||||
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
|
||||
@@ -34,27 +33,31 @@
|
||||
//!
|
||||
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
|
||||
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
use smol_str::SmolStr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Mutex, Weak};
|
||||
#[cfg(not(wasm_platform))]
|
||||
use std::time::Instant;
|
||||
|
||||
use smol_str::SmolStr;
|
||||
#[cfg(wasm_platform)]
|
||||
use web_time::Instant;
|
||||
|
||||
use crate::error::ExternalError;
|
||||
#[cfg(doc)]
|
||||
use crate::window::Window;
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
event_loop::AsyncRequestSerial,
|
||||
keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState},
|
||||
platform_impl,
|
||||
window::{Theme, WindowId},
|
||||
window::{ActivationToken, Theme, WindowId},
|
||||
};
|
||||
|
||||
/// Describes a generic event.
|
||||
///
|
||||
/// See the module-level docs for more information on the event loop manages each event.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Event<'a, T: 'static> {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Event<T: 'static> {
|
||||
/// 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
|
||||
@@ -66,7 +69,7 @@ pub enum Event<'a, T: 'static> {
|
||||
/// Emitted when the OS sends an event to a winit window.
|
||||
WindowEvent {
|
||||
window_id: WindowId,
|
||||
event: WindowEvent<'a>,
|
||||
event: WindowEvent,
|
||||
},
|
||||
|
||||
/// Emitted when the OS sends an event to a device.
|
||||
@@ -84,7 +87,7 @@ pub enum Event<'a, T: 'static> {
|
||||
///
|
||||
/// Not all platforms support the notion of suspending applications, and there may be no
|
||||
/// technical way to guarantee being able to emit a `Suspended` event if the OS has
|
||||
/// no formal application lifecycle (currently only Android and iOS do). For this reason,
|
||||
/// no formal application lifecycle (currently only Android, iOS, and Web do). For this reason,
|
||||
/// Winit does not currently try to emit pseudo `Suspended` events before the application
|
||||
/// quits on platforms without an application lifecycle.
|
||||
///
|
||||
@@ -129,7 +132,7 @@ pub enum Event<'a, T: 'static> {
|
||||
///
|
||||
/// On Web, the `Suspended` event is emitted in response to a [`pagehide`] event
|
||||
/// with the property [`persisted`] being true, which means that the page is being
|
||||
/// put in the [´bfcache`] (back/forward cache) - an in-memory cache that stores a
|
||||
/// put in the [`bfcache`] (back/forward cache) - an in-memory cache that stores a
|
||||
/// complete snapshot of a page (including the JavaScript heap) as the user is
|
||||
/// navigating away.
|
||||
///
|
||||
@@ -195,7 +198,7 @@ pub enum Event<'a, T: 'static> {
|
||||
///
|
||||
/// On Web, the `Resumed` event is emitted in response to a [`pageshow`] event
|
||||
/// with the property [`persisted`] being true, which means that the page is being
|
||||
/// restored from the [´bfcache`] (back/forward cache) - an in-memory cache that
|
||||
/// restored from the [`bfcache`] (back/forward cache) - an in-memory cache that
|
||||
/// stores a complete snapshot of a page (including the JavaScript heap) as the
|
||||
/// user is navigating away.
|
||||
///
|
||||
@@ -206,121 +209,53 @@ pub enum Event<'a, T: 'static> {
|
||||
/// [`Suspended`]: Self::Suspended
|
||||
Resumed,
|
||||
|
||||
/// Emitted when all of the event loop's input events have been processed and redraw processing
|
||||
/// is about to begin.
|
||||
/// Emitted when the event loop is about to block and wait for new events.
|
||||
///
|
||||
/// This event is useful as a place to put your code that should be run after all
|
||||
/// state-changing events have been handled and you want to do stuff (updating state, performing
|
||||
/// calculations, etc) that happens as the "main body" of your event loop. If your program only draws
|
||||
/// graphics when something changes, it's usually better to do it in response to
|
||||
/// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted
|
||||
/// immediately after this event. Programs that draw graphics continuously, like most games,
|
||||
/// can render here unconditionally for simplicity.
|
||||
MainEventsCleared,
|
||||
/// Most applications shouldn't need to hook into this event since there is no real relationship
|
||||
/// between how often the event loop needs to wake up and the dispatching of any specific events.
|
||||
///
|
||||
/// High frequency event sources, such as input devices could potentially lead to lots of wake
|
||||
/// ups and also lots of corresponding `AboutToWait` events.
|
||||
///
|
||||
/// This is not an ideal event to drive application rendering from and instead applications
|
||||
/// should render in response to [`Event::RedrawRequested`](crate::event::Event::RedrawRequested)
|
||||
/// events.
|
||||
AboutToWait,
|
||||
|
||||
/// Emitted after [`MainEventsCleared`] when a window should be redrawn.
|
||||
/// Emitted when a window should be redrawn.
|
||||
///
|
||||
/// This gets triggered in two scenarios:
|
||||
/// - The OS has performed an operation that's invalidated the window's contents (such as
|
||||
/// resizing the window).
|
||||
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
|
||||
///
|
||||
/// During each iteration of the event loop, Winit will aggregate duplicate redraw requests
|
||||
/// into a single event, to help avoid duplicating rendering work.
|
||||
///
|
||||
/// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless
|
||||
/// something changes, like most non-game GUIs.
|
||||
///
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS / iOS:** Due to implementation difficulties, this will often, but not always, be
|
||||
/// emitted directly inside `drawRect:`, with neither a preceding [`MainEventsCleared`] nor
|
||||
/// subsequent `RedrawEventsCleared`. See [#2640] for work on this.
|
||||
///
|
||||
/// [`MainEventsCleared`]: Self::MainEventsCleared
|
||||
/// [`RedrawEventsCleared`]: Self::RedrawEventsCleared
|
||||
/// [#2640]: https://github.com/rust-windowing/winit/issues/2640
|
||||
/// Winit will aggregate duplicate redraw requests into a single event, to
|
||||
/// help avoid duplicating rendering work.
|
||||
RedrawRequested(WindowId),
|
||||
|
||||
/// Emitted after all [`RedrawRequested`] events have been processed and control flow is about to
|
||||
/// be taken away from the program. If there are no `RedrawRequested` events, it is emitted
|
||||
/// immediately after `MainEventsCleared`.
|
||||
///
|
||||
/// This event is useful for doing any cleanup or bookkeeping work after all the rendering
|
||||
/// tasks have been completed.
|
||||
///
|
||||
/// [`RedrawRequested`]: Self::RedrawRequested
|
||||
RedrawEventsCleared,
|
||||
|
||||
/// Emitted when the event loop is being shut down.
|
||||
///
|
||||
/// This is irreversible - if this event is emitted, it is guaranteed to be the last event that
|
||||
/// gets emitted. You generally want to treat this as a "do on quit" event.
|
||||
LoopDestroyed,
|
||||
LoopExiting,
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for Event<'static, T> {
|
||||
fn clone(&self) -> Self {
|
||||
use self::Event::*;
|
||||
match self {
|
||||
WindowEvent { window_id, event } => WindowEvent {
|
||||
window_id: *window_id,
|
||||
event: event.clone(),
|
||||
},
|
||||
UserEvent(event) => UserEvent(event.clone()),
|
||||
DeviceEvent { device_id, event } => DeviceEvent {
|
||||
device_id: *device_id,
|
||||
event: event.clone(),
|
||||
},
|
||||
NewEvents(cause) => NewEvents(*cause),
|
||||
MainEventsCleared => MainEventsCleared,
|
||||
RedrawRequested(wid) => RedrawRequested(*wid),
|
||||
RedrawEventsCleared => RedrawEventsCleared,
|
||||
LoopDestroyed => LoopDestroyed,
|
||||
Suspended => Suspended,
|
||||
Resumed => Resumed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Event<'a, T> {
|
||||
impl<T> Event<T> {
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub fn map_nonuser_event<U>(self) -> Result<Event<'a, U>, Event<'a, T>> {
|
||||
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
|
||||
use self::Event::*;
|
||||
match self {
|
||||
UserEvent(_) => Err(self),
|
||||
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
|
||||
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
|
||||
NewEvents(cause) => Ok(NewEvents(cause)),
|
||||
MainEventsCleared => Ok(MainEventsCleared),
|
||||
AboutToWait => Ok(AboutToWait),
|
||||
RedrawRequested(wid) => Ok(RedrawRequested(wid)),
|
||||
RedrawEventsCleared => Ok(RedrawEventsCleared),
|
||||
LoopDestroyed => Ok(LoopDestroyed),
|
||||
LoopExiting => Ok(LoopExiting),
|
||||
Suspended => Ok(Suspended),
|
||||
Resumed => Ok(Resumed),
|
||||
}
|
||||
}
|
||||
|
||||
/// If the event doesn't contain a reference, turn it into an event with a `'static` lifetime.
|
||||
/// Otherwise, return `None`.
|
||||
pub fn to_static(self) -> Option<Event<'static, T>> {
|
||||
use self::Event::*;
|
||||
match self {
|
||||
WindowEvent { window_id, event } => event
|
||||
.to_static()
|
||||
.map(|event| WindowEvent { window_id, event }),
|
||||
UserEvent(event) => Some(UserEvent(event)),
|
||||
DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }),
|
||||
NewEvents(cause) => Some(NewEvents(cause)),
|
||||
MainEventsCleared => Some(MainEventsCleared),
|
||||
RedrawRequested(wid) => Some(RedrawRequested(wid)),
|
||||
RedrawEventsCleared => Some(RedrawEventsCleared),
|
||||
LoopDestroyed => Some(LoopDestroyed),
|
||||
Suspended => Some(Suspended),
|
||||
Resumed => Some(Resumed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the reason the event loop is resuming.
|
||||
@@ -354,8 +289,22 @@ pub enum StartCause {
|
||||
}
|
||||
|
||||
/// Describes an event from a [`Window`].
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum WindowEvent<'a> {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum WindowEvent {
|
||||
/// The activation token was delivered back and now could be used.
|
||||
///
|
||||
#[cfg_attr(
|
||||
not(any(x11_platform, wayland_platfrom)),
|
||||
allow(rustdoc::broken_intra_doc_links)
|
||||
)]
|
||||
/// Delivered in response to [`request_activation_token`].
|
||||
///
|
||||
/// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token
|
||||
ActivationTokenDone {
|
||||
serial: AsyncRequestSerial,
|
||||
token: ActivationToken,
|
||||
},
|
||||
|
||||
/// The size of the window has changed. Contains the client area's new dimensions.
|
||||
Resized(PhysicalSize<u32>),
|
||||
|
||||
@@ -430,6 +379,14 @@ pub enum WindowEvent<'a> {
|
||||
Ime(Ime),
|
||||
|
||||
/// The cursor has moved on the window.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
||||
///
|
||||
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
|
||||
CursorMoved {
|
||||
device_id: DeviceId,
|
||||
|
||||
@@ -440,9 +397,25 @@ pub enum WindowEvent<'a> {
|
||||
},
|
||||
|
||||
/// The cursor has entered the window.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
||||
///
|
||||
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
|
||||
CursorEntered { device_id: DeviceId },
|
||||
|
||||
/// The cursor has left the window.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
||||
///
|
||||
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
|
||||
CursorLeft { device_id: DeviceId },
|
||||
|
||||
/// A mouse wheel movement or touchpad scroll occurred.
|
||||
@@ -528,7 +501,12 @@ pub enum WindowEvent<'a> {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
||||
/// - **macOS:** Unsupported.
|
||||
///
|
||||
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
|
||||
Touch(Touch),
|
||||
|
||||
/// The window's scale factor has changed.
|
||||
@@ -546,7 +524,10 @@ pub enum WindowEvent<'a> {
|
||||
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
|
||||
ScaleFactorChanged {
|
||||
scale_factor: f64,
|
||||
new_inner_size: &'a mut PhysicalSize<u32>,
|
||||
/// Handle to update inner size during scale changes.
|
||||
///
|
||||
/// See [`InnerSizeWriter`] docs for more details.
|
||||
inner_size_writer: InnerSizeWriter,
|
||||
},
|
||||
|
||||
/// The system window theme has changed.
|
||||
@@ -565,208 +546,16 @@ pub enum WindowEvent<'a> {
|
||||
/// minimised, set invisible, or fully occluded by another window.
|
||||
///
|
||||
/// Platform-specific behavior:
|
||||
/// - **iOS / Android / Web / Wayland / Windows / Orbital:** Unsupported.
|
||||
///
|
||||
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
||||
/// - **iOS / Android / Wayland / Windows / Orbital:** Unsupported.
|
||||
///
|
||||
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
|
||||
Occluded(bool),
|
||||
}
|
||||
|
||||
impl Clone for WindowEvent<'static> {
|
||||
fn clone(&self) -> Self {
|
||||
use self::WindowEvent::*;
|
||||
return match self {
|
||||
Resized(size) => Resized(*size),
|
||||
Moved(pos) => Moved(*pos),
|
||||
CloseRequested => CloseRequested,
|
||||
Destroyed => Destroyed,
|
||||
DroppedFile(file) => DroppedFile(file.clone()),
|
||||
HoveredFile(file) => HoveredFile(file.clone()),
|
||||
HoveredFileCancelled => HoveredFileCancelled,
|
||||
Focused(f) => Focused(*f),
|
||||
KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
is_synthetic,
|
||||
} => KeyboardInput {
|
||||
device_id: *device_id,
|
||||
event: event.clone(),
|
||||
is_synthetic: *is_synthetic,
|
||||
},
|
||||
Ime(preedit_state) => Ime(preedit_state.clone()),
|
||||
ModifiersChanged(modifiers) => ModifiersChanged(*modifiers),
|
||||
CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
} => CursorMoved {
|
||||
device_id: *device_id,
|
||||
position: *position,
|
||||
},
|
||||
CursorEntered { device_id } => CursorEntered {
|
||||
device_id: *device_id,
|
||||
},
|
||||
CursorLeft { device_id } => CursorLeft {
|
||||
device_id: *device_id,
|
||||
},
|
||||
MouseWheel {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
} => MouseWheel {
|
||||
device_id: *device_id,
|
||||
delta: *delta,
|
||||
phase: *phase,
|
||||
},
|
||||
MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button,
|
||||
} => MouseInput {
|
||||
device_id: *device_id,
|
||||
state: *state,
|
||||
button: *button,
|
||||
},
|
||||
TouchpadMagnify {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
} => TouchpadMagnify {
|
||||
device_id: *device_id,
|
||||
delta: *delta,
|
||||
phase: *phase,
|
||||
},
|
||||
SmartMagnify { device_id } => SmartMagnify {
|
||||
device_id: *device_id,
|
||||
},
|
||||
TouchpadRotate {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
} => TouchpadRotate {
|
||||
device_id: *device_id,
|
||||
delta: *delta,
|
||||
phase: *phase,
|
||||
},
|
||||
TouchpadPressure {
|
||||
device_id,
|
||||
pressure,
|
||||
stage,
|
||||
} => TouchpadPressure {
|
||||
device_id: *device_id,
|
||||
pressure: *pressure,
|
||||
stage: *stage,
|
||||
},
|
||||
AxisMotion {
|
||||
device_id,
|
||||
axis,
|
||||
value,
|
||||
} => AxisMotion {
|
||||
device_id: *device_id,
|
||||
axis: *axis,
|
||||
value: *value,
|
||||
},
|
||||
Touch(touch) => Touch(*touch),
|
||||
ThemeChanged(theme) => ThemeChanged(*theme),
|
||||
ScaleFactorChanged { .. } => {
|
||||
unreachable!("Static event can't be about scale factor changing")
|
||||
}
|
||||
Occluded(occluded) => Occluded(*occluded),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> WindowEvent<'a> {
|
||||
pub fn to_static(self) -> Option<WindowEvent<'static>> {
|
||||
use self::WindowEvent::*;
|
||||
match self {
|
||||
Resized(size) => Some(Resized(size)),
|
||||
Moved(position) => Some(Moved(position)),
|
||||
CloseRequested => Some(CloseRequested),
|
||||
Destroyed => Some(Destroyed),
|
||||
DroppedFile(file) => Some(DroppedFile(file)),
|
||||
HoveredFile(file) => Some(HoveredFile(file)),
|
||||
HoveredFileCancelled => Some(HoveredFileCancelled),
|
||||
Focused(focused) => Some(Focused(focused)),
|
||||
KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
is_synthetic,
|
||||
} => Some(KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
is_synthetic,
|
||||
}),
|
||||
ModifiersChanged(modifers) => Some(ModifiersChanged(modifers)),
|
||||
Ime(event) => Some(Ime(event)),
|
||||
CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
} => Some(CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
}),
|
||||
CursorEntered { device_id } => Some(CursorEntered { device_id }),
|
||||
CursorLeft { device_id } => Some(CursorLeft { device_id }),
|
||||
MouseWheel {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
} => Some(MouseWheel {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
}),
|
||||
MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button,
|
||||
} => Some(MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button,
|
||||
}),
|
||||
TouchpadMagnify {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
} => Some(TouchpadMagnify {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
}),
|
||||
SmartMagnify { device_id } => Some(SmartMagnify { device_id }),
|
||||
TouchpadRotate {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
} => Some(TouchpadRotate {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
}),
|
||||
TouchpadPressure {
|
||||
device_id,
|
||||
pressure,
|
||||
stage,
|
||||
} => Some(TouchpadPressure {
|
||||
device_id,
|
||||
pressure,
|
||||
stage,
|
||||
}),
|
||||
AxisMotion {
|
||||
device_id,
|
||||
axis,
|
||||
value,
|
||||
} => Some(AxisMotion {
|
||||
device_id,
|
||||
axis,
|
||||
value,
|
||||
}),
|
||||
Touch(touch) => Some(Touch(touch)),
|
||||
ThemeChanged(theme) => Some(ThemeChanged(theme)),
|
||||
ScaleFactorChanged { .. } => None,
|
||||
Occluded(occluded) => Some(Occluded(occluded)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier of an input device.
|
||||
///
|
||||
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
|
||||
@@ -1137,7 +926,12 @@ pub enum TouchPhase {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
||||
/// - **macOS:** Unsupported.
|
||||
///
|
||||
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Touch {
|
||||
pub device_id: DeviceId,
|
||||
@@ -1148,7 +942,7 @@ pub struct Touch {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
|
||||
/// - Only available on **iOS** 9.0+, **Windows** 8+, and **Web**.
|
||||
pub force: Option<Force>,
|
||||
/// Unique identifier of a finger.
|
||||
pub id: u64,
|
||||
@@ -1225,6 +1019,13 @@ pub enum ElementState {
|
||||
Released,
|
||||
}
|
||||
|
||||
impl ElementState {
|
||||
/// True if `self == Pressed`.
|
||||
pub fn is_pressed(self) -> bool {
|
||||
self == ElementState::Pressed
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a button of a mouse controller.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
@@ -1268,3 +1069,239 @@ pub enum MouseScrollDelta {
|
||||
/// and move the content right and down (to reveal more things left and up).
|
||||
PixelDelta(PhysicalPosition<f64>),
|
||||
}
|
||||
|
||||
/// Handle to synchroniously change the size of the window from the
|
||||
/// [`WindowEvent`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InnerSizeWriter {
|
||||
pub(crate) new_inner_size: Weak<Mutex<PhysicalSize<u32>>>,
|
||||
}
|
||||
|
||||
impl InnerSizeWriter {
|
||||
#[cfg(not(orbital_platform))]
|
||||
pub(crate) fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
|
||||
Self { new_inner_size }
|
||||
}
|
||||
|
||||
/// Try to request inner size which will be set synchroniously on the window.
|
||||
pub fn request_inner_size(
|
||||
&mut self,
|
||||
new_inner_size: PhysicalSize<u32>,
|
||||
) -> Result<(), ExternalError> {
|
||||
if let Some(inner) = self.new_inner_size.upgrade() {
|
||||
*inner.lock().unwrap() = new_inner_size;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ExternalError::Ignored)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for InnerSizeWriter {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.new_inner_size.as_ptr() == other.new_inner_size.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::event;
|
||||
use std::collections::{BTreeSet, HashSet};
|
||||
|
||||
macro_rules! foreach_event {
|
||||
($closure:expr) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut x = $closure;
|
||||
let did = unsafe { event::DeviceId::dummy() };
|
||||
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
use crate::event::{Event::*, Ime::Enabled, WindowEvent::*};
|
||||
use crate::window::WindowId;
|
||||
|
||||
// Mainline events.
|
||||
let wid = unsafe { WindowId::dummy() };
|
||||
x(UserEvent(()));
|
||||
x(NewEvents(event::StartCause::Init));
|
||||
x(RedrawRequested(wid));
|
||||
x(AboutToWait);
|
||||
x(LoopExiting);
|
||||
x(Suspended);
|
||||
x(Resumed);
|
||||
|
||||
// Window events.
|
||||
let with_window_event = |wev| {
|
||||
x(WindowEvent {
|
||||
window_id: wid,
|
||||
event: wev,
|
||||
})
|
||||
};
|
||||
|
||||
with_window_event(CloseRequested);
|
||||
with_window_event(Destroyed);
|
||||
with_window_event(Focused(true));
|
||||
with_window_event(Moved((0, 0).into()));
|
||||
with_window_event(Resized((0, 0).into()));
|
||||
with_window_event(DroppedFile("x.txt".into()));
|
||||
with_window_event(HoveredFile("x.txt".into()));
|
||||
with_window_event(HoveredFileCancelled);
|
||||
with_window_event(Ime(Enabled));
|
||||
with_window_event(CursorMoved {
|
||||
device_id: did,
|
||||
position: (0, 0).into(),
|
||||
});
|
||||
with_window_event(ModifiersChanged(event::Modifiers::default()));
|
||||
with_window_event(CursorEntered { device_id: did });
|
||||
with_window_event(CursorLeft { device_id: did });
|
||||
with_window_event(MouseWheel {
|
||||
device_id: did,
|
||||
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(MouseInput {
|
||||
device_id: did,
|
||||
state: event::ElementState::Pressed,
|
||||
button: event::MouseButton::Other(0),
|
||||
});
|
||||
with_window_event(TouchpadMagnify {
|
||||
device_id: did,
|
||||
delta: 0.0,
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(SmartMagnify { device_id: did });
|
||||
with_window_event(TouchpadRotate {
|
||||
device_id: did,
|
||||
delta: 0.0,
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(TouchpadPressure {
|
||||
device_id: did,
|
||||
pressure: 0.0,
|
||||
stage: 0,
|
||||
});
|
||||
with_window_event(AxisMotion {
|
||||
device_id: did,
|
||||
axis: 0,
|
||||
value: 0.0,
|
||||
});
|
||||
with_window_event(Touch(event::Touch {
|
||||
device_id: did,
|
||||
phase: event::TouchPhase::Started,
|
||||
location: (0.0, 0.0).into(),
|
||||
id: 0,
|
||||
force: Some(event::Force::Normalized(0.0)),
|
||||
}));
|
||||
with_window_event(ThemeChanged(crate::window::Theme::Light));
|
||||
with_window_event(Occluded(true));
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
use event::DeviceEvent::*;
|
||||
|
||||
let with_device_event = |dev_ev| {
|
||||
x(event::Event::DeviceEvent {
|
||||
device_id: did,
|
||||
event: dev_ev,
|
||||
})
|
||||
};
|
||||
|
||||
with_device_event(Added);
|
||||
with_device_event(Removed);
|
||||
with_device_event(MouseMotion {
|
||||
delta: (0.0, 0.0).into(),
|
||||
});
|
||||
with_device_event(MouseWheel {
|
||||
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
|
||||
});
|
||||
with_device_event(Motion {
|
||||
axis: 0,
|
||||
value: 0.0,
|
||||
});
|
||||
with_device_event(Button {
|
||||
button: 0,
|
||||
state: event::ElementState::Pressed,
|
||||
});
|
||||
with_device_event(Text { codepoint: 'a' });
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[allow(clippy::redundant_clone)]
|
||||
#[test]
|
||||
fn test_event_clone() {
|
||||
foreach_event!(|event: event::Event<()>| {
|
||||
let event2 = event.clone();
|
||||
assert_eq!(event, event2);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_nonuser_event() {
|
||||
foreach_event!(|event: event::Event<()>| {
|
||||
let is_user = matches!(event, event::Event::UserEvent(()));
|
||||
let event2 = event.map_nonuser_event::<()>();
|
||||
if is_user {
|
||||
assert_eq!(event2, Err(event::Event::UserEvent(())));
|
||||
} else {
|
||||
assert!(event2.is_ok());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_force_normalize() {
|
||||
let force = event::Force::Normalized(0.0);
|
||||
assert_eq!(force.normalized(), 0.0);
|
||||
|
||||
let force2 = event::Force::Calibrated {
|
||||
force: 5.0,
|
||||
max_possible_force: 2.5,
|
||||
altitude_angle: None,
|
||||
};
|
||||
assert_eq!(force2.normalized(), 2.0);
|
||||
|
||||
let force3 = event::Force::Calibrated {
|
||||
force: 5.0,
|
||||
max_possible_force: 2.5,
|
||||
altitude_angle: Some(std::f64::consts::PI / 2.0),
|
||||
};
|
||||
assert_eq!(force3.normalized(), 2.0);
|
||||
}
|
||||
|
||||
#[allow(clippy::clone_on_copy)]
|
||||
#[test]
|
||||
fn ensure_attrs_do_not_panic() {
|
||||
foreach_event!(|event: event::Event<()>| {
|
||||
let _ = format!("{:?}", event);
|
||||
});
|
||||
let _ = event::StartCause::Init.clone();
|
||||
|
||||
let did = unsafe { crate::event::DeviceId::dummy() }.clone();
|
||||
HashSet::new().insert(did);
|
||||
let mut set = [did, did, did];
|
||||
set.sort_unstable();
|
||||
let mut set2 = BTreeSet::new();
|
||||
set2.insert(did);
|
||||
set2.insert(did);
|
||||
|
||||
HashSet::new().insert(event::TouchPhase::Started.clone());
|
||||
HashSet::new().insert(event::MouseButton::Left.clone());
|
||||
HashSet::new().insert(event::Ime::Enabled);
|
||||
|
||||
let _ = event::Touch {
|
||||
device_id: did,
|
||||
phase: event::TouchPhase::Started,
|
||||
location: (0.0, 0.0).into(),
|
||||
id: 0,
|
||||
force: Some(event::Force::Normalized(0.0)),
|
||||
}
|
||||
.clone();
|
||||
let _ = event::Force::Calibrated {
|
||||
force: 0.0,
|
||||
max_possible_force: 0.0,
|
||||
altitude_angle: None,
|
||||
}
|
||||
.clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
//! handle events.
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::{error, fmt};
|
||||
|
||||
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
|
||||
@@ -18,6 +18,7 @@ use std::time::{Duration, Instant};
|
||||
#[cfg(wasm_platform)]
|
||||
use web_time::{Duration, Instant};
|
||||
|
||||
use crate::error::EventLoopError;
|
||||
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
|
||||
|
||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
||||
@@ -87,23 +88,23 @@ impl<T> EventLoopBuilder<T> {
|
||||
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
|
||||
/// and only once per application.***
|
||||
///
|
||||
/// Attempting to create the event loop on a different thread, or multiple event loops in
|
||||
/// the same application, will panic. This restriction isn't
|
||||
/// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
|
||||
/// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed
|
||||
/// in the relevant [`platform`] module if the target platform supports creating an event loop on
|
||||
/// any thread.
|
||||
///
|
||||
/// Calling this function will result in display backend initialisation.
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// Attempting to create the event loop off the main thread will panic. This
|
||||
/// restriction isn't strictly necessary on all platforms, but is imposed to
|
||||
/// eliminate any nasty surprises when porting to platforms that require it.
|
||||
/// `EventLoopBuilderExt::any_thread` functions are exposed in the relevant
|
||||
/// [`platform`] module if the target platform supports creating an event
|
||||
/// loop on any thread.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux:** Backend type can be controlled using an environment variable
|
||||
/// `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
|
||||
/// If it is not set, winit will try to connect to a Wayland connection, and if that fails,
|
||||
/// will fall back on X11. If this variable is set with any other value, winit will panic.
|
||||
/// - **Android:** Must be configured with an `AndroidApp` from `android_main()` by calling
|
||||
/// [`.with_android_app(app)`] before calling `.build()`.
|
||||
/// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY`
|
||||
/// or `DISPLAY` respectively when building the event loop.
|
||||
/// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling
|
||||
/// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic.
|
||||
///
|
||||
/// [`platform`]: crate::platform
|
||||
#[cfg_attr(
|
||||
@@ -115,17 +116,17 @@ impl<T> EventLoopBuilder<T> {
|
||||
doc = "[`.with_android_app(app)`]: #only-available-on-android"
|
||||
)]
|
||||
#[inline]
|
||||
pub fn build(&mut self) -> EventLoop<T> {
|
||||
pub fn build(&mut self) -> Result<EventLoop<T>, EventLoopError> {
|
||||
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
|
||||
panic!("Creating EventLoop multiple times is not supported.");
|
||||
return Err(EventLoopError::RecreationAttempt);
|
||||
}
|
||||
|
||||
// Certain platforms accept a mutable reference in their API.
|
||||
#[allow(clippy::unnecessary_mut_passed)]
|
||||
EventLoop {
|
||||
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific),
|
||||
Ok(EventLoop {
|
||||
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific)?,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
@@ -148,7 +149,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||
|
||||
/// Set by the user callback given to the [`EventLoop::run`] method.
|
||||
///
|
||||
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted.
|
||||
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
|
||||
///
|
||||
/// Defaults to [`Poll`].
|
||||
///
|
||||
@@ -156,7 +157,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||
///
|
||||
/// Almost every change is persistent between multiple calls to the event loop closure within a
|
||||
/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset.
|
||||
/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will
|
||||
/// Changes are **not** persistent between multiple calls to `run_ondemand` - issuing a new call will
|
||||
/// reset the control flow to [`Poll`].
|
||||
///
|
||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
||||
@@ -180,7 +181,7 @@ pub enum ControlFlow {
|
||||
/// [`Poll`]: Self::Poll
|
||||
WaitUntil(Instant),
|
||||
|
||||
/// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set,
|
||||
/// Send a [`LoopExiting`] event and stop the event loop. This variant is *sticky* - once set,
|
||||
/// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
|
||||
/// result in the `control_flow` parameter being reset to `ExitWithCode`.
|
||||
///
|
||||
@@ -189,12 +190,12 @@ pub enum ControlFlow {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS / WASM:** The supplied exit code is unused.
|
||||
/// - **Android / iOS / Web:** The supplied exit code is unused.
|
||||
/// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used,
|
||||
/// which can cause surprises with negative exit values (`-42` would end up as `214`). See
|
||||
/// [`std::process::exit`].
|
||||
///
|
||||
/// [`LoopDestroyed`]: Event::LoopDestroyed
|
||||
/// [`LoopExiting`]: Event::LoopExiting
|
||||
/// [`Exit`]: ControlFlow::Exit
|
||||
ExitWithCode(i32),
|
||||
}
|
||||
@@ -268,42 +269,49 @@ impl EventLoop<()> {
|
||||
///
|
||||
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
|
||||
#[inline]
|
||||
pub fn new() -> EventLoop<()> {
|
||||
pub fn new() -> Result<EventLoop<()>, EventLoopError> {
|
||||
EventLoopBuilder::new().build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EventLoop<()> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoop<T> {
|
||||
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
|
||||
pub fn with_user_event() -> EventLoop<T> {
|
||||
pub fn with_user_event() -> Result<EventLoop<T>, EventLoopError> {
|
||||
EventLoopBuilder::<T>::with_user_event().build()
|
||||
}
|
||||
|
||||
/// Hijacks the calling thread and initializes the winit event loop with the provided
|
||||
/// closure. Since the closure is `'static`, it must be a `move` closure if it needs to
|
||||
/// Runs the event loop in the calling thread and calls the given `event_handler` closure
|
||||
/// to dispatch any pending events.
|
||||
///
|
||||
/// Since the closure is `'static`, it must be a `move` closure if it needs to
|
||||
/// access any data from the calling context.
|
||||
///
|
||||
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
|
||||
/// event loop's behavior.
|
||||
///
|
||||
/// Any values not passed to this function will *not* be dropped.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11 / Wayland:** The program terminates with exit code 1 if the display server
|
||||
/// disconnects.
|
||||
/// - **iOS:** Will never return to the caller and so values not passed to this function will
|
||||
/// *not* be dropped before the process exits.
|
||||
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript exception
|
||||
/// (that Rust doesn't see) that will also mean that the rest of the function is never executed
|
||||
/// and any values not passed to this function will *not* be dropped.
|
||||
///
|
||||
/// Web applications are recommended to use `spawn()` instead of `run()` to avoid the need
|
||||
/// for the Javascript exception trick, and to make it clearer that the event loop runs
|
||||
/// asynchronously (via the browser's own, internal, event loop) and doesn't block the
|
||||
/// current thread of execution like it does on other platforms.
|
||||
///
|
||||
/// This function won't be available with `target_feature = "exception-handling"`.
|
||||
///
|
||||
/// [`ControlFlow`]: crate::event_loop::ControlFlow
|
||||
#[inline]
|
||||
pub fn run<F>(self, event_handler: F) -> !
|
||||
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
|
||||
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(Event<T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
self.event_loop.run(event_handler)
|
||||
}
|
||||
@@ -437,3 +445,29 @@ pub enum DeviceEvents {
|
||||
/// Never capture device events.
|
||||
Never,
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
// TODO(kchibisov) remove `cfg` when the clipboard will be added.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) 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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
//
|
||||
// --------- END OF W3C SHORT NOTICE ---------------------------------------------------------------
|
||||
|
||||
use smol_str::SmolStr;
|
||||
pub use smol_str::SmolStr;
|
||||
|
||||
/// Contains the platform-native physical key identifier
|
||||
///
|
||||
@@ -135,7 +135,7 @@ impl std::fmt::Debug for NativeKeyCode {
|
||||
/// This enum is primarily used to store raw keysym when Winit doesn't map a given native logical
|
||||
/// key identifier to a meaningful [`Key`] variant. This lets you use [`Key`], and let the user
|
||||
/// define keybinds which work in the presence of identifiers we haven't mapped for you yet.
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum NativeKey {
|
||||
Unidentified,
|
||||
@@ -662,7 +662,7 @@ pub enum KeyCode {
|
||||
///
|
||||
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Key<Str = SmolStr> {
|
||||
/// A key string that corresponds to the character typed by the user, taking into account the
|
||||
|
||||
16
src/lib.rs
16
src/lib.rs
@@ -7,7 +7,7 @@
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use winit::event_loop::EventLoop;
|
||||
//! let event_loop = EventLoop::new();
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Once this is done there are two ways to create a [`Window`]:
|
||||
@@ -32,12 +32,12 @@
|
||||
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
|
||||
//! will run until the `control_flow` argument given to the closure is set to
|
||||
//! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which
|
||||
//! point [`Event`]`::`[`LoopDestroyed`] is emitted and the entire program terminates.
|
||||
//! point [`Event`]`::`[`LoopExiting`] is emitted and the entire program terminates.
|
||||
//!
|
||||
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
||||
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
|
||||
//! most other platforms. However, this model can be re-implemented to an extent with
|
||||
//! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why
|
||||
//! [`EventLoopExtPumpEvents::pump_events`]. See that method's documentation for more reasons about why
|
||||
//! it's discouraged, beyond compatibility reasons.
|
||||
//!
|
||||
//!
|
||||
@@ -48,7 +48,7 @@
|
||||
//! window::WindowBuilder,
|
||||
//! };
|
||||
//!
|
||||
//! let event_loop = EventLoop::new();
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
//!
|
||||
//! event_loop.run(move |event, _, control_flow| {
|
||||
@@ -69,7 +69,7 @@
|
||||
//! println!("The close button was pressed; stopping");
|
||||
//! control_flow.set_exit();
|
||||
//! },
|
||||
//! Event::MainEventsCleared => {
|
||||
//! Event::AboutToWait => {
|
||||
//! // Application update code.
|
||||
//!
|
||||
//! // Queue a RedrawRequested event.
|
||||
@@ -83,7 +83,7 @@
|
||||
//! // Redraw the application.
|
||||
//! //
|
||||
//! // It's preferable for applications that do not render continuously to render in
|
||||
//! // this event rather than in MainEventsCleared, since rendering in here allows
|
||||
//! // this event rather than in AboutToWait, since rendering in here allows
|
||||
//! // the program to gracefully handle redraws requested by the OS.
|
||||
//! },
|
||||
//! _ => ()
|
||||
@@ -109,7 +109,7 @@
|
||||
//! window visible only once you're ready to render into it.
|
||||
//!
|
||||
//! [`EventLoop`]: event_loop::EventLoop
|
||||
//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return
|
||||
//! [`EventLoopExtPumpEvents::pump_events`]: ./platform/pump_events/trait.EventLoopExtPumpEvents.html#tymethod.pump_events
|
||||
//! [`EventLoop::new()`]: event_loop::EventLoop::new
|
||||
//! [event_loop_run]: event_loop::EventLoop::run
|
||||
//! [`ControlFlow`]: event_loop::ControlFlow
|
||||
@@ -126,7 +126,7 @@
|
||||
//! [`WindowEvent`]: event::WindowEvent
|
||||
//! [`DeviceEvent`]: event::DeviceEvent
|
||||
//! [`UserEvent`]: event::Event::UserEvent
|
||||
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
|
||||
//! [`LoopExiting`]: event::Event::LoopExiting
|
||||
//! [`platform`]: platform
|
||||
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
|
||||
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
|
||||
|
||||
@@ -22,27 +22,6 @@ impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to iOS.
|
||||
pub trait WindowExtIOS {
|
||||
/// Returns a pointer to the [`UIWindow`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
///
|
||||
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
||||
fn ui_window(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the [`UIViewController`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
///
|
||||
/// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc
|
||||
fn ui_view_controller(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the [`UIView`] that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
///
|
||||
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
|
||||
fn ui_view(&self) -> *mut c_void;
|
||||
|
||||
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
|
||||
///
|
||||
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
||||
@@ -97,45 +76,35 @@ pub trait WindowExtIOS {
|
||||
}
|
||||
|
||||
impl WindowExtIOS for Window {
|
||||
#[inline]
|
||||
fn ui_window(&self) -> *mut c_void {
|
||||
self.window.ui_window()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ui_view_controller(&self) -> *mut c_void {
|
||||
self.window.ui_view_controller()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ui_view(&self) -> *mut c_void {
|
||||
self.window.ui_view()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_scale_factor(&self, scale_factor: f64) {
|
||||
self.window.set_scale_factor(scale_factor)
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
||||
self.window.set_valid_orientations(valid_orientations)
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||
self.window.set_prefers_home_indicator_hidden(hidden)
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
||||
self.window
|
||||
.set_preferred_screen_edges_deferring_system_gestures(edges)
|
||||
self.window.maybe_queue_on_main(move |w| {
|
||||
w.set_preferred_screen_edges_deferring_system_gestures(edges)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||
self.window.set_prefers_status_bar_hidden(hidden)
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,16 +10,6 @@ use crate::{
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to MacOS.
|
||||
pub trait WindowExtMacOS {
|
||||
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
fn ns_window(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the cocoa `NSView` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
fn ns_view(&self) -> *mut c_void;
|
||||
|
||||
/// Returns whether or not the window is in simple fullscreen mode.
|
||||
fn simple_fullscreen(&self) -> bool;
|
||||
|
||||
@@ -38,6 +28,28 @@ pub trait WindowExtMacOS {
|
||||
/// Sets whether or not the window has shadow.
|
||||
fn set_has_shadow(&self, has_shadow: bool);
|
||||
|
||||
/// Group windows together by using the same tabbing identifier.
|
||||
///
|
||||
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
|
||||
fn set_tabbing_identifier(&self, identifier: &str);
|
||||
|
||||
/// Returns the window's tabbing identifier.
|
||||
fn tabbing_identifier(&self) -> String;
|
||||
|
||||
/// Select next tab.
|
||||
fn select_next_tab(&self);
|
||||
|
||||
/// Select previous tab.
|
||||
fn select_previous_tab(&self);
|
||||
|
||||
/// Select the tab with the given index.
|
||||
///
|
||||
/// Will no-op when the index is out of bounds.
|
||||
fn select_tab_at_index(&self, index: usize);
|
||||
|
||||
/// Get the number of tabs in the window tab group.
|
||||
fn num_tabs(&self) -> usize;
|
||||
|
||||
/// Get the window's edit state.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -70,54 +82,80 @@ pub trait WindowExtMacOS {
|
||||
}
|
||||
|
||||
impl WindowExtMacOS for Window {
|
||||
#[inline]
|
||||
fn ns_window(&self) -> *mut c_void {
|
||||
self.window.ns_window()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ns_view(&self) -> *mut c_void {
|
||||
self.window.ns_view()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn simple_fullscreen(&self) -> bool {
|
||||
self.window.simple_fullscreen()
|
||||
self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
||||
self.window.set_simple_fullscreen(fullscreen)
|
||||
self.window
|
||||
.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn has_shadow(&self) -> bool {
|
||||
self.window.has_shadow()
|
||||
self.window.maybe_wait_on_main(|w| w.has_shadow())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_has_shadow(&self, has_shadow: bool) {
|
||||
self.window.set_has_shadow(has_shadow)
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_tabbing_identifier(&self, identifier: &str) {
|
||||
self.window
|
||||
.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn tabbing_identifier(&self) -> String {
|
||||
self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select_next_tab(&self) {
|
||||
self.window.maybe_queue_on_main(|w| w.select_next_tab())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select_previous_tab(&self) {
|
||||
self.window.maybe_queue_on_main(|w| w.select_previous_tab())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select_tab_at_index(&self, index: usize) {
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn num_tabs(&self) -> usize {
|
||||
self.window.maybe_wait_on_main(|w| w.num_tabs())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_document_edited(&self) -> bool {
|
||||
self.window.is_document_edited()
|
||||
self.window.maybe_wait_on_main(|w| w.is_document_edited())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_document_edited(&self, edited: bool) {
|
||||
self.window.set_document_edited(edited)
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.set_document_edited(edited))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
|
||||
self.window.set_option_as_alt(option_as_alt)
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn option_as_alt(&self) -> OptionAsAlt {
|
||||
self.window.option_as_alt()
|
||||
self.window.maybe_wait_on_main(|w| w.option_as_alt())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +199,10 @@ pub trait WindowBuilderExtMacOS {
|
||||
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
|
||||
/// Window accepts click-through mouse events.
|
||||
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
|
||||
/// Defines the window tabbing identifier.
|
||||
///
|
||||
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
|
||||
fn with_tabbing_identifier(self, identifier: &str) -> WindowBuilder;
|
||||
/// Set how the <kbd>Option</kbd> keys are interpreted.
|
||||
///
|
||||
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
|
||||
@@ -225,6 +267,14 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> WindowBuilder {
|
||||
self.platform_specific
|
||||
.tabbing_identifier
|
||||
.replace(tabbing_identifier.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
|
||||
self.platform_specific.option_as_alt = option_as_alt;
|
||||
@@ -329,6 +379,12 @@ pub trait EventLoopWindowTargetExtMacOS {
|
||||
fn hide_application(&self);
|
||||
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
|
||||
fn hide_other_applications(&self);
|
||||
/// Set whether the system can automatically organize windows into tabs.
|
||||
///
|
||||
/// <https://developer.apple.com/documentation/appkit/nswindow/1646657-allowsautomaticwindowtabbing>
|
||||
fn set_allows_automatic_window_tabbing(&self, enabled: bool);
|
||||
/// Returns whether the system can automatically organize windows into tabs.
|
||||
fn allows_automatic_window_tabbing(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
||||
@@ -339,6 +395,14 @@ impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
||||
fn hide_other_applications(&self) {
|
||||
self.p.hide_other_applications()
|
||||
}
|
||||
|
||||
fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
|
||||
self.p.set_allows_automatic_window_tabbing(enabled);
|
||||
}
|
||||
|
||||
fn allows_automatic_window_tabbing(&self) -> bool {
|
||||
self.p.allows_automatic_window_tabbing()
|
||||
}
|
||||
}
|
||||
|
||||
/// Option as alt behavior.
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
//! - `windows`
|
||||
//! - `web`
|
||||
//!
|
||||
//! And the following platform-specific module:
|
||||
//! And the following platform-specific modules:
|
||||
//!
|
||||
//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`)
|
||||
//! - `run_ondemand` (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.
|
||||
|
||||
@@ -23,6 +24,8 @@ pub mod ios;
|
||||
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)]
|
||||
@@ -32,14 +35,23 @@ pub mod windows;
|
||||
#[cfg(x11_platform)]
|
||||
pub mod x11;
|
||||
|
||||
pub mod modifier_supplement;
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
orbital_platform
|
||||
wayland_platform
|
||||
))]
|
||||
pub mod run_return;
|
||||
pub mod run_ondemand;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform
|
||||
))]
|
||||
pub mod pump_events;
|
||||
|
||||
pub mod modifier_supplement;
|
||||
pub mod scancode;
|
||||
|
||||
189
src/platform/pump_events.rs
Normal file
189
src/platform/pump_events.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||
};
|
||||
|
||||
/// The return status for `pump_events`
|
||||
pub enum PumpStatus {
|
||||
/// Continue running external loop.
|
||||
Continue,
|
||||
/// Exit external loop.
|
||||
Exit(i32),
|
||||
}
|
||||
|
||||
/// Additional methods on [`EventLoop`] for pumping events within an external event loop
|
||||
pub trait EventLoopExtPumpEvents {
|
||||
/// A type provided by the user that can be passed through [`Event::UserEvent`].
|
||||
type UserEvent;
|
||||
|
||||
/// Pump the `EventLoop` to check for and dispatch pending events.
|
||||
///
|
||||
/// This API is designed to enable applications to integrate Winit into an
|
||||
/// external event loop, for platforms that can support this.
|
||||
///
|
||||
/// The given `timeout` limits how long it may block waiting for new events.
|
||||
///
|
||||
/// Passing a `timeout` of `Some(Duration::ZERO)` would ensure your external
|
||||
/// event loop is never blocked but you would likely need to consider how
|
||||
/// to throttle your own external loop.
|
||||
///
|
||||
/// Passing a `timeout` of `None` means that it may wait indefinitely for new
|
||||
/// events before returning control back to the external loop.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # // Copied from examples/window_pump_events.rs
|
||||
/// # #[cfg(any(
|
||||
/// # windows_platform,
|
||||
/// # macos_platform,
|
||||
/// # x11_platform,
|
||||
/// # wayland_platform,
|
||||
/// # android_platform,
|
||||
/// # ))]
|
||||
/// fn main() -> std::process::ExitCode {
|
||||
/// # use std::{process::ExitCode, thread::sleep, time::Duration};
|
||||
/// #
|
||||
/// # use simple_logger::SimpleLogger;
|
||||
/// # use winit::{
|
||||
/// # event::{Event, WindowEvent},
|
||||
/// # event_loop::EventLoop,
|
||||
/// # platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
|
||||
/// # window::WindowBuilder,
|
||||
/// # };
|
||||
/// let mut event_loop = EventLoop::new().unwrap();
|
||||
/// #
|
||||
/// # SimpleLogger::new().init().unwrap();
|
||||
/// let window = WindowBuilder::new()
|
||||
/// .with_title("A fantastic window!")
|
||||
/// .build(&event_loop)
|
||||
/// .unwrap();
|
||||
///
|
||||
/// 'main: loop {
|
||||
/// let timeout = Some(Duration::ZERO);
|
||||
/// let status = event_loop.pump_events(timeout, |event, _, control_flow| {
|
||||
/// # if let Event::WindowEvent { event, .. } = &event {
|
||||
/// # // Print only Window events to reduce noise
|
||||
/// # println!("{event:?}");
|
||||
/// # }
|
||||
/// #
|
||||
/// match event {
|
||||
/// Event::WindowEvent {
|
||||
/// event: WindowEvent::CloseRequested,
|
||||
/// window_id,
|
||||
/// } if window_id == window.id() => control_flow.set_exit(),
|
||||
/// Event::AboutToWait => {
|
||||
/// window.request_redraw();
|
||||
/// }
|
||||
/// _ => (),
|
||||
/// }
|
||||
/// });
|
||||
/// if let PumpStatus::Exit(exit_code) = status {
|
||||
/// break 'main ExitCode::from(exit_code as u8);
|
||||
/// }
|
||||
///
|
||||
/// // Sleep for 1/60 second to simulate application work
|
||||
/// //
|
||||
/// // Since `pump_events` doesn't block it will be important to
|
||||
/// // throttle the loop in the app somehow.
|
||||
/// println!("Update()");
|
||||
/// sleep(Duration::from_millis(16));
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// **Note:** This is not a portable API, and its usage involves a number of
|
||||
/// caveats and trade offs that should be considered before using this API!
|
||||
///
|
||||
/// You almost certainly shouldn't use this API, unless you absolutely know it's
|
||||
/// the only practical option you have.
|
||||
///
|
||||
/// ## Synchronous events
|
||||
///
|
||||
/// Some events _must_ only be handled synchronously via the closure that
|
||||
/// is passed to Winit so that the handler will also be synchronized with
|
||||
/// the window system and operating system.
|
||||
///
|
||||
/// This is because some events are driven by a window system callback
|
||||
/// where the window systems expects the application to have handled the
|
||||
/// event before returning.
|
||||
///
|
||||
/// **These events can not be buffered and handled outside of the closure
|
||||
/// passed to Winit.**
|
||||
///
|
||||
/// As a general rule it is not recommended to ever buffer events to handle
|
||||
/// them outside of the closure passed to Winit since it's difficult to
|
||||
/// provide guarantees about which events are safe to buffer across all
|
||||
/// operating systems.
|
||||
///
|
||||
/// Notable events that will certainly create portability problems if
|
||||
/// buffered and handled outside of Winit include:
|
||||
/// - `RedrawRequested` events, used to schedule rendering.
|
||||
///
|
||||
/// macOS for example uses a `drawRect` callback to drive rendering
|
||||
/// within applications and expects rendering to be finished before
|
||||
/// the `drawRect` callback returns.
|
||||
///
|
||||
/// For portability it's strongly recommended that applications should
|
||||
/// keep their rendering inside the closure provided to Winit.
|
||||
/// - Any lifecycle events, such as `Suspended` / `Resumed`.
|
||||
///
|
||||
/// The handling of these events needs to be synchronized with the
|
||||
/// operating system and it would never be appropriate to buffer a
|
||||
/// notification that your application has been suspended or resumed and
|
||||
/// then handled that later since there would always be a chance that
|
||||
/// other lifecycle events occur while the event is buffered.
|
||||
///
|
||||
/// ## Supported Platforms
|
||||
/// - Windows
|
||||
/// - Linux
|
||||
/// - MacOS
|
||||
/// - Android
|
||||
///
|
||||
/// ## Unsupported Platforms
|
||||
/// - **Web:** This API is fundamentally incompatible with the event-based way in which
|
||||
/// Web browsers work because it's not possible to have a long-running external
|
||||
/// loop that would block the browser and there is nothing that can be
|
||||
/// polled to ask for new new events. Events are delivered via callbacks based
|
||||
/// on an event loop that is internal to the browser itself.
|
||||
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so
|
||||
/// there's no way to support the same approach to polling as on MacOS.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
/// - **Windows**: The implementation will use `PeekMessage` when checking for
|
||||
/// window messages to avoid blocking your external event loop.
|
||||
///
|
||||
/// - **MacOS**: The implementation works in terms of stopping the global `NSApp`
|
||||
/// whenever the application `RunLoop` indicates that it is preparing to block
|
||||
/// and wait for new events.
|
||||
///
|
||||
/// This is very different to the polling APIs that are available on other
|
||||
/// platforms (the lower level polling primitives on MacOS are private
|
||||
/// implementation details for `NSApp` which aren't accessible to application
|
||||
/// developers)
|
||||
///
|
||||
/// It's likely this will be less efficient than polling on other OSs and
|
||||
/// it also means the `NSApp` is stopped while outside of the Winit
|
||||
/// event loop - and that's observable (for example to crates like `rfd`)
|
||||
/// because the `NSApp` is global state.
|
||||
///
|
||||
/// If you render outside of Winit you are likely to see window resizing artifacts
|
||||
/// since MacOS expects applications to render synchronously during any `drawRect`
|
||||
/// callback.
|
||||
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
|
||||
type UserEvent = T;
|
||||
|
||||
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
|
||||
{
|
||||
self.event_loop.pump_events(timeout, event_handler)
|
||||
}
|
||||
}
|
||||
74
src/platform/run_ondemand.rs
Normal file
74
src/platform/run_ondemand.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use crate::{
|
||||
error::EventLoopError,
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||
};
|
||||
|
||||
#[cfg(doc)]
|
||||
use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
|
||||
|
||||
/// Additional methods on [`EventLoop`] to return control flow to the caller.
|
||||
pub trait EventLoopExtRunOnDemand {
|
||||
/// A type provided by the user that can be passed through [`Event::UserEvent`].
|
||||
type UserEvent;
|
||||
|
||||
/// Runs the event loop in the calling thread and calls the given `event_handler` closure
|
||||
/// to dispatch any window system events.
|
||||
///
|
||||
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures
|
||||
/// and it is possible to return control back to the caller without
|
||||
/// consuming the `EventLoop` (by setting the `control_flow` to [`ControlFlow::Exit`]) and
|
||||
/// so the event loop can be re-run after it has exit.
|
||||
///
|
||||
/// It's expected that each run of the loop will be for orthogonal instantiations of your
|
||||
/// Winit application, but internally each instantiation may re-use some common window
|
||||
/// system resources, such as a display server connection.
|
||||
///
|
||||
/// This API is not designed to run an event loop in bursts that you can exit from and return
|
||||
/// to while maintaining the full state of your application. (If you need something like this
|
||||
/// you can look at the [`EventLoopExtPumpEvents::pump_events()`] API)
|
||||
///
|
||||
/// Each time `run_ondemand` is called the `event_handler` can expect to receive a
|
||||
/// `NewEvents(Init)` and `Resumed` event (even on platforms that have no suspend/resume
|
||||
/// lifecycle) - which can be used to consistently initialize application state.
|
||||
///
|
||||
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
|
||||
/// event loop's behavior.
|
||||
///
|
||||
/// # Caveats
|
||||
/// - This extension isn't available on all platforms, since it's not always possible to
|
||||
/// return to the caller (specifically this is impossible on iOS and Web - though with
|
||||
/// the Web backend it is possible to use `spawn()` more than once instead).
|
||||
/// - No [`Window`] state can be carried between separate runs of the event loop.
|
||||
///
|
||||
/// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you specifically need
|
||||
/// the ability to re-run a single event loop more than once
|
||||
///
|
||||
/// # Supported Platforms
|
||||
/// - Windows
|
||||
/// - Linux
|
||||
/// - macOS
|
||||
/// - Android
|
||||
///
|
||||
/// # Unsupported Platforms
|
||||
/// - **Web:** This API is fundamentally incompatible with the event-based way in which
|
||||
/// Web browsers work because it's not possible to have a long-running external
|
||||
/// loop that would block the browser and there is nothing that can be
|
||||
/// polled to ask for new events. Events are delivered via callbacks based
|
||||
/// on an event loop that is internal to the browser itself.
|
||||
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS.
|
||||
fn run_ondemand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
|
||||
type UserEvent = T;
|
||||
|
||||
fn run_ondemand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
|
||||
{
|
||||
self.event_loop.run_ondemand(event_handler)
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
use crate::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||
};
|
||||
|
||||
/// Additional methods on [`EventLoop`] to return control flow to the caller.
|
||||
pub trait EventLoopExtRunReturn {
|
||||
/// A type provided by the user that can be passed through [`Event::UserEvent`].
|
||||
type UserEvent;
|
||||
|
||||
/// Initializes the `winit` event loop.
|
||||
///
|
||||
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures
|
||||
/// and returns control flow to the caller when `control_flow` is set to [`ControlFlow::Exit`].
|
||||
///
|
||||
/// # Caveats
|
||||
///
|
||||
/// Despite its appearance at first glance, this is *not* a perfect replacement for
|
||||
/// `poll_events`. For example, this function will not return on Windows or macOS while a
|
||||
/// window is getting resized, resulting in all application logic outside of the
|
||||
/// `event_handler` closure not running until the resize operation ends. Other OS operations
|
||||
/// may also result in such freezes. This behavior is caused by fundamental limitations in the
|
||||
/// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions.
|
||||
///
|
||||
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11 / Wayland:** This function returns `1` upon disconnection from
|
||||
/// the display server.
|
||||
fn run_return<F>(&mut self, event_handler: F) -> i32
|
||||
where
|
||||
F: FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtRunReturn for EventLoop<T> {
|
||||
type UserEvent = T;
|
||||
|
||||
fn run_return<F>(&mut self, event_handler: F) -> i32
|
||||
where
|
||||
F: FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
),
|
||||
{
|
||||
self.event_loop.run_return(event_handler)
|
||||
}
|
||||
}
|
||||
109
src/platform/startup_notify.rs
Normal file
109
src/platform/startup_notify.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
//! Window startup notification to handle window raising.
|
||||
//!
|
||||
//! The [`ActivationToken`] is essential to ensure that your newly
|
||||
//! created window will obtain the focus, otherwise the user could
|
||||
//! be requered to click on the window.
|
||||
//!
|
||||
//! Such token is usually delivered via the environment variable and
|
||||
//! could be read from it with the [`EventLoopExtStartupNotify::read_token_from_env`].
|
||||
//!
|
||||
//! Such token must also be reset after reading it from your environment with
|
||||
//! [`reset_activation_token_env`] otherwise child processes could inherit it.
|
||||
//!
|
||||
//! When starting a new child process with a newly obtained [`ActivationToken`] from
|
||||
//! [`WindowExtStartupNotify::request_activation_token`] the [`set_activation_token_env`]
|
||||
//! must be used to propagate it to the child
|
||||
//!
|
||||
//! To ensure the delivery of such token by other processes to you, the user should
|
||||
//! set `StartupNotify=true` inside the `.desktop` file of their application.
|
||||
//!
|
||||
//! The specification could be found [`here`].
|
||||
//!
|
||||
//! [`here`]: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt
|
||||
|
||||
use std::env;
|
||||
|
||||
use crate::error::NotSupportedError;
|
||||
use crate::event_loop::{AsyncRequestSerial, EventLoopWindowTarget};
|
||||
use crate::window::{ActivationToken, Window, WindowBuilder};
|
||||
|
||||
/// The variable which is used mostly on X11.
|
||||
const X11_VAR: &str = "DESKTOP_STARTUP_ID";
|
||||
|
||||
/// The variable which is used mostly on Wayland.
|
||||
const WAYLAND_VAR: &str = "XDG_ACTIVATION_TOKEN";
|
||||
|
||||
pub trait EventLoopExtStartupNotify {
|
||||
/// Read the token from the environment.
|
||||
///
|
||||
/// It's recommended **to unset** this environment variable for child processes.
|
||||
fn read_token_from_env(&self) -> Option<ActivationToken>;
|
||||
}
|
||||
|
||||
pub trait WindowExtStartupNotify {
|
||||
/// Request a new activation token.
|
||||
///
|
||||
/// The token will be delivered inside
|
||||
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError>;
|
||||
}
|
||||
|
||||
pub trait WindowBuilderExtStartupNotify {
|
||||
/// Use this [`ActivationToken`] during window creation.
|
||||
///
|
||||
/// Not using such a token upon a window could make your window not gaining
|
||||
/// focus until the user clicks on the window.
|
||||
fn with_activation_token(self, token: ActivationToken) -> Self;
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtStartupNotify for EventLoopWindowTarget<T> {
|
||||
fn read_token_from_env(&self) -> Option<ActivationToken> {
|
||||
match self.p {
|
||||
#[cfg(wayland_platform)]
|
||||
crate::platform_impl::EventLoopWindowTarget::Wayland(_) => env::var(WAYLAND_VAR),
|
||||
#[cfg(x11_platform)]
|
||||
crate::platform_impl::EventLoopWindowTarget::X(_) => env::var(X11_VAR),
|
||||
}
|
||||
.ok()
|
||||
.map(ActivationToken::_new)
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowExtStartupNotify for Window {
|
||||
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
|
||||
self.window.request_activation_token()
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowBuilderExtStartupNotify for WindowBuilder {
|
||||
fn with_activation_token(mut self, token: ActivationToken) -> Self {
|
||||
self.platform_specific.activation_token = Some(token);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the activation environment variables from the current process.
|
||||
///
|
||||
/// This is wise to do before running child processes,
|
||||
/// which may not to support the activation token.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// While the function is safe internally, it mutates the global environment
|
||||
/// state for the process, hence unsafe.
|
||||
pub unsafe fn reset_activation_token_env() {
|
||||
env::remove_var(X11_VAR);
|
||||
env::remove_var(WAYLAND_VAR);
|
||||
}
|
||||
|
||||
/// Set environment variables responsible for activation token.
|
||||
///
|
||||
/// This could be used before running daemon processes.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// While the function is safe internally, it mutates the global environment
|
||||
/// state for the process, hence unsafe.
|
||||
pub unsafe fn set_activation_token_env(token: ActivationToken) {
|
||||
env::set_var(X11_VAR, &token._token);
|
||||
env::set_var(WAYLAND_VAR, token._token);
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
use std::os::raw;
|
||||
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
use crate::{
|
||||
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
use crate::platform_impl::{
|
||||
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
|
||||
Window as LinuxWindow,
|
||||
};
|
||||
use crate::platform_impl::{ApplicationName, Backend};
|
||||
|
||||
pub use crate::window::Theme;
|
||||
|
||||
@@ -19,16 +12,6 @@ pub use crate::window::Theme;
|
||||
pub trait EventLoopWindowTargetExtWayland {
|
||||
/// True if the [`EventLoopWindowTarget`] uses Wayland.
|
||||
fn is_wayland(&self) -> bool;
|
||||
|
||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this
|
||||
/// [`EventLoopWindowTarget`].
|
||||
///
|
||||
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
|
||||
///
|
||||
/// [`EventLoop`]: crate::event_loop::EventLoop
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
|
||||
@@ -36,17 +19,6 @@ impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
|
||||
fn is_wayland(&self) -> bool {
|
||||
self.p.is_wayland()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.p {
|
||||
LinuxEventLoopWindowTarget::Wayland(ref p) => {
|
||||
Some(p.connection.display().id().as_ptr() as *mut _)
|
||||
}
|
||||
#[cfg(x11_platform)]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
|
||||
@@ -76,41 +48,9 @@ impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
|
||||
}
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to Wayland.
|
||||
pub trait WindowExtWayland {
|
||||
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
|
||||
pub trait WindowExtWayland {}
|
||||
|
||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
}
|
||||
|
||||
impl WindowExtWayland for Window {
|
||||
#[inline]
|
||||
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.surface().id().as_ptr() as *mut _),
|
||||
#[cfg(x11_platform)]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.display().id().as_ptr() as *mut _),
|
||||
#[cfg(x11_platform)]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl WindowExtWayland for Window {}
|
||||
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to Wayland.
|
||||
pub trait WindowBuilderExtWayland {
|
||||
|
||||
@@ -2,24 +2,58 @@
|
||||
//! allow end users to determine how the page should be laid out. Use the [`WindowExtWebSys`] trait
|
||||
//! to retrieve the canvas from the Window. Alternatively, use the [`WindowBuilderExtWebSys`] trait
|
||||
//! to provide your own canvas.
|
||||
//!
|
||||
//! It is recommended **not** to apply certain CSS properties to the canvas:
|
||||
//! - [`transform`]
|
||||
//! - [`border`]
|
||||
//! - [`padding`]
|
||||
//!
|
||||
//! The following APIs can't take them into account and will therefore provide inaccurate results:
|
||||
//! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`]
|
||||
//! - [`WindowEvent::Occluded`]
|
||||
//! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`],
|
||||
//! and [`WindowEvent::Touch`].
|
||||
//! - [`Window::set_outer_position()`]
|
||||
//!
|
||||
//! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized
|
||||
//! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size()
|
||||
//! [`WindowEvent::Occluded`]: crate::event::WindowEvent::Occluded
|
||||
//! [`WindowEvent::CursorMoved`]: crate::event::WindowEvent::CursorMoved
|
||||
//! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered
|
||||
//! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft
|
||||
//! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch
|
||||
//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position()
|
||||
//! [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
|
||||
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::ControlFlow;
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::event_loop::EventLoopWindowTarget;
|
||||
use crate::window::WindowBuilder;
|
||||
use crate::window::{Window, WindowBuilder};
|
||||
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
pub trait WindowExtWebSys {
|
||||
/// Only returns the canvas if called from inside the window.
|
||||
fn canvas(&self) -> Option<HtmlCanvasElement>;
|
||||
}
|
||||
|
||||
/// Whether the browser reports the preferred color scheme to be "dark".
|
||||
fn is_dark_mode(&self) -> bool;
|
||||
impl WindowExtWebSys for Window {
|
||||
#[inline]
|
||||
fn canvas(&self) -> Option<HtmlCanvasElement> {
|
||||
self.window.canvas()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WindowBuilderExtWebSys {
|
||||
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`](crate::window::Window). If
|
||||
/// [`None`], [`WindowBuilder::build()`] will create one.
|
||||
///
|
||||
/// In any case, the canvas won't be automatically inserted into the web page.
|
||||
///
|
||||
/// [`None`] by default.
|
||||
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
|
||||
|
||||
/// Whether `event.preventDefault` should be automatically called to prevent event propagation
|
||||
@@ -30,11 +64,20 @@ pub trait WindowBuilderExtWebSys {
|
||||
///
|
||||
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
|
||||
/// context menu with Shift+Rightclick.
|
||||
///
|
||||
/// Enabled by default.
|
||||
fn with_prevent_default(self, prevent_default: bool) -> Self;
|
||||
|
||||
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
|
||||
/// canvas keyboard events.
|
||||
///
|
||||
/// Enabled by default.
|
||||
fn with_focusable(self, focusable: bool) -> Self;
|
||||
|
||||
/// On window creation, append the canvas element to the web page if it isn't already.
|
||||
///
|
||||
/// Disabled by default.
|
||||
fn with_append(self, append: bool) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtWebSys for WindowBuilder {
|
||||
@@ -55,6 +98,12 @@ impl WindowBuilderExtWebSys for WindowBuilder {
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn with_append(mut self, append: bool) -> Self {
|
||||
self.platform_specific.append = append;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to the web.
|
||||
@@ -74,11 +123,7 @@ pub trait EventLoopExtWebSys {
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
);
|
||||
+ FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
@@ -87,11 +132,7 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
),
|
||||
+ FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
|
||||
{
|
||||
self.event_loop.spawn(event_handler)
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ pub type HWND = isize;
|
||||
pub type HMENU = isize;
|
||||
/// Monitor Handle type used by Win32 API
|
||||
pub type HMONITOR = isize;
|
||||
/// Instance Handle type used by Win32 API
|
||||
pub type HINSTANCE = isize;
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Windows.
|
||||
pub trait EventLoopBuilderExtWindows {
|
||||
@@ -113,13 +111,6 @@ impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
|
||||
|
||||
/// Additional methods on `Window` that are specific to Windows.
|
||||
pub trait WindowExtWindows {
|
||||
/// Returns the HINSTANCE of the window
|
||||
fn hinstance(&self) -> HINSTANCE;
|
||||
/// Returns the native handle that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the native window was destroyed.
|
||||
fn hwnd(&self) -> HWND;
|
||||
|
||||
/// Enables or disables mouse and keyboard input to the specified window.
|
||||
///
|
||||
/// A window must be enabled before it can be activated.
|
||||
@@ -148,16 +139,6 @@ pub trait WindowExtWindows {
|
||||
}
|
||||
|
||||
impl WindowExtWindows for Window {
|
||||
#[inline]
|
||||
fn hinstance(&self) -> HINSTANCE {
|
||||
self.window.hinstance()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hwnd(&self) -> HWND {
|
||||
self.window.hwnd()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_enable(&self, enabled: bool) {
|
||||
self.window.set_enable(enabled)
|
||||
@@ -223,6 +204,9 @@ pub trait WindowBuilderExtWindows {
|
||||
/// Whether show or hide the window icon in the taskbar.
|
||||
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
|
||||
|
||||
/// Customize the window class name.
|
||||
fn with_class_name<S: Into<String>>(self, class_name: S) -> WindowBuilder;
|
||||
|
||||
/// Shows or hides the background drop shadow for undecorated windows.
|
||||
///
|
||||
/// The shadow is hidden by default.
|
||||
@@ -267,6 +251,12 @@ impl WindowBuilderExtWindows for WindowBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_class_name<S: Into<String>>(mut self, class_name: S) -> WindowBuilder {
|
||||
self.platform_specific.class_name = class_name.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder {
|
||||
self.platform_specific.decoration_shadow = shadow;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
use std::os::raw;
|
||||
use std::ptr;
|
||||
|
||||
use crate::{
|
||||
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
@@ -8,9 +5,7 @@ use crate::{
|
||||
};
|
||||
|
||||
use crate::dpi::Size;
|
||||
use crate::platform_impl::{
|
||||
x11::ffi::XVisualInfo, ApplicationName, Backend, Window as LinuxWindow, XLIB_ERROR_HOOKS,
|
||||
};
|
||||
use crate::platform_impl::{ApplicationName, Backend, XLIB_ERROR_HOOKS};
|
||||
|
||||
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
|
||||
|
||||
@@ -22,6 +17,12 @@ pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupport
|
||||
pub type XlibErrorHook =
|
||||
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
|
||||
|
||||
/// A unique identifer for an X11 visual.
|
||||
pub type XVisualID = u32;
|
||||
|
||||
/// A unique identifier for an X11 window.
|
||||
pub type XWindow = u32;
|
||||
|
||||
/// Hook to winit's xlib error handling callback.
|
||||
///
|
||||
/// This method is provided as a safe way to handle the errors comming from X11
|
||||
@@ -81,70 +82,14 @@ impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
|
||||
}
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to X11.
|
||||
pub trait WindowExtX11 {
|
||||
/// Returns the ID of the [`Window`] xlib object that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
fn xlib_window(&self) -> Option<raw::c_ulong>;
|
||||
pub trait WindowExtX11 {}
|
||||
|
||||
/// Returns a pointer to the `Display` object of xlib that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
fn xlib_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
fn xlib_screen_id(&self) -> Option<raw::c_int>;
|
||||
|
||||
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
///
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
|
||||
}
|
||||
|
||||
impl WindowExtX11 for Window {
|
||||
#[inline]
|
||||
fn xlib_window(&self) -> Option<raw::c_ulong> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_window()),
|
||||
#[cfg(wayland_platform)]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn xlib_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_display()),
|
||||
#[cfg(wayland_platform)]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn xlib_screen_id(&self) -> Option<raw::c_int> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
|
||||
#[cfg(wayland_platform)]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
|
||||
#[cfg(wayland_platform)]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl WindowExtX11 for Window {}
|
||||
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to X11.
|
||||
pub trait WindowBuilderExtX11 {
|
||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
|
||||
/// Create this window with a specific X11 visual.
|
||||
fn with_x11_visual(self, visual_id: XVisualID) -> Self;
|
||||
|
||||
fn with_x11_screen(self, screen_id: i32) -> Self;
|
||||
|
||||
@@ -176,21 +121,35 @@ pub trait WindowBuilderExtX11 {
|
||||
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
|
||||
/// ```
|
||||
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
|
||||
|
||||
/// Embed this window into another parent window.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use winit::window::WindowBuilder;
|
||||
/// use winit::platform::x11::{XWindow, WindowBuilderExtX11};
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let event_loop = winit::event_loop::EventLoop::new().unwrap();
|
||||
/// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?;
|
||||
/// let window = WindowBuilder::new()
|
||||
/// .with_embed_parent_window(parent_window_id)
|
||||
/// .build(&event_loop)?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
fn with_embed_parent_window(self, parent_window_id: XWindow) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtX11 for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
|
||||
{
|
||||
self.platform_specific.visual_infos =
|
||||
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
|
||||
}
|
||||
fn with_x11_visual(mut self, visual_id: XVisualID) -> Self {
|
||||
self.platform_specific.x11.visual_id = Some(visual_id);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_x11_screen(mut self, screen_id: i32) -> Self {
|
||||
self.platform_specific.screen_id = Some(screen_id);
|
||||
self.platform_specific.x11.screen_id = Some(screen_id);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -202,19 +161,25 @@ impl WindowBuilderExtX11 for WindowBuilder {
|
||||
|
||||
#[inline]
|
||||
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
|
||||
self.platform_specific.override_redirect = override_redirect;
|
||||
self.platform_specific.x11.override_redirect = override_redirect;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
|
||||
self.platform_specific.x11_window_types = x11_window_types;
|
||||
self.platform_specific.x11.x11_window_types = x11_window_types;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
|
||||
self.platform_specific.base_size = Some(base_size.into());
|
||||
self.platform_specific.x11.base_size = Some(base_size.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self {
|
||||
self.platform_specific.x11.embed_window = Some(parent_window_id);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use android_activity::input::Keycode;
|
||||
use android_activity::{
|
||||
input::{KeyAction, KeyEvent, KeyMapChar, Keycode},
|
||||
AndroidApp,
|
||||
};
|
||||
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
|
||||
|
||||
@@ -156,324 +159,405 @@ pub fn to_physical_keycode(keycode: Keycode) -> KeyCode {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We need to expose getUnicodeChar via android-activity instead of having
|
||||
// a fixed mapping from key codes
|
||||
pub fn to_logical(keycode: Keycode, native: NativeKey) -> Key {
|
||||
/// Tries to map the `key_event` to a `KeyMapChar` containing a unicode character or dead key accent
|
||||
///
|
||||
/// This takes a `KeyEvent` and looks up its corresponding `KeyCharacterMap` and
|
||||
/// uses that to try and map the `key_code` + `meta_state` to a unicode
|
||||
/// character or a dead key that can be combined with the next key press.
|
||||
pub fn character_map_and_combine_key(
|
||||
app: &AndroidApp,
|
||||
key_event: &KeyEvent<'_>,
|
||||
combining_accent: &mut Option<char>,
|
||||
) -> Option<KeyMapChar> {
|
||||
let device_id = key_event.device_id();
|
||||
|
||||
let key_map = match app.device_key_character_map(device_id) {
|
||||
Ok(key_map) => key_map,
|
||||
Err(err) => {
|
||||
log::warn!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
match key_map.get(key_event.key_code(), key_event.meta_state()) {
|
||||
Ok(KeyMapChar::Unicode(unicode)) => {
|
||||
// Only do dead key combining on key down
|
||||
if key_event.action() == KeyAction::Down {
|
||||
let combined_unicode = if let Some(accent) = combining_accent {
|
||||
match key_map.get_dead_char(*accent, unicode) {
|
||||
Ok(Some(key)) => Some(key),
|
||||
Ok(None) => None,
|
||||
Err(err) => {
|
||||
log::warn!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Some(unicode)
|
||||
};
|
||||
*combining_accent = None;
|
||||
combined_unicode.map(KeyMapChar::Unicode)
|
||||
} else {
|
||||
Some(KeyMapChar::Unicode(unicode))
|
||||
}
|
||||
}
|
||||
Ok(KeyMapChar::CombiningAccent(accent)) => {
|
||||
if key_event.action() == KeyAction::Down {
|
||||
*combining_accent = Some(accent);
|
||||
}
|
||||
Some(KeyMapChar::CombiningAccent(accent))
|
||||
}
|
||||
Ok(KeyMapChar::None) => {
|
||||
// Leave any combining_accent state in tact (seems to match how other
|
||||
// Android apps work)
|
||||
None
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("KeyEvent: Failed to get key map character: {err:?}");
|
||||
*combining_accent = None;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
|
||||
use android_activity::input::Keycode::*;
|
||||
|
||||
match keycode {
|
||||
Unknown => Key::Unidentified(native),
|
||||
let native = NativeKey::Android(keycode.into());
|
||||
|
||||
// Can be added on demand
|
||||
SoftLeft => Key::Unidentified(native),
|
||||
SoftRight => Key::Unidentified(native),
|
||||
match key_char {
|
||||
Some(KeyMapChar::Unicode(c)) => {
|
||||
Key::Character(smol_str::SmolStr::from_iter([c].into_iter()))
|
||||
}
|
||||
Some(KeyMapChar::CombiningAccent(c)) => Key::Dead(Some(c)),
|
||||
None | Some(KeyMapChar::None) => match keycode {
|
||||
// Using `BrowserHome` instead of `GoHome` according to
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
||||
Home => Key::BrowserHome,
|
||||
Back => Key::BrowserBack,
|
||||
Call => Key::Call,
|
||||
Endcall => Key::EndCall,
|
||||
|
||||
// Using `BrowserHome` instead of `GoHome` according to
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
||||
Home => Key::BrowserHome,
|
||||
Back => Key::BrowserBack,
|
||||
Call => Key::Call,
|
||||
Endcall => Key::EndCall,
|
||||
//-------------------------------------------------------------------------------
|
||||
// These should be redundant because they should have already been matched
|
||||
// as `KeyMapChar::Unicode`, but also matched here as a fallback
|
||||
Keycode0 => Key::Character("0".into()),
|
||||
Keycode1 => Key::Character("1".into()),
|
||||
Keycode2 => Key::Character("2".into()),
|
||||
Keycode3 => Key::Character("3".into()),
|
||||
Keycode4 => Key::Character("4".into()),
|
||||
Keycode5 => Key::Character("5".into()),
|
||||
Keycode6 => Key::Character("6".into()),
|
||||
Keycode7 => Key::Character("7".into()),
|
||||
Keycode8 => Key::Character("8".into()),
|
||||
Keycode9 => Key::Character("9".into()),
|
||||
Star => Key::Character("*".into()),
|
||||
Pound => Key::Character("#".into()),
|
||||
A => Key::Character("a".into()),
|
||||
B => Key::Character("b".into()),
|
||||
C => Key::Character("c".into()),
|
||||
D => Key::Character("d".into()),
|
||||
E => Key::Character("e".into()),
|
||||
F => Key::Character("f".into()),
|
||||
G => Key::Character("g".into()),
|
||||
H => Key::Character("h".into()),
|
||||
I => Key::Character("i".into()),
|
||||
J => Key::Character("j".into()),
|
||||
K => Key::Character("k".into()),
|
||||
L => Key::Character("l".into()),
|
||||
M => Key::Character("m".into()),
|
||||
N => Key::Character("n".into()),
|
||||
O => Key::Character("o".into()),
|
||||
P => Key::Character("p".into()),
|
||||
Q => Key::Character("q".into()),
|
||||
R => Key::Character("r".into()),
|
||||
S => Key::Character("s".into()),
|
||||
T => Key::Character("t".into()),
|
||||
U => Key::Character("u".into()),
|
||||
V => Key::Character("v".into()),
|
||||
W => Key::Character("w".into()),
|
||||
X => Key::Character("x".into()),
|
||||
Y => Key::Character("y".into()),
|
||||
Z => Key::Character("z".into()),
|
||||
Comma => Key::Character(",".into()),
|
||||
Period => Key::Character(".".into()),
|
||||
Grave => Key::Character("`".into()),
|
||||
Minus => Key::Character("-".into()),
|
||||
Equals => Key::Character("=".into()),
|
||||
LeftBracket => Key::Character("[".into()),
|
||||
RightBracket => Key::Character("]".into()),
|
||||
Backslash => Key::Character("\\".into()),
|
||||
Semicolon => Key::Character(";".into()),
|
||||
Apostrophe => Key::Character("'".into()),
|
||||
Slash => Key::Character("/".into()),
|
||||
At => Key::Character("@".into()),
|
||||
Plus => Key::Character("+".into()),
|
||||
//-------------------------------------------------------------------------------
|
||||
DpadUp => Key::ArrowUp,
|
||||
DpadDown => Key::ArrowDown,
|
||||
DpadLeft => Key::ArrowLeft,
|
||||
DpadRight => Key::ArrowRight,
|
||||
DpadCenter => Key::Enter,
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// Reporting unidentified, because the specific character is layout dependent.
|
||||
// (I'm not sure though)
|
||||
Keycode0 => Key::Character("0".into()),
|
||||
Keycode1 => Key::Character("1".into()),
|
||||
Keycode2 => Key::Character("2".into()),
|
||||
Keycode3 => Key::Character("3".into()),
|
||||
Keycode4 => Key::Character("4".into()),
|
||||
Keycode5 => Key::Character("5".into()),
|
||||
Keycode6 => Key::Character("6".into()),
|
||||
Keycode7 => Key::Character("7".into()),
|
||||
Keycode8 => Key::Character("8".into()),
|
||||
Keycode9 => Key::Character("9".into()),
|
||||
Star => Key::Character("*".into()),
|
||||
Pound => Key::Character("#".into()),
|
||||
A => Key::Character("a".into()),
|
||||
B => Key::Character("b".into()),
|
||||
C => Key::Character("c".into()),
|
||||
D => Key::Character("d".into()),
|
||||
E => Key::Character("e".into()),
|
||||
F => Key::Character("f".into()),
|
||||
G => Key::Character("g".into()),
|
||||
H => Key::Character("h".into()),
|
||||
I => Key::Character("i".into()),
|
||||
J => Key::Character("j".into()),
|
||||
K => Key::Character("k".into()),
|
||||
L => Key::Character("l".into()),
|
||||
M => Key::Character("m".into()),
|
||||
N => Key::Character("n".into()),
|
||||
O => Key::Character("o".into()),
|
||||
P => Key::Character("p".into()),
|
||||
Q => Key::Character("q".into()),
|
||||
R => Key::Character("r".into()),
|
||||
S => Key::Character("s".into()),
|
||||
T => Key::Character("t".into()),
|
||||
U => Key::Character("u".into()),
|
||||
V => Key::Character("v".into()),
|
||||
W => Key::Character("w".into()),
|
||||
X => Key::Character("x".into()),
|
||||
Y => Key::Character("y".into()),
|
||||
Z => Key::Character("z".into()),
|
||||
Comma => Key::Character(",".into()),
|
||||
Period => Key::Character(".".into()),
|
||||
Grave => Key::Character("`".into()),
|
||||
Minus => Key::Character("-".into()),
|
||||
Equals => Key::Character("=".into()),
|
||||
LeftBracket => Key::Character("[".into()),
|
||||
RightBracket => Key::Character("]".into()),
|
||||
Backslash => Key::Character("\\".into()),
|
||||
Semicolon => Key::Character(";".into()),
|
||||
Apostrophe => Key::Character("'".into()),
|
||||
Slash => Key::Character("/".into()),
|
||||
At => Key::Character("@".into()),
|
||||
Plus => Key::Character("+".into()),
|
||||
//-------------------------------------------------------------------------------
|
||||
DpadUp => Key::ArrowUp,
|
||||
DpadDown => Key::ArrowDown,
|
||||
DpadLeft => Key::ArrowLeft,
|
||||
DpadRight => Key::ArrowRight,
|
||||
DpadCenter => Key::Enter,
|
||||
VolumeUp => Key::AudioVolumeUp,
|
||||
VolumeDown => Key::AudioVolumeDown,
|
||||
Power => Key::Power,
|
||||
Camera => Key::Camera,
|
||||
Clear => Key::Clear,
|
||||
|
||||
VolumeUp => Key::AudioVolumeUp,
|
||||
VolumeDown => Key::AudioVolumeDown,
|
||||
Power => Key::Power,
|
||||
Camera => Key::Camera,
|
||||
Clear => Key::Clear,
|
||||
AltLeft => Key::Alt,
|
||||
AltRight => Key::Alt,
|
||||
ShiftLeft => Key::Shift,
|
||||
ShiftRight => Key::Shift,
|
||||
Tab => Key::Tab,
|
||||
Space => Key::Space,
|
||||
Sym => Key::Symbol,
|
||||
Explorer => Key::LaunchWebBrowser,
|
||||
Envelope => Key::LaunchMail,
|
||||
Enter => Key::Enter,
|
||||
Del => Key::Backspace,
|
||||
|
||||
AltLeft => Key::Alt,
|
||||
AltRight => Key::Alt,
|
||||
ShiftLeft => Key::Shift,
|
||||
ShiftRight => Key::Shift,
|
||||
Tab => Key::Tab,
|
||||
Space => Key::Space,
|
||||
Sym => Key::Symbol,
|
||||
Explorer => Key::LaunchWebBrowser,
|
||||
Envelope => Key::LaunchMail,
|
||||
Enter => Key::Enter,
|
||||
Del => Key::Backspace,
|
||||
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
|
||||
Num => Key::Alt,
|
||||
|
||||
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
|
||||
Num => Key::Alt,
|
||||
Headsethook => Key::HeadsetHook,
|
||||
Focus => Key::CameraFocus,
|
||||
|
||||
Headsethook => Key::HeadsetHook,
|
||||
Focus => Key::CameraFocus,
|
||||
Notification => Key::Notification,
|
||||
Search => Key::BrowserSearch,
|
||||
MediaPlayPause => Key::MediaPlayPause,
|
||||
MediaStop => Key::MediaStop,
|
||||
MediaNext => Key::MediaTrackNext,
|
||||
MediaPrevious => Key::MediaTrackPrevious,
|
||||
MediaRewind => Key::MediaRewind,
|
||||
MediaFastForward => Key::MediaFastForward,
|
||||
Mute => Key::MicrophoneVolumeMute,
|
||||
PageUp => Key::PageUp,
|
||||
PageDown => Key::PageDown,
|
||||
|
||||
Menu => Key::Unidentified(native),
|
||||
Escape => Key::Escape,
|
||||
ForwardDel => Key::Delete,
|
||||
CtrlLeft => Key::Control,
|
||||
CtrlRight => Key::Control,
|
||||
CapsLock => Key::CapsLock,
|
||||
ScrollLock => Key::ScrollLock,
|
||||
MetaLeft => Key::Super,
|
||||
MetaRight => Key::Super,
|
||||
Function => Key::Fn,
|
||||
Sysrq => Key::PrintScreen,
|
||||
Break => Key::Pause,
|
||||
MoveHome => Key::Home,
|
||||
MoveEnd => Key::End,
|
||||
Insert => Key::Insert,
|
||||
Forward => Key::BrowserForward,
|
||||
MediaPlay => Key::MediaPlay,
|
||||
MediaPause => Key::MediaPause,
|
||||
MediaClose => Key::MediaClose,
|
||||
MediaEject => Key::Eject,
|
||||
MediaRecord => Key::MediaRecord,
|
||||
F1 => Key::F1,
|
||||
F2 => Key::F2,
|
||||
F3 => Key::F3,
|
||||
F4 => Key::F4,
|
||||
F5 => Key::F5,
|
||||
F6 => Key::F6,
|
||||
F7 => Key::F7,
|
||||
F8 => Key::F8,
|
||||
F9 => Key::F9,
|
||||
F10 => Key::F10,
|
||||
F11 => Key::F11,
|
||||
F12 => Key::F12,
|
||||
NumLock => Key::NumLock,
|
||||
Numpad0 => Key::Character("0".into()),
|
||||
Numpad1 => Key::Character("1".into()),
|
||||
Numpad2 => Key::Character("2".into()),
|
||||
Numpad3 => Key::Character("3".into()),
|
||||
Numpad4 => Key::Character("4".into()),
|
||||
Numpad5 => Key::Character("5".into()),
|
||||
Numpad6 => Key::Character("6".into()),
|
||||
Numpad7 => Key::Character("7".into()),
|
||||
Numpad8 => Key::Character("8".into()),
|
||||
Numpad9 => Key::Character("9".into()),
|
||||
NumpadDivide => Key::Character("/".into()),
|
||||
NumpadMultiply => Key::Character("*".into()),
|
||||
NumpadSubtract => Key::Character("-".into()),
|
||||
NumpadAdd => Key::Character("+".into()),
|
||||
NumpadDot => Key::Character(".".into()),
|
||||
NumpadComma => Key::Character(",".into()),
|
||||
NumpadEnter => Key::Enter,
|
||||
NumpadEquals => Key::Character("=".into()),
|
||||
NumpadLeftParen => Key::Character("(".into()),
|
||||
NumpadRightParen => Key::Character(")".into()),
|
||||
|
||||
Notification => Key::Notification,
|
||||
Search => Key::BrowserSearch,
|
||||
MediaPlayPause => Key::MediaPlayPause,
|
||||
MediaStop => Key::MediaStop,
|
||||
MediaNext => Key::MediaTrackNext,
|
||||
MediaPrevious => Key::MediaTrackPrevious,
|
||||
MediaRewind => Key::MediaRewind,
|
||||
MediaFastForward => Key::MediaFastForward,
|
||||
Mute => Key::MicrophoneVolumeMute,
|
||||
PageUp => Key::PageUp,
|
||||
PageDown => Key::PageDown,
|
||||
Pictsymbols => Key::Unidentified(native),
|
||||
SwitchCharset => Key::Unidentified(native),
|
||||
VolumeMute => Key::AudioVolumeMute,
|
||||
Info => Key::Info,
|
||||
ChannelUp => Key::ChannelUp,
|
||||
ChannelDown => Key::ChannelDown,
|
||||
ZoomIn => Key::ZoomIn,
|
||||
ZoomOut => Key::ZoomOut,
|
||||
Tv => Key::TV,
|
||||
Guide => Key::Guide,
|
||||
Dvr => Key::DVR,
|
||||
Bookmark => Key::BrowserFavorites,
|
||||
Captions => Key::ClosedCaptionToggle,
|
||||
Settings => Key::Settings,
|
||||
TvPower => Key::TVPower,
|
||||
TvInput => Key::TVInput,
|
||||
StbPower => Key::STBPower,
|
||||
StbInput => Key::STBInput,
|
||||
AvrPower => Key::AVRPower,
|
||||
AvrInput => Key::AVRInput,
|
||||
ProgRed => Key::ColorF0Red,
|
||||
ProgGreen => Key::ColorF1Green,
|
||||
ProgYellow => Key::ColorF2Yellow,
|
||||
ProgBlue => Key::ColorF3Blue,
|
||||
AppSwitch => Key::AppSwitch,
|
||||
LanguageSwitch => Key::GroupNext,
|
||||
MannerMode => Key::MannerMode,
|
||||
Keycode3dMode => Key::TV3DMode,
|
||||
Contacts => Key::LaunchContacts,
|
||||
Calendar => Key::LaunchCalendar,
|
||||
Music => Key::LaunchMusicPlayer,
|
||||
Calculator => Key::LaunchApplication2,
|
||||
ZenkakuHankaku => Key::ZenkakuHankaku,
|
||||
Eisu => Key::Eisu,
|
||||
Muhenkan => Key::NonConvert,
|
||||
Henkan => Key::Convert,
|
||||
KatakanaHiragana => Key::HiraganaKatakana,
|
||||
Kana => Key::KanjiMode,
|
||||
BrightnessDown => Key::BrightnessDown,
|
||||
BrightnessUp => Key::BrightnessUp,
|
||||
MediaAudioTrack => Key::MediaAudioTrack,
|
||||
Sleep => Key::Standby,
|
||||
Wakeup => Key::WakeUp,
|
||||
Pairing => Key::Pairing,
|
||||
MediaTopMenu => Key::MediaTopMenu,
|
||||
LastChannel => Key::MediaLast,
|
||||
TvDataService => Key::TVDataService,
|
||||
VoiceAssist => Key::VoiceDial,
|
||||
TvRadioService => Key::TVRadioService,
|
||||
TvTeletext => Key::Teletext,
|
||||
TvNumberEntry => Key::TVNumberEntry,
|
||||
TvTerrestrialAnalog => Key::TVTerrestrialAnalog,
|
||||
TvTerrestrialDigital => Key::TVTerrestrialDigital,
|
||||
TvSatellite => Key::TVSatellite,
|
||||
TvSatelliteBs => Key::TVSatelliteBS,
|
||||
TvSatelliteCs => Key::TVSatelliteCS,
|
||||
TvSatelliteService => Key::TVSatelliteToggle,
|
||||
TvNetwork => Key::TVNetwork,
|
||||
TvAntennaCable => Key::TVAntennaCable,
|
||||
TvInputHdmi1 => Key::TVInputHDMI1,
|
||||
TvInputHdmi2 => Key::TVInputHDMI2,
|
||||
TvInputHdmi3 => Key::TVInputHDMI3,
|
||||
TvInputHdmi4 => Key::TVInputHDMI4,
|
||||
TvInputComposite1 => Key::TVInputComposite1,
|
||||
TvInputComposite2 => Key::TVInputComposite2,
|
||||
TvInputComponent1 => Key::TVInputComponent1,
|
||||
TvInputComponent2 => Key::TVInputComponent2,
|
||||
TvInputVga1 => Key::TVInputVGA1,
|
||||
TvAudioDescription => Key::TVAudioDescription,
|
||||
TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp,
|
||||
TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown,
|
||||
TvZoomMode => Key::ZoomToggle,
|
||||
TvContentsMenu => Key::TVContentsMenu,
|
||||
TvMediaContextMenu => Key::TVMediaContext,
|
||||
TvTimerProgramming => Key::TVTimer,
|
||||
Help => Key::Help,
|
||||
NavigatePrevious => Key::NavigatePrevious,
|
||||
NavigateNext => Key::NavigateNext,
|
||||
NavigateIn => Key::NavigateIn,
|
||||
NavigateOut => Key::NavigateOut,
|
||||
MediaSkipForward => Key::MediaSkipForward,
|
||||
MediaSkipBackward => Key::MediaSkipBackward,
|
||||
MediaStepForward => Key::MediaStepForward,
|
||||
MediaStepBackward => Key::MediaStepBackward,
|
||||
Cut => Key::Cut,
|
||||
Copy => Key::Copy,
|
||||
Paste => Key::Paste,
|
||||
Refresh => Key::BrowserRefresh,
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Gamepad events should be exposed through a separate API, not
|
||||
// keyboard events
|
||||
ButtonA => Key::Unidentified(native),
|
||||
ButtonB => Key::Unidentified(native),
|
||||
ButtonC => Key::Unidentified(native),
|
||||
ButtonX => Key::Unidentified(native),
|
||||
ButtonY => Key::Unidentified(native),
|
||||
ButtonZ => Key::Unidentified(native),
|
||||
ButtonL1 => Key::Unidentified(native),
|
||||
ButtonR1 => Key::Unidentified(native),
|
||||
ButtonL2 => Key::Unidentified(native),
|
||||
ButtonR2 => Key::Unidentified(native),
|
||||
ButtonThumbl => Key::Unidentified(native),
|
||||
ButtonThumbr => Key::Unidentified(native),
|
||||
ButtonStart => Key::Unidentified(native),
|
||||
ButtonSelect => Key::Unidentified(native),
|
||||
ButtonMode => Key::Unidentified(native),
|
||||
// -----------------------------------------------------------------
|
||||
Escape => Key::Escape,
|
||||
ForwardDel => Key::Delete,
|
||||
CtrlLeft => Key::Control,
|
||||
CtrlRight => Key::Control,
|
||||
CapsLock => Key::CapsLock,
|
||||
ScrollLock => Key::ScrollLock,
|
||||
MetaLeft => Key::Super,
|
||||
MetaRight => Key::Super,
|
||||
Function => Key::Fn,
|
||||
Sysrq => Key::PrintScreen,
|
||||
Break => Key::Pause,
|
||||
MoveHome => Key::Home,
|
||||
MoveEnd => Key::End,
|
||||
Insert => Key::Insert,
|
||||
Forward => Key::BrowserForward,
|
||||
MediaPlay => Key::MediaPlay,
|
||||
MediaPause => Key::MediaPause,
|
||||
MediaClose => Key::MediaClose,
|
||||
MediaEject => Key::Eject,
|
||||
MediaRecord => Key::MediaRecord,
|
||||
F1 => Key::F1,
|
||||
F2 => Key::F2,
|
||||
F3 => Key::F3,
|
||||
F4 => Key::F4,
|
||||
F5 => Key::F5,
|
||||
F6 => Key::F6,
|
||||
F7 => Key::F7,
|
||||
F8 => Key::F8,
|
||||
F9 => Key::F9,
|
||||
F10 => Key::F10,
|
||||
F11 => Key::F11,
|
||||
F12 => Key::F12,
|
||||
NumLock => Key::NumLock,
|
||||
Numpad0 => Key::Character("0".into()),
|
||||
Numpad1 => Key::Character("1".into()),
|
||||
Numpad2 => Key::Character("2".into()),
|
||||
Numpad3 => Key::Character("3".into()),
|
||||
Numpad4 => Key::Character("4".into()),
|
||||
Numpad5 => Key::Character("5".into()),
|
||||
Numpad6 => Key::Character("6".into()),
|
||||
Numpad7 => Key::Character("7".into()),
|
||||
Numpad8 => Key::Character("8".into()),
|
||||
Numpad9 => Key::Character("9".into()),
|
||||
NumpadDivide => Key::Character("/".into()),
|
||||
NumpadMultiply => Key::Character("*".into()),
|
||||
NumpadSubtract => Key::Character("-".into()),
|
||||
NumpadAdd => Key::Character("+".into()),
|
||||
NumpadDot => Key::Character(".".into()),
|
||||
NumpadComma => Key::Character(",".into()),
|
||||
NumpadEnter => Key::Enter,
|
||||
NumpadEquals => Key::Character("=".into()),
|
||||
NumpadLeftParen => Key::Character("(".into()),
|
||||
NumpadRightParen => Key::Character(")".into()),
|
||||
// -----------------------------------------------------------------
|
||||
// Keycodes that don't have a logical Key mapping
|
||||
// -----------------------------------------------------------------
|
||||
Unknown => Key::Unidentified(native),
|
||||
|
||||
VolumeMute => Key::AudioVolumeMute,
|
||||
Info => Key::Info,
|
||||
ChannelUp => Key::ChannelUp,
|
||||
ChannelDown => Key::ChannelDown,
|
||||
ZoomIn => Key::ZoomIn,
|
||||
ZoomOut => Key::ZoomOut,
|
||||
Tv => Key::TV,
|
||||
Window => Key::Unidentified(native),
|
||||
Guide => Key::Guide,
|
||||
Dvr => Key::DVR,
|
||||
Bookmark => Key::BrowserFavorites,
|
||||
Captions => Key::ClosedCaptionToggle,
|
||||
Settings => Key::Settings,
|
||||
TvPower => Key::TVPower,
|
||||
TvInput => Key::TVInput,
|
||||
StbPower => Key::STBPower,
|
||||
StbInput => Key::STBInput,
|
||||
AvrPower => Key::AVRPower,
|
||||
AvrInput => Key::AVRInput,
|
||||
ProgRed => Key::ColorF0Red,
|
||||
ProgGreen => Key::ColorF1Green,
|
||||
ProgYellow => Key::ColorF2Yellow,
|
||||
ProgBlue => Key::ColorF3Blue,
|
||||
AppSwitch => Key::AppSwitch,
|
||||
Button1 => Key::Unidentified(native),
|
||||
Button2 => Key::Unidentified(native),
|
||||
Button3 => Key::Unidentified(native),
|
||||
Button4 => Key::Unidentified(native),
|
||||
Button5 => Key::Unidentified(native),
|
||||
Button6 => Key::Unidentified(native),
|
||||
Button7 => Key::Unidentified(native),
|
||||
Button8 => Key::Unidentified(native),
|
||||
Button9 => Key::Unidentified(native),
|
||||
Button10 => Key::Unidentified(native),
|
||||
Button11 => Key::Unidentified(native),
|
||||
Button12 => Key::Unidentified(native),
|
||||
Button13 => Key::Unidentified(native),
|
||||
Button14 => Key::Unidentified(native),
|
||||
Button15 => Key::Unidentified(native),
|
||||
Button16 => Key::Unidentified(native),
|
||||
LanguageSwitch => Key::GroupNext,
|
||||
MannerMode => Key::MannerMode,
|
||||
Keycode3dMode => Key::TV3DMode,
|
||||
Contacts => Key::LaunchContacts,
|
||||
Calendar => Key::LaunchCalendar,
|
||||
Music => Key::LaunchMusicPlayer,
|
||||
Calculator => Key::LaunchApplication2,
|
||||
ZenkakuHankaku => Key::ZenkakuHankaku,
|
||||
Eisu => Key::Eisu,
|
||||
Muhenkan => Key::NonConvert,
|
||||
Henkan => Key::Convert,
|
||||
KatakanaHiragana => Key::HiraganaKatakana,
|
||||
Yen => Key::Unidentified(native),
|
||||
Ro => Key::Unidentified(native),
|
||||
Kana => Key::KanjiMode,
|
||||
Assist => Key::Unidentified(native),
|
||||
BrightnessDown => Key::BrightnessDown,
|
||||
BrightnessUp => Key::BrightnessUp,
|
||||
MediaAudioTrack => Key::MediaAudioTrack,
|
||||
Sleep => Key::Standby,
|
||||
Wakeup => Key::WakeUp,
|
||||
Pairing => Key::Pairing,
|
||||
MediaTopMenu => Key::MediaTopMenu,
|
||||
Keycode11 => Key::Unidentified(native),
|
||||
Keycode12 => Key::Unidentified(native),
|
||||
LastChannel => Key::MediaLast,
|
||||
TvDataService => Key::TVDataService,
|
||||
VoiceAssist => Key::VoiceDial,
|
||||
TvRadioService => Key::TVRadioService,
|
||||
TvTeletext => Key::Teletext,
|
||||
TvNumberEntry => Key::TVNumberEntry,
|
||||
TvTerrestrialAnalog => Key::TVTerrestrialAnalog,
|
||||
TvTerrestrialDigital => Key::TVTerrestrialDigital,
|
||||
TvSatellite => Key::TVSatellite,
|
||||
TvSatelliteBs => Key::TVSatelliteBS,
|
||||
TvSatelliteCs => Key::TVSatelliteCS,
|
||||
TvSatelliteService => Key::TVSatelliteToggle,
|
||||
TvNetwork => Key::TVNetwork,
|
||||
TvAntennaCable => Key::TVAntennaCable,
|
||||
TvInputHdmi1 => Key::TVInputHDMI1,
|
||||
TvInputHdmi2 => Key::TVInputHDMI2,
|
||||
TvInputHdmi3 => Key::TVInputHDMI3,
|
||||
TvInputHdmi4 => Key::TVInputHDMI4,
|
||||
TvInputComposite1 => Key::TVInputComposite1,
|
||||
TvInputComposite2 => Key::TVInputComposite2,
|
||||
TvInputComponent1 => Key::TVInputComponent1,
|
||||
TvInputComponent2 => Key::TVInputComponent2,
|
||||
TvInputVga1 => Key::TVInputVGA1,
|
||||
TvAudioDescription => Key::TVAudioDescription,
|
||||
TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp,
|
||||
TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown,
|
||||
TvZoomMode => Key::ZoomToggle,
|
||||
TvContentsMenu => Key::TVContentsMenu,
|
||||
TvMediaContextMenu => Key::TVMediaContext,
|
||||
TvTimerProgramming => Key::TVTimer,
|
||||
Help => Key::Help,
|
||||
NavigatePrevious => Key::NavigatePrevious,
|
||||
NavigateNext => Key::NavigateNext,
|
||||
NavigateIn => Key::NavigateIn,
|
||||
NavigateOut => Key::NavigateOut,
|
||||
StemPrimary => Key::Unidentified(native),
|
||||
Stem1 => Key::Unidentified(native),
|
||||
Stem2 => Key::Unidentified(native),
|
||||
Stem3 => Key::Unidentified(native),
|
||||
DpadUpLeft => Key::Unidentified(native),
|
||||
DpadDownLeft => Key::Unidentified(native),
|
||||
DpadUpRight => Key::Unidentified(native),
|
||||
DpadDownRight => Key::Unidentified(native),
|
||||
MediaSkipForward => Key::MediaSkipForward,
|
||||
MediaSkipBackward => Key::MediaSkipBackward,
|
||||
MediaStepForward => Key::MediaStepForward,
|
||||
MediaStepBackward => Key::MediaStepBackward,
|
||||
SoftSleep => Key::Unidentified(native),
|
||||
Cut => Key::Cut,
|
||||
Copy => Key::Copy,
|
||||
Paste => Key::Paste,
|
||||
SystemNavigationUp => Key::Unidentified(native),
|
||||
SystemNavigationDown => Key::Unidentified(native),
|
||||
SystemNavigationLeft => Key::Unidentified(native),
|
||||
SystemNavigationRight => Key::Unidentified(native),
|
||||
AllApps => Key::Unidentified(native),
|
||||
Refresh => Key::BrowserRefresh,
|
||||
ThumbsUp => Key::Unidentified(native),
|
||||
ThumbsDown => Key::Unidentified(native),
|
||||
ProfileSwitch => Key::Unidentified(native),
|
||||
// Can be added on demand
|
||||
SoftLeft => Key::Unidentified(native),
|
||||
SoftRight => Key::Unidentified(native),
|
||||
|
||||
Menu => Key::Unidentified(native),
|
||||
|
||||
Pictsymbols => Key::Unidentified(native),
|
||||
SwitchCharset => Key::Unidentified(native),
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Gamepad events should be exposed through a separate API, not
|
||||
// keyboard events
|
||||
ButtonA => Key::Unidentified(native),
|
||||
ButtonB => Key::Unidentified(native),
|
||||
ButtonC => Key::Unidentified(native),
|
||||
ButtonX => Key::Unidentified(native),
|
||||
ButtonY => Key::Unidentified(native),
|
||||
ButtonZ => Key::Unidentified(native),
|
||||
ButtonL1 => Key::Unidentified(native),
|
||||
ButtonR1 => Key::Unidentified(native),
|
||||
ButtonL2 => Key::Unidentified(native),
|
||||
ButtonR2 => Key::Unidentified(native),
|
||||
ButtonThumbl => Key::Unidentified(native),
|
||||
ButtonThumbr => Key::Unidentified(native),
|
||||
ButtonStart => Key::Unidentified(native),
|
||||
ButtonSelect => Key::Unidentified(native),
|
||||
ButtonMode => Key::Unidentified(native),
|
||||
// -----------------------------------------------------------------
|
||||
Window => Key::Unidentified(native),
|
||||
|
||||
Button1 => Key::Unidentified(native),
|
||||
Button2 => Key::Unidentified(native),
|
||||
Button3 => Key::Unidentified(native),
|
||||
Button4 => Key::Unidentified(native),
|
||||
Button5 => Key::Unidentified(native),
|
||||
Button6 => Key::Unidentified(native),
|
||||
Button7 => Key::Unidentified(native),
|
||||
Button8 => Key::Unidentified(native),
|
||||
Button9 => Key::Unidentified(native),
|
||||
Button10 => Key::Unidentified(native),
|
||||
Button11 => Key::Unidentified(native),
|
||||
Button12 => Key::Unidentified(native),
|
||||
Button13 => Key::Unidentified(native),
|
||||
Button14 => Key::Unidentified(native),
|
||||
Button15 => Key::Unidentified(native),
|
||||
Button16 => Key::Unidentified(native),
|
||||
|
||||
Yen => Key::Unidentified(native),
|
||||
Ro => Key::Unidentified(native),
|
||||
|
||||
Assist => Key::Unidentified(native),
|
||||
|
||||
Keycode11 => Key::Unidentified(native),
|
||||
Keycode12 => Key::Unidentified(native),
|
||||
|
||||
StemPrimary => Key::Unidentified(native),
|
||||
Stem1 => Key::Unidentified(native),
|
||||
Stem2 => Key::Unidentified(native),
|
||||
Stem3 => Key::Unidentified(native),
|
||||
|
||||
DpadUpLeft => Key::Unidentified(native),
|
||||
DpadDownLeft => Key::Unidentified(native),
|
||||
DpadUpRight => Key::Unidentified(native),
|
||||
DpadDownRight => Key::Unidentified(native),
|
||||
|
||||
SoftSleep => Key::Unidentified(native),
|
||||
|
||||
SystemNavigationUp => Key::Unidentified(native),
|
||||
SystemNavigationDown => Key::Unidentified(native),
|
||||
SystemNavigationLeft => Key::Unidentified(native),
|
||||
SystemNavigationRight => Key::Unidentified(native),
|
||||
|
||||
AllApps => Key::Unidentified(native),
|
||||
ThumbsUp => Key::Unidentified(native),
|
||||
ThumbsDown => Key::Unidentified(native),
|
||||
ProfileSwitch => Key::Unidentified(native),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
hash::Hash,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc, Arc, RwLock,
|
||||
mpsc, Arc, Mutex, RwLock,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@@ -19,22 +19,31 @@ use raw_window_handle::{
|
||||
AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
|
||||
};
|
||||
|
||||
use crate::platform_impl::Fullscreen;
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error,
|
||||
event::{self, StartCause},
|
||||
event::{self, InnerSizeWriter, StartCause},
|
||||
event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW},
|
||||
keyboard::NativeKey,
|
||||
platform::pump_events::PumpStatus,
|
||||
window::{
|
||||
self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel,
|
||||
},
|
||||
};
|
||||
use crate::{error::EventLoopError, platform_impl::Fullscreen};
|
||||
|
||||
mod keycodes;
|
||||
|
||||
static HAS_FOCUS: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true));
|
||||
|
||||
/// Returns the minimum `Option<Duration>`, taking into account that `None`
|
||||
/// equates to an infinite timeout, not a zero timeout (so can't just use
|
||||
/// `Option::min`)
|
||||
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
|
||||
a.map_or(b, |a_timeout| {
|
||||
b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))
|
||||
})
|
||||
}
|
||||
|
||||
struct PeekableReceiver<T> {
|
||||
recv: mpsc::Receiver<T>,
|
||||
first: Option<T>,
|
||||
@@ -135,8 +144,13 @@ pub struct EventLoop<T: 'static> {
|
||||
redraw_flag: SharedFlag,
|
||||
user_events_sender: mpsc::Sender<T>,
|
||||
user_events_receiver: PeekableReceiver<T>, //must wake looper whenever something gets sent
|
||||
loop_running: bool, // Dispatched `NewEvents<Init>`
|
||||
running: bool,
|
||||
pending_redraw: bool,
|
||||
control_flow: ControlFlow,
|
||||
cause: StartCause,
|
||||
ignore_volume_keys: bool,
|
||||
combining_accent: Option<char>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -155,12 +169,12 @@ impl Default for PlatformSpecificEventLoopAttributes {
|
||||
}
|
||||
|
||||
fn sticky_exit_callback<T, F>(
|
||||
evt: event::Event<'_, T>,
|
||||
evt: event::Event<T>,
|
||||
target: &RootELW<T>,
|
||||
control_flow: &mut ControlFlow,
|
||||
callback: &mut F,
|
||||
) where
|
||||
F: FnMut(event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
// make ControlFlow::ExitWithCode sticky by providing a dummy
|
||||
// control flow reference if it is already ExitWithCode.
|
||||
@@ -171,20 +185,16 @@ fn sticky_exit_callback<T, F>(
|
||||
}
|
||||
}
|
||||
|
||||
struct IterationResult {
|
||||
deadline: Option<Instant>,
|
||||
timeout: Option<Duration>,
|
||||
wait_start: Instant,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
|
||||
pub(crate) fn new(
|
||||
attributes: &PlatformSpecificEventLoopAttributes,
|
||||
) -> Result<Self, EventLoopError> {
|
||||
let (user_events_sender, user_events_receiver) = mpsc::channel();
|
||||
|
||||
let android_app = attributes.android_app.as_ref().expect("An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on Android");
|
||||
let redraw_flag = SharedFlag::new();
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
android_app: android_app.clone(),
|
||||
window_target: event_loop::EventLoopWindowTarget {
|
||||
p: EventLoopWindowTarget {
|
||||
@@ -200,33 +210,34 @@ impl<T: 'static> EventLoop<T> {
|
||||
redraw_flag,
|
||||
user_events_sender,
|
||||
user_events_receiver: PeekableReceiver::from_recv(user_events_receiver),
|
||||
loop_running: false,
|
||||
running: false,
|
||||
pending_redraw: false,
|
||||
control_flow: Default::default(),
|
||||
cause: StartCause::Init,
|
||||
ignore_volume_keys: attributes.ignore_volume_keys,
|
||||
}
|
||||
combining_accent: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn single_iteration<F>(
|
||||
&mut self,
|
||||
control_flow: &mut ControlFlow,
|
||||
main_event: Option<MainEvent<'_>>,
|
||||
pending_redraw: &mut bool,
|
||||
cause: &mut StartCause,
|
||||
callback: &mut F,
|
||||
) -> IterationResult
|
||||
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F)
|
||||
where
|
||||
F: FnMut(event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
trace!("Mainloop iteration");
|
||||
|
||||
let cause = self.cause;
|
||||
let mut control_flow = self.control_flow;
|
||||
let mut pending_redraw = self.pending_redraw;
|
||||
let mut resized = false;
|
||||
|
||||
sticky_exit_callback(
|
||||
event::Event::NewEvents(*cause),
|
||||
event::Event::NewEvents(cause),
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
|
||||
let mut resized = false;
|
||||
|
||||
if let Some(event) = main_event {
|
||||
trace!("Handling main event {:?}", event);
|
||||
|
||||
@@ -235,7 +246,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
sticky_exit_callback(
|
||||
event::Event::Resumed,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
@@ -243,12 +254,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
sticky_exit_callback(
|
||||
event::Event::Suspended,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
MainEvent::WindowResized { .. } => resized = true,
|
||||
MainEvent::RedrawNeeded { .. } => *pending_redraw = true,
|
||||
MainEvent::RedrawNeeded { .. } => pending_redraw = true,
|
||||
MainEvent::ContentRectChanged { .. } => {
|
||||
warn!("TODO: find a way to notify application of content rect change");
|
||||
}
|
||||
@@ -260,7 +271,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
event: event::WindowEvent::Focused(true),
|
||||
},
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
@@ -272,7 +283,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
event: event::WindowEvent::Focused(false),
|
||||
},
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
@@ -281,15 +292,24 @@ impl<T: 'static> EventLoop<T> {
|
||||
let old_scale_factor = monitor.scale_factor();
|
||||
let scale_factor = monitor.scale_factor();
|
||||
if (scale_factor - old_scale_factor).abs() < f64::EPSILON {
|
||||
let mut size = MonitorHandle::new(self.android_app.clone()).size();
|
||||
let new_inner_size = Arc::new(Mutex::new(
|
||||
MonitorHandle::new(self.android_app.clone()).size(),
|
||||
));
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::ScaleFactorChanged {
|
||||
new_inner_size: &mut size,
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
|
||||
&new_inner_size,
|
||||
)),
|
||||
scale_factor,
|
||||
},
|
||||
};
|
||||
sticky_exit_callback(event, self.window_target(), control_flow, callback);
|
||||
sticky_exit_callback(
|
||||
event,
|
||||
self.window_target(),
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
MainEvent::LowMemory => {
|
||||
@@ -336,128 +356,25 @@ impl<T: 'static> EventLoop<T> {
|
||||
trace!("No main event to handle");
|
||||
}
|
||||
|
||||
// temporarily decouple `android_app` from `self` so we aren't holding
|
||||
// a borrow of `self` while iterating
|
||||
let android_app = self.android_app.clone();
|
||||
|
||||
// Process input events
|
||||
self.android_app.input_events(|event| {
|
||||
let mut input_status = InputStatus::Handled;
|
||||
match event {
|
||||
InputEvent::MotionEvent(motion_event) => {
|
||||
let window_id = window::WindowId(WindowId);
|
||||
let device_id = event::DeviceId(DeviceId);
|
||||
match android_app.input_events_iter() {
|
||||
Ok(mut input_iter) => loop {
|
||||
let read_event = input_iter.next(|event| {
|
||||
self.handle_input_event(&android_app, event, &mut control_flow, callback)
|
||||
});
|
||||
|
||||
let phase = match motion_event.action() {
|
||||
MotionAction::Down | MotionAction::PointerDown => {
|
||||
Some(event::TouchPhase::Started)
|
||||
}
|
||||
MotionAction::Up | MotionAction::PointerUp => {
|
||||
Some(event::TouchPhase::Ended)
|
||||
}
|
||||
MotionAction::Move => Some(event::TouchPhase::Moved),
|
||||
MotionAction::Cancel => {
|
||||
Some(event::TouchPhase::Cancelled)
|
||||
}
|
||||
_ => {
|
||||
None // TODO mouse events
|
||||
}
|
||||
};
|
||||
if let Some(phase) = phase {
|
||||
let pointers: Box<
|
||||
dyn Iterator<Item = android_activity::input::Pointer<'_>>,
|
||||
> = match phase {
|
||||
event::TouchPhase::Started
|
||||
| event::TouchPhase::Ended => {
|
||||
Box::new(
|
||||
std::iter::once(motion_event.pointer_at_index(
|
||||
motion_event.pointer_index(),
|
||||
))
|
||||
)
|
||||
},
|
||||
event::TouchPhase::Moved
|
||||
| event::TouchPhase::Cancelled => {
|
||||
Box::new(motion_event.pointers())
|
||||
}
|
||||
};
|
||||
|
||||
for pointer in pointers {
|
||||
let location = PhysicalPosition {
|
||||
x: pointer.x() as _,
|
||||
y: pointer.y() as _,
|
||||
};
|
||||
trace!("Input event {device_id:?}, {phase:?}, loc={location:?}, pointer={pointer:?}");
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id,
|
||||
event: event::WindowEvent::Touch(
|
||||
event::Touch {
|
||||
device_id,
|
||||
phase,
|
||||
location,
|
||||
id: pointer.pointer_id() as u64,
|
||||
force: None,
|
||||
},
|
||||
),
|
||||
};
|
||||
sticky_exit_callback(
|
||||
event,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
callback
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
InputEvent::KeyEvent(key) => {
|
||||
match key.key_code() {
|
||||
// Flag keys related to volume as unhandled. While winit does not have a way for applications
|
||||
// to configure what keys to flag as handled, this appears to be a good default until winit
|
||||
// can be configured.
|
||||
Keycode::VolumeUp |
|
||||
Keycode::VolumeDown |
|
||||
Keycode::VolumeMute => {
|
||||
if self.ignore_volume_keys {
|
||||
input_status = InputStatus::Unhandled
|
||||
}
|
||||
},
|
||||
keycode => {
|
||||
let state = match key.action() {
|
||||
KeyAction::Down => event::ElementState::Pressed,
|
||||
KeyAction::Up => event::ElementState::Released,
|
||||
_ => event::ElementState::Released,
|
||||
};
|
||||
|
||||
let native = NativeKey::Android(keycode.into());
|
||||
let logical_key = keycodes::to_logical(keycode, native);
|
||||
// TODO: maybe use getUnicodeChar to get the logical key
|
||||
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::KeyboardInput {
|
||||
device_id: event::DeviceId(DeviceId),
|
||||
event: event::KeyEvent {
|
||||
state,
|
||||
physical_key: keycodes::to_physical_keycode(keycode),
|
||||
logical_key,
|
||||
location: keycodes::to_location(keycode),
|
||||
repeat: key.repeat_count() > 0,
|
||||
text: None,
|
||||
platform_specific: KeyEventExtra {},
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
};
|
||||
sticky_exit_callback(
|
||||
event,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
warn!("Unknown android_activity input event {event:?}")
|
||||
if !read_event {
|
||||
break;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::warn!("Failed to get input events iterator: {err:?}");
|
||||
}
|
||||
input_status
|
||||
});
|
||||
}
|
||||
|
||||
// Empty the user event buffer
|
||||
{
|
||||
@@ -465,19 +382,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
sticky_exit_callback(
|
||||
crate::event::Event::UserEvent(event),
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sticky_exit_callback(
|
||||
event::Event::MainEventsCleared,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
callback,
|
||||
);
|
||||
|
||||
if self.running {
|
||||
if resized {
|
||||
let size = if let Some(native_window) = self.android_app.native_window().as_ref() {
|
||||
@@ -491,163 +401,290 @@ impl<T: 'static> EventLoop<T> {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::Resized(size),
|
||||
};
|
||||
sticky_exit_callback(event, self.window_target(), control_flow, callback);
|
||||
sticky_exit_callback(event, self.window_target(), &mut control_flow, callback);
|
||||
}
|
||||
|
||||
*pending_redraw |= self.redraw_flag.get_and_reset();
|
||||
if *pending_redraw {
|
||||
*pending_redraw = false;
|
||||
pending_redraw |= self.redraw_flag.get_and_reset();
|
||||
if pending_redraw {
|
||||
pending_redraw = false;
|
||||
let event = event::Event::RedrawRequested(window::WindowId(WindowId));
|
||||
sticky_exit_callback(event, self.window_target(), control_flow, callback);
|
||||
sticky_exit_callback(event, self.window_target(), &mut control_flow, callback);
|
||||
}
|
||||
}
|
||||
|
||||
// This is always the last event we dispatch before poll again
|
||||
sticky_exit_callback(
|
||||
event::Event::RedrawEventsCleared,
|
||||
event::Event::AboutToWait,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
|
||||
let start = Instant::now();
|
||||
let (deadline, timeout);
|
||||
|
||||
match control_flow {
|
||||
ControlFlow::ExitWithCode(_) => {
|
||||
deadline = None;
|
||||
timeout = None;
|
||||
}
|
||||
ControlFlow::Poll => {
|
||||
*cause = StartCause::Poll;
|
||||
deadline = None;
|
||||
timeout = Some(Duration::from_millis(0));
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
*cause = StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
};
|
||||
deadline = None;
|
||||
timeout = None;
|
||||
}
|
||||
ControlFlow::WaitUntil(wait_deadline) => {
|
||||
*cause = StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume: *wait_deadline,
|
||||
};
|
||||
timeout = if *wait_deadline > start {
|
||||
Some(*wait_deadline - start)
|
||||
} else {
|
||||
Some(Duration::from_millis(0))
|
||||
};
|
||||
deadline = Some(*wait_deadline);
|
||||
}
|
||||
}
|
||||
|
||||
IterationResult {
|
||||
wait_start: start,
|
||||
deadline,
|
||||
timeout,
|
||||
}
|
||||
self.control_flow = control_flow;
|
||||
self.pending_redraw = pending_redraw;
|
||||
}
|
||||
|
||||
pub fn run<F>(mut self, event_handler: F) -> !
|
||||
fn handle_input_event<F>(
|
||||
&mut self,
|
||||
android_app: &AndroidApp,
|
||||
event: &InputEvent<'_>,
|
||||
control_flow: &mut ControlFlow,
|
||||
callback: &mut F,
|
||||
) -> InputStatus
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
let exit_code = self.run_return(event_handler);
|
||||
::std::process::exit(exit_code);
|
||||
}
|
||||
let mut input_status = InputStatus::Handled;
|
||||
match event {
|
||||
InputEvent::MotionEvent(motion_event) => {
|
||||
let window_id = window::WindowId(WindowId);
|
||||
let device_id = event::DeviceId(DeviceId);
|
||||
|
||||
pub fn run_return<F>(&mut self, mut callback: F) -> i32
|
||||
where
|
||||
F: FnMut(event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
let mut control_flow = ControlFlow::default();
|
||||
let mut cause = StartCause::Init;
|
||||
let mut pending_redraw = false;
|
||||
let phase = match motion_event.action() {
|
||||
MotionAction::Down | MotionAction::PointerDown => {
|
||||
Some(event::TouchPhase::Started)
|
||||
}
|
||||
MotionAction::Up | MotionAction::PointerUp => Some(event::TouchPhase::Ended),
|
||||
MotionAction::Move => Some(event::TouchPhase::Moved),
|
||||
MotionAction::Cancel => Some(event::TouchPhase::Cancelled),
|
||||
_ => {
|
||||
None // TODO mouse events
|
||||
}
|
||||
};
|
||||
if let Some(phase) = phase {
|
||||
let pointers: Box<dyn Iterator<Item = android_activity::input::Pointer<'_>>> =
|
||||
match phase {
|
||||
event::TouchPhase::Started | event::TouchPhase::Ended => {
|
||||
Box::new(std::iter::once(
|
||||
motion_event.pointer_at_index(motion_event.pointer_index()),
|
||||
))
|
||||
}
|
||||
event::TouchPhase::Moved | event::TouchPhase::Cancelled => {
|
||||
Box::new(motion_event.pointers())
|
||||
}
|
||||
};
|
||||
|
||||
// run the initial loop iteration
|
||||
let mut iter_result = self.single_iteration(
|
||||
&mut control_flow,
|
||||
None,
|
||||
&mut pending_redraw,
|
||||
&mut cause,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
let exit_code = loop {
|
||||
if let ControlFlow::ExitWithCode(code) = control_flow {
|
||||
break code;
|
||||
for pointer in pointers {
|
||||
let location = PhysicalPosition {
|
||||
x: pointer.x() as _,
|
||||
y: pointer.y() as _,
|
||||
};
|
||||
trace!("Input event {device_id:?}, {phase:?}, loc={location:?}, pointer={pointer:?}");
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id,
|
||||
event: event::WindowEvent::Touch(event::Touch {
|
||||
device_id,
|
||||
phase,
|
||||
location,
|
||||
id: pointer.pointer_id() as u64,
|
||||
force: None,
|
||||
}),
|
||||
};
|
||||
sticky_exit_callback(event, self.window_target(), control_flow, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut timeout = iter_result.timeout;
|
||||
|
||||
// If we already have work to do then we don't want to block on the next poll...
|
||||
pending_redraw |= self.redraw_flag.get_and_reset();
|
||||
if self.running && (pending_redraw || self.user_events_receiver.has_incoming()) {
|
||||
timeout = Some(Duration::from_millis(0))
|
||||
}
|
||||
|
||||
let app = self.android_app.clone(); // Don't borrow self as part of poll expression
|
||||
app.poll_events(timeout, |poll_event| {
|
||||
let mut main_event = None;
|
||||
|
||||
match poll_event {
|
||||
android_activity::PollEvent::Wake => {
|
||||
// In the X11 backend it's noted that too many false-positive wake ups
|
||||
// would cause the event loop to run continuously. They handle this by re-checking
|
||||
// for pending events (assuming they cover all valid reasons for a wake up).
|
||||
//
|
||||
// For now, user_events and redraw_requests are the only reasons to expect
|
||||
// a wake up here so we can ignore the wake up if there are no events/requests.
|
||||
// We also ignore wake ups while suspended.
|
||||
pending_redraw |= self.redraw_flag.get_and_reset();
|
||||
if !self.running
|
||||
|| (!pending_redraw && !self.user_events_receiver.has_incoming())
|
||||
{
|
||||
return;
|
||||
InputEvent::KeyEvent(key) => {
|
||||
match key.key_code() {
|
||||
// Flag keys related to volume as unhandled. While winit does not have a way for applications
|
||||
// to configure what keys to flag as handled, this appears to be a good default until winit
|
||||
// can be configured.
|
||||
Keycode::VolumeUp | Keycode::VolumeDown | Keycode::VolumeMute => {
|
||||
if self.ignore_volume_keys {
|
||||
input_status = InputStatus::Unhandled
|
||||
}
|
||||
}
|
||||
android_activity::PollEvent::Timeout => {}
|
||||
android_activity::PollEvent::Main(event) => {
|
||||
main_event = Some(event);
|
||||
}
|
||||
unknown_event => {
|
||||
warn!("Unknown poll event {unknown_event:?} (ignored)");
|
||||
keycode => {
|
||||
let state = match key.action() {
|
||||
KeyAction::Down => event::ElementState::Pressed,
|
||||
KeyAction::Up => event::ElementState::Released,
|
||||
_ => event::ElementState::Released,
|
||||
};
|
||||
|
||||
let key_char = keycodes::character_map_and_combine_key(
|
||||
android_app,
|
||||
key,
|
||||
&mut self.combining_accent,
|
||||
);
|
||||
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::KeyboardInput {
|
||||
device_id: event::DeviceId(DeviceId),
|
||||
event: event::KeyEvent {
|
||||
state,
|
||||
physical_key: keycodes::to_physical_keycode(keycode),
|
||||
logical_key: keycodes::to_logical(key_char, keycode),
|
||||
location: keycodes::to_location(keycode),
|
||||
repeat: key.repeat_count() > 0,
|
||||
text: None,
|
||||
platform_specific: KeyEventExtra {},
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
};
|
||||
sticky_exit_callback(event, self.window_target(), control_flow, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
warn!("Unknown android_activity input event {event:?}")
|
||||
}
|
||||
}
|
||||
|
||||
let wait_cancelled = iter_result
|
||||
.deadline
|
||||
.map_or(false, |deadline| Instant::now() < deadline);
|
||||
input_status
|
||||
}
|
||||
|
||||
if wait_cancelled {
|
||||
cause = StartCause::WaitCancelled {
|
||||
start: iter_result.wait_start,
|
||||
requested_resume: iter_result.deadline,
|
||||
};
|
||||
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
self.run_ondemand(event_handler)
|
||||
}
|
||||
|
||||
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
if self.loop_running {
|
||||
return Err(EventLoopError::AlreadyRunning);
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.pump_events(None, &mut event_handler) {
|
||||
PumpStatus::Exit(0) => {
|
||||
break Ok(());
|
||||
}
|
||||
PumpStatus::Exit(code) => {
|
||||
break Err(EventLoopError::ExitFailure(code));
|
||||
}
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iter_result = self.single_iteration(
|
||||
&mut control_flow,
|
||||
main_event,
|
||||
&mut pending_redraw,
|
||||
&mut cause,
|
||||
&mut callback,
|
||||
);
|
||||
});
|
||||
};
|
||||
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
if !self.loop_running {
|
||||
self.loop_running = true;
|
||||
|
||||
sticky_exit_callback(
|
||||
event::Event::LoopDestroyed,
|
||||
self.window_target(),
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
// Reset the internal state for the loop as we start running to
|
||||
// ensure consistent behaviour in case the loop runs and exits more
|
||||
// than once
|
||||
self.pending_redraw = false;
|
||||
self.cause = StartCause::Init;
|
||||
self.control_flow = ControlFlow::Poll;
|
||||
|
||||
exit_code
|
||||
// run the initial loop iteration
|
||||
self.single_iteration(None, &mut callback);
|
||||
}
|
||||
|
||||
// Consider the possibility that the `StartCause::Init` iteration could
|
||||
// request to Exit
|
||||
if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) {
|
||||
self.poll_events_with_timeout(timeout, &mut callback);
|
||||
}
|
||||
if let ControlFlow::ExitWithCode(code) = self.control_flow {
|
||||
self.loop_running = false;
|
||||
|
||||
let mut dummy = self.control_flow;
|
||||
sticky_exit_callback(
|
||||
event::Event::LoopExiting,
|
||||
self.window_target(),
|
||||
&mut dummy,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
PumpStatus::Exit(code)
|
||||
} else {
|
||||
PumpStatus::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
|
||||
where
|
||||
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
let start = Instant::now();
|
||||
|
||||
self.pending_redraw |= self.redraw_flag.get_and_reset();
|
||||
|
||||
timeout =
|
||||
if self.running && (self.pending_redraw || self.user_events_receiver.has_incoming()) {
|
||||
// If we already have work to do then we don't want to block on the next poll
|
||||
Some(Duration::ZERO)
|
||||
} else {
|
||||
let control_flow_timeout = match self.control_flow {
|
||||
ControlFlow::Wait => None,
|
||||
ControlFlow::Poll => Some(Duration::ZERO),
|
||||
ControlFlow::WaitUntil(wait_deadline) => {
|
||||
Some(wait_deadline.saturating_duration_since(start))
|
||||
}
|
||||
// `ExitWithCode()` will be reset to `Poll` before polling
|
||||
ControlFlow::ExitWithCode(_code) => unreachable!(),
|
||||
};
|
||||
|
||||
min_timeout(control_flow_timeout, timeout)
|
||||
};
|
||||
|
||||
let app = self.android_app.clone(); // Don't borrow self as part of poll expression
|
||||
app.poll_events(timeout, |poll_event| {
|
||||
let mut main_event = None;
|
||||
|
||||
match poll_event {
|
||||
android_activity::PollEvent::Wake => {
|
||||
// In the X11 backend it's noted that too many false-positive wake ups
|
||||
// would cause the event loop to run continuously. They handle this by re-checking
|
||||
// for pending events (assuming they cover all valid reasons for a wake up).
|
||||
//
|
||||
// For now, user_events and redraw_requests are the only reasons to expect
|
||||
// a wake up here so we can ignore the wake up if there are no events/requests.
|
||||
// We also ignore wake ups while suspended.
|
||||
self.pending_redraw |= self.redraw_flag.get_and_reset();
|
||||
if !self.running
|
||||
|| (!self.pending_redraw && !self.user_events_receiver.has_incoming())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
android_activity::PollEvent::Timeout => {}
|
||||
android_activity::PollEvent::Main(event) => {
|
||||
main_event = Some(event);
|
||||
}
|
||||
unknown_event => {
|
||||
warn!("Unknown poll event {unknown_event:?} (ignored)");
|
||||
}
|
||||
}
|
||||
|
||||
self.cause = match self.control_flow {
|
||||
ControlFlow::Poll => StartCause::Poll,
|
||||
ControlFlow::Wait => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
},
|
||||
ControlFlow::WaitUntil(deadline) => {
|
||||
if Instant::now() < deadline {
|
||||
StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(deadline),
|
||||
}
|
||||
} else {
|
||||
StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume: deadline,
|
||||
}
|
||||
}
|
||||
}
|
||||
// `ExitWithCode()` will be reset to `Poll` before polling
|
||||
ControlFlow::ExitWithCode(_code) => unreachable!(),
|
||||
};
|
||||
|
||||
self.single_iteration(main_event, &mut callback);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget<T> {
|
||||
@@ -760,6 +797,14 @@ impl Window {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
|
||||
f(self)
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
|
||||
f(self)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId
|
||||
}
|
||||
@@ -786,6 +831,8 @@ impl Window {
|
||||
self.redraw_requester.request_redraw()
|
||||
}
|
||||
|
||||
pub fn pre_present_notify(&self) {}
|
||||
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
|
||||
Err(error::NotSupportedError::new())
|
||||
}
|
||||
@@ -802,8 +849,8 @@ impl Window {
|
||||
self.outer_size()
|
||||
}
|
||||
|
||||
pub fn set_inner_size(&self, _size: Size) {
|
||||
warn!("Cannot set window size on Android");
|
||||
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
|
||||
Some(self.inner_size())
|
||||
}
|
||||
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
@@ -973,8 +1020,8 @@ pub struct MonitorHandle {
|
||||
app: AndroidApp,
|
||||
}
|
||||
impl PartialOrd for MonitorHandle {
|
||||
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(std::cmp::Ordering::Equal)
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
impl Ord for MonitorHandle {
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::{
|
||||
mem,
|
||||
os::raw::c_void,
|
||||
ptr,
|
||||
sync::{Arc, Mutex},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
@@ -15,9 +16,9 @@ use core_foundation::runloop::{
|
||||
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
|
||||
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
|
||||
};
|
||||
use objc2::foundation::{CGRect, CGSize, NSInteger, NSProcessInfo};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::runtime::Object;
|
||||
use icrate::Foundation::{CGRect, CGSize, NSInteger, NSOperatingSystemVersion, NSProcessInfo};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{msg_send, sel};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
@@ -25,12 +26,9 @@ use super::uikit::UIView;
|
||||
use super::view::WinitUIWindow;
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
|
||||
event_loop::ControlFlow,
|
||||
platform_impl::platform::{
|
||||
event_loop::{EventHandler, EventProxy, EventWrapper, Never},
|
||||
ffi::NSOperatingSystemVersion,
|
||||
},
|
||||
platform_impl::platform::event_loop::{EventHandler, EventProxy, EventWrapper, Never},
|
||||
window::WindowId as RootWindowId,
|
||||
};
|
||||
|
||||
@@ -57,7 +55,7 @@ enum UserCallbackTransitionResult<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
impl Event<'static, Never> {
|
||||
impl Event<Never> {
|
||||
fn is_redraw(&self) -> bool {
|
||||
matches!(self, Event::RedrawRequested(_))
|
||||
}
|
||||
@@ -68,25 +66,25 @@ impl Event<'static, Never> {
|
||||
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
|
||||
enum AppStateImpl {
|
||||
NotLaunched {
|
||||
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
|
||||
queued_windows: Vec<Id<WinitUIWindow>>,
|
||||
queued_events: Vec<EventWrapper>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
||||
},
|
||||
Launching {
|
||||
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
|
||||
queued_windows: Vec<Id<WinitUIWindow>>,
|
||||
queued_events: Vec<EventWrapper>,
|
||||
queued_event_handler: Box<dyn EventHandler>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
||||
},
|
||||
ProcessingEvents {
|
||||
event_handler: Box<dyn EventHandler>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
||||
active_control_flow: ControlFlow,
|
||||
},
|
||||
// special state to deal with reentrancy and prevent mutable aliasing.
|
||||
InUserCallback {
|
||||
queued_events: Vec<EventWrapper>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
||||
},
|
||||
ProcessingRedraws {
|
||||
event_handler: Box<dyn EventHandler>,
|
||||
@@ -204,9 +202,7 @@ impl AppState {
|
||||
});
|
||||
}
|
||||
|
||||
fn did_finish_launching_transition(
|
||||
&mut self,
|
||||
) -> (Vec<Id<WinitUIWindow, Shared>>, Vec<EventWrapper>) {
|
||||
fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) {
|
||||
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
|
||||
AppStateImpl::Launching {
|
||||
queued_windows,
|
||||
@@ -363,7 +359,7 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow, Shared>> {
|
||||
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> {
|
||||
let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
|
||||
AppStateImpl::ProcessingEvents {
|
||||
event_handler,
|
||||
@@ -393,9 +389,6 @@ impl AppState {
|
||||
|
||||
let new = self.control_flow;
|
||||
match (old, new) {
|
||||
(ControlFlow::Poll, ControlFlow::Poll) => self.set_state(AppStateImpl::PollFinished {
|
||||
waiting_event_handler,
|
||||
}),
|
||||
(ControlFlow::Wait, ControlFlow::Wait) => {
|
||||
let start = Instant::now();
|
||||
self.set_state(AppStateImpl::Waiting {
|
||||
@@ -428,6 +421,7 @@ impl AppState {
|
||||
});
|
||||
self.waker.start_at(new_instant)
|
||||
}
|
||||
// Unlike on macOS, handle Poll to Poll transition here to call the waker
|
||||
(_, ControlFlow::Poll) => {
|
||||
self.set_state(AppStateImpl::PollFinished {
|
||||
waiting_event_handler,
|
||||
@@ -446,17 +440,14 @@ impl AppState {
|
||||
fn terminated_transition(&mut self) -> Box<dyn EventHandler> {
|
||||
match self.replace_state(AppStateImpl::Terminated) {
|
||||
AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler,
|
||||
s => bug!(
|
||||
"`LoopDestroyed` happened while not processing events {:?}",
|
||||
s
|
||||
),
|
||||
s => bug!("`LoopExiting` happened while not processing events {:?}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// requires main thread and window is a UIWindow
|
||||
// retains window
|
||||
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
|
||||
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow>) {
|
||||
let mut this = AppState::get_mut();
|
||||
match this.state_mut() {
|
||||
&mut AppStateImpl::NotLaunched {
|
||||
@@ -479,7 +470,7 @@ pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
|
||||
|
||||
// requires main thread and window is a UIWindow
|
||||
// retains window
|
||||
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow, Shared>) {
|
||||
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow>) {
|
||||
let mut this = AppState::get_mut();
|
||||
match this.state_mut() {
|
||||
&mut AppStateImpl::NotLaunched {
|
||||
@@ -544,7 +535,7 @@ pub unsafe fn did_finish_launching() {
|
||||
// completed. This may result in incorrect visual appearance.
|
||||
// ```
|
||||
let screen = window.screen();
|
||||
let _: () = msg_send![&window, setScreen: ptr::null::<Object>()];
|
||||
let _: () = msg_send![&window, setScreen: ptr::null::<AnyObject>()];
|
||||
window.setScreen(&screen);
|
||||
|
||||
let controller = window.rootViewController();
|
||||
@@ -758,21 +749,18 @@ pub unsafe fn handle_main_events_cleared() {
|
||||
};
|
||||
drop(this);
|
||||
|
||||
// User events are always sent out at the end of the "MainEventLoop"
|
||||
handle_user_events();
|
||||
handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));
|
||||
|
||||
let mut this = AppState::get_mut();
|
||||
let mut redraw_events: Vec<EventWrapper> = this
|
||||
let redraw_events: Vec<EventWrapper> = this
|
||||
.main_events_cleared_transition()
|
||||
.into_iter()
|
||||
.map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.id()))))
|
||||
.collect();
|
||||
|
||||
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
|
||||
drop(this);
|
||||
|
||||
handle_nonuser_events(redraw_events);
|
||||
handle_nonuser_event(EventWrapper::StaticEvent(Event::AboutToWait));
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
@@ -787,7 +775,7 @@ pub unsafe fn terminated() {
|
||||
let mut control_flow = this.control_flow;
|
||||
drop(this);
|
||||
|
||||
event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow)
|
||||
event_handler.handle_nonuser_event(Event::LoopExiting, &mut control_flow)
|
||||
}
|
||||
|
||||
fn handle_event_proxy(
|
||||
@@ -815,27 +803,27 @@ fn handle_hidpi_proxy(
|
||||
mut control_flow: ControlFlow,
|
||||
suggested_size: LogicalSize<f64>,
|
||||
scale_factor: f64,
|
||||
window: Id<WinitUIWindow, Shared>,
|
||||
window: Id<WinitUIWindow>,
|
||||
) {
|
||||
let mut size = suggested_size.to_physical(scale_factor);
|
||||
let new_inner_size = &mut size;
|
||||
let new_inner_size = Arc::new(Mutex::new(suggested_size.to_physical(scale_factor)));
|
||||
let event = Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size,
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
|
||||
},
|
||||
};
|
||||
event_handler.handle_nonuser_event(event, &mut control_flow);
|
||||
let (view, screen_frame) = get_view_and_screen_frame(&window);
|
||||
let physical_size = *new_inner_size;
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
let logical_size = physical_size.to_logical(scale_factor);
|
||||
let size = CGSize::new(logical_size.width, logical_size.height);
|
||||
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
|
||||
view.setFrame(new_frame);
|
||||
}
|
||||
|
||||
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView, Shared>, CGRect) {
|
||||
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView>, CGRect) {
|
||||
let view_controller = window.rootViewController().unwrap();
|
||||
let view = view_controller.view().unwrap();
|
||||
let bounds = window.bounds();
|
||||
@@ -924,7 +912,7 @@ macro_rules! os_capabilities {
|
||||
|
||||
impl From<NSOperatingSystemVersion> for OSCapabilities {
|
||||
fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities {
|
||||
$(let $name = os_version.meets_requirements($major, $minor);)*
|
||||
$(let $name = meets_requirements(os_version, $major, $minor);)*
|
||||
OSCapabilities { $($name,)* os_version, }
|
||||
}
|
||||
}
|
||||
@@ -934,7 +922,7 @@ macro_rules! os_capabilities {
|
||||
pub fn $error_name(&self, extra_msg: &str) {
|
||||
log::warn!(
|
||||
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
|
||||
$major, $minor, self.os_version.major, self.os_version.minor, self.os_version.patch,
|
||||
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
|
||||
extra_msg
|
||||
)
|
||||
}
|
||||
@@ -962,16 +950,18 @@ os_capabilities! {
|
||||
force_touch: 9-0,
|
||||
}
|
||||
|
||||
impl NSOperatingSystemVersion {
|
||||
fn meets_requirements(&self, required_major: NSInteger, required_minor: NSInteger) -> bool {
|
||||
(self.major, self.minor) >= (required_major, required_minor)
|
||||
}
|
||||
fn meets_requirements(
|
||||
version: NSOperatingSystemVersion,
|
||||
required_major: NSInteger,
|
||||
required_minor: NSInteger,
|
||||
) -> bool {
|
||||
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
|
||||
}
|
||||
|
||||
pub fn os_capabilities() -> OSCapabilities {
|
||||
static OS_CAPABILITIES: Lazy<OSCapabilities> = Lazy::new(|| {
|
||||
let version: NSOperatingSystemVersion = unsafe {
|
||||
let process_info = NSProcessInfo::process_info();
|
||||
let process_info = NSProcessInfo::processInfo();
|
||||
let atleast_ios_8: bool = msg_send![
|
||||
&process_info,
|
||||
respondsToSelector: sel!(operatingSystemVersion)
|
||||
@@ -984,7 +974,7 @@ pub fn os_capabilities() -> OSCapabilities {
|
||||
//
|
||||
// The minimum required iOS version is likely to grow in the future.
|
||||
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
|
||||
msg_send![&process_info, operatingSystemVersion]
|
||||
process_info.operatingSystemVersion()
|
||||
};
|
||||
version.into()
|
||||
});
|
||||
|
||||
@@ -14,16 +14,14 @@ use core_foundation::runloop::{
|
||||
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
|
||||
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
use objc2::foundation::{MainThreadMarker, NSString};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use icrate::Foundation::{MainThreadMarker, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::ClassType;
|
||||
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
|
||||
|
||||
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen};
|
||||
use super::view::WinitUIWindow;
|
||||
use super::{app_state, monitor, view, MonitorHandle};
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
error::EventLoopError,
|
||||
event::Event,
|
||||
event_loop::{
|
||||
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
|
||||
@@ -31,16 +29,20 @@ use crate::{
|
||||
platform::ios::Idiom,
|
||||
};
|
||||
|
||||
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen};
|
||||
use super::view::WinitUIWindow;
|
||||
use super::{app_state, monitor, view, MonitorHandle};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum EventWrapper {
|
||||
StaticEvent(Event<'static, Never>),
|
||||
StaticEvent(Event<Never>),
|
||||
EventProxy(EventProxy),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum EventProxy {
|
||||
DpiChangedProxy {
|
||||
window: Id<WinitUIWindow, Shared>,
|
||||
window: Id<WinitUIWindow>,
|
||||
suggested_size: LogicalSize<f64>,
|
||||
scale_factor: f64,
|
||||
},
|
||||
@@ -75,7 +77,9 @@ pub struct EventLoop<T: 'static> {
|
||||
pub(crate) struct PlatformSpecificEventLoopAttributes {}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
|
||||
pub(crate) fn new(
|
||||
_: &PlatformSpecificEventLoopAttributes,
|
||||
) -> Result<EventLoop<T>, EventLoopError> {
|
||||
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
|
||||
|
||||
static mut SINGLETON_INIT: bool = false;
|
||||
@@ -93,7 +97,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// this line sets up the main run loop before `UIApplicationMain`
|
||||
setup_control_flow_observers();
|
||||
|
||||
EventLoop {
|
||||
Ok(EventLoop {
|
||||
window_target: RootEventLoopWindowTarget {
|
||||
p: EventLoopWindowTarget {
|
||||
receiver,
|
||||
@@ -101,12 +105,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
},
|
||||
_marker: PhantomData,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run<F>(self, event_handler: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
unsafe {
|
||||
let application = UIApplication::shared(MainThreadMarker::new().unwrap());
|
||||
@@ -116,10 +120,18 @@ impl<T: 'static> EventLoop<T> {
|
||||
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
|
||||
Note: `EventLoop::run` calls `UIApplicationMain` on iOS",
|
||||
);
|
||||
app_state::will_launch(Box::new(EventLoopHandler {
|
||||
|
||||
let event_handler = std::mem::transmute::<
|
||||
Box<dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow)>,
|
||||
Box<EventHandlerCallback<T>>,
|
||||
>(Box::new(event_handler));
|
||||
|
||||
let handler = EventLoopHandler {
|
||||
f: event_handler,
|
||||
event_loop: self.window_target,
|
||||
}));
|
||||
};
|
||||
|
||||
app_state::will_launch(Box::new(handler));
|
||||
|
||||
// Ensure application delegate is initialized
|
||||
view::WinitApplicationDelegate::class();
|
||||
@@ -237,10 +249,10 @@ fn setup_control_flow_observers() {
|
||||
|
||||
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
||||
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
|
||||
// priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
|
||||
// priority to be 0, in order to send AboutToWait before RedrawRequested. This value was
|
||||
// chosen conservatively to guard against apple using different priorities for their redraw
|
||||
// observers in different OS's or on different devices. If it so happens that it's too
|
||||
// conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
|
||||
// conservative, the main symptom would be non-redraw events coming in after `AboutToWait`.
|
||||
//
|
||||
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
|
||||
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
||||
@@ -314,17 +326,20 @@ fn setup_control_flow_observers() {
|
||||
#[derive(Debug)]
|
||||
pub enum Never {}
|
||||
|
||||
type EventHandlerCallback<T> =
|
||||
dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static;
|
||||
|
||||
pub trait EventHandler: Debug {
|
||||
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow);
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
|
||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
|
||||
}
|
||||
|
||||
struct EventLoopHandler<F, T: 'static> {
|
||||
f: F,
|
||||
struct EventLoopHandler<T: 'static> {
|
||||
f: Box<EventHandlerCallback<T>>,
|
||||
event_loop: RootEventLoopWindowTarget<T>,
|
||||
}
|
||||
|
||||
impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
|
||||
impl<T: 'static> Debug for EventLoopHandler<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("EventLoopHandler")
|
||||
.field("event_loop", &self.event_loop)
|
||||
@@ -332,12 +347,8 @@ impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T> EventHandler for EventLoopHandler<F, T>
|
||||
where
|
||||
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
T: 'static,
|
||||
{
|
||||
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
|
||||
impl<T: 'static> EventHandler for EventLoopHandler<T> {
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
|
||||
(self.f)(
|
||||
event.map_nonuser_event().unwrap(),
|
||||
&self.event_loop,
|
||||
|
||||
@@ -2,30 +2,11 @@
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use icrate::Foundation::{NSInteger, NSUInteger};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::foundation::{NSInteger, NSUInteger};
|
||||
|
||||
use crate::platform::ios::{Idiom, ScreenEdge};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NSOperatingSystemVersion {
|
||||
pub major: NSInteger,
|
||||
pub minor: NSInteger,
|
||||
pub patch: NSInteger,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSOperatingSystemVersion {
|
||||
const ENCODING: Encoding = Encoding::Struct(
|
||||
"NSOperatingSystemVersion",
|
||||
&[
|
||||
NSInteger::ENCODING,
|
||||
NSInteger::ENCODING,
|
||||
NSInteger::ENCODING,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIUserInterfaceIdiom(NSInteger);
|
||||
@@ -70,6 +51,10 @@ impl From<UIUserInterfaceIdiom> for Idiom {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIRectEdge(NSUInteger);
|
||||
|
||||
impl UIRectEdge {
|
||||
pub(crate) const NONE: Self = Self(0);
|
||||
}
|
||||
|
||||
unsafe impl Encode for UIRectEdge {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
@@ -48,12 +48,12 @@
|
||||
//!
|
||||
//! - applicationDidBecomeActive is Resumed
|
||||
//! - applicationWillResignActive is Suspended
|
||||
//! - applicationWillTerminate is LoopDestroyed
|
||||
//! - applicationWillTerminate is LoopExiting
|
||||
//!
|
||||
//! Keep in mind that after LoopDestroyed event is received every attempt to draw with
|
||||
//! Keep in mind that after LoopExiting event is received every attempt to draw with
|
||||
//! opengl will result in segfault.
|
||||
//!
|
||||
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
|
||||
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
|
||||
|
||||
#![cfg(ios_platform)]
|
||||
#![allow(clippy::let_unit_value)]
|
||||
@@ -63,7 +63,7 @@
|
||||
// window size/position.
|
||||
macro_rules! assert_main_thread {
|
||||
($($t:tt)*) => {
|
||||
if !::objc2::foundation::is_main_thread() {
|
||||
if !::icrate::Foundation::is_main_thread() {
|
||||
panic!($($t)*);
|
||||
}
|
||||
};
|
||||
@@ -89,7 +89,7 @@ pub(crate) use self::{
|
||||
|
||||
use self::uikit::UIScreen;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(self) use crate::platform_impl::Fullscreen;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId {
|
||||
|
||||
@@ -6,8 +6,8 @@ use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use objc2::foundation::{MainThreadMarker, NSInteger};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use icrate::Foundation::{MainThreadMarker, NSInteger};
|
||||
use objc2::rc::Id;
|
||||
|
||||
use super::uikit::{UIScreen, UIScreenMode};
|
||||
use crate::{
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
|
||||
// TODO(madsmtm): Remove or refactor this
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode, Shared>);
|
||||
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode>);
|
||||
|
||||
unsafe impl Send for ScreenModeSendSync {}
|
||||
unsafe impl Sync for ScreenModeSendSync {}
|
||||
@@ -33,7 +33,7 @@ pub struct VideoMode {
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
fn new(uiscreen: Id<UIScreen, Shared>, screen_mode: Id<UIScreenMode, Shared>) -> VideoMode {
|
||||
fn new(uiscreen: Id<UIScreen>, screen_mode: Id<UIScreenMode>) -> VideoMode {
|
||||
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
|
||||
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
|
||||
let size = screen_mode.size();
|
||||
@@ -65,7 +65,7 @@ impl VideoMode {
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Inner {
|
||||
uiscreen: Id<UIScreen, Shared>,
|
||||
uiscreen: Id<UIScreen>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
@@ -135,7 +135,7 @@ impl fmt::Debug for MonitorHandle {
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
pub(crate) fn new(uiscreen: Id<UIScreen, Shared>) -> Self {
|
||||
pub(crate) fn new(uiscreen: Id<UIScreen>) -> Self {
|
||||
assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS");
|
||||
Self {
|
||||
inner: Inner { uiscreen },
|
||||
@@ -182,13 +182,8 @@ impl Inner {
|
||||
.uiscreen
|
||||
.availableModes()
|
||||
.into_iter()
|
||||
.map(|mode| {
|
||||
let mode: *const UIScreenMode = mode;
|
||||
let mode = unsafe { Id::retain(mode as *mut UIScreenMode).unwrap() };
|
||||
|
||||
RootVideoMode {
|
||||
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
|
||||
}
|
||||
.map(|mode| RootVideoMode {
|
||||
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -222,7 +217,7 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
|
||||
|
||||
// MonitorHandleExtIOS
|
||||
impl Inner {
|
||||
pub(crate) fn ui_screen(&self) -> &Id<UIScreen, Shared> {
|
||||
pub(crate) fn ui_screen(&self) -> &Id<UIScreen> {
|
||||
&self.uiscreen
|
||||
}
|
||||
|
||||
@@ -237,10 +232,6 @@ impl Inner {
|
||||
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
|
||||
UIScreen::screens(mtm)
|
||||
.into_iter()
|
||||
.map(|screen| {
|
||||
let screen: *const UIScreen = screen;
|
||||
let screen = unsafe { Id::retain(screen as *mut UIScreen).unwrap() };
|
||||
MonitorHandle::new(screen)
|
||||
})
|
||||
.map(MonitorHandle::new)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use objc2::foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
use icrate::Foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::{UIResponder, UIWindow};
|
||||
|
||||
@@ -11,20 +11,21 @@ extern_class!(
|
||||
unsafe impl ClassType for UIApplication {
|
||||
#[inherits(NSObject)]
|
||||
type Super = UIResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIApplication {
|
||||
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self, Shared>> {
|
||||
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self>> {
|
||||
unsafe { msg_send_id![Self::class(), sharedApplication] }
|
||||
}
|
||||
|
||||
pub fn windows(&self) -> Id<NSArray<UIWindow, Shared>, Shared> {
|
||||
pub fn windows(&self) -> Id<NSArray<UIWindow>> {
|
||||
unsafe { msg_send_id![self, windows] }
|
||||
}
|
||||
|
||||
#[sel(statusBarFrame)]
|
||||
#[method(statusBarFrame)]
|
||||
pub fn statusBarFrame(&self) -> CGRect;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use objc2::foundation::NSObject;
|
||||
use objc2::{extern_class, ClassType};
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::{extern_class, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
@@ -7,5 +7,6 @@ extern_class!(
|
||||
|
||||
unsafe impl ClassType for UICoordinateSpace {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use objc2::foundation::{MainThreadMarker, NSObject};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
use icrate::Foundation::{MainThreadMarker, NSObject};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::super::ffi::UIUserInterfaceIdiom;
|
||||
|
||||
@@ -10,16 +10,17 @@ extern_class!(
|
||||
|
||||
unsafe impl ClassType for UIDevice {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIDevice {
|
||||
pub fn current(_mtm: MainThreadMarker) -> Id<Self, Shared> {
|
||||
pub fn current(_mtm: MainThreadMarker) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::class(), currentDevice] }
|
||||
}
|
||||
|
||||
#[sel(userInterfaceIdiom)]
|
||||
#[method(userInterfaceIdiom)]
|
||||
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use objc2::foundation::NSObject;
|
||||
use objc2::{extern_class, ClassType};
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::{extern_class, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
@@ -7,5 +7,6 @@ extern_class!(
|
||||
|
||||
unsafe impl ClassType for UIEvent {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use objc2::foundation::NSString;
|
||||
use icrate::Foundation::NSString;
|
||||
|
||||
mod application;
|
||||
mod coordinate_space;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use objc2::foundation::NSObject;
|
||||
use objc2::{extern_class, ClassType};
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::{extern_class, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
@@ -7,5 +7,6 @@ extern_class!(
|
||||
|
||||
unsafe impl ClassType for UIResponder {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::{UICoordinateSpace, UIScreenMode};
|
||||
|
||||
@@ -11,53 +11,54 @@ extern_class!(
|
||||
|
||||
unsafe impl ClassType for UIScreen {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIScreen {
|
||||
pub fn main(_mtm: MainThreadMarker) -> Id<Self, Shared> {
|
||||
pub fn main(_mtm: MainThreadMarker) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::class(), mainScreen] }
|
||||
}
|
||||
|
||||
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self, Shared>, Shared> {
|
||||
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self>> {
|
||||
unsafe { msg_send_id![Self::class(), screens] }
|
||||
}
|
||||
|
||||
#[sel(bounds)]
|
||||
#[method(bounds)]
|
||||
pub fn bounds(&self) -> CGRect;
|
||||
|
||||
#[sel(scale)]
|
||||
#[method(scale)]
|
||||
pub fn scale(&self) -> CGFloat;
|
||||
|
||||
#[sel(nativeBounds)]
|
||||
#[method(nativeBounds)]
|
||||
pub fn nativeBounds(&self) -> CGRect;
|
||||
|
||||
#[sel(nativeScale)]
|
||||
#[method(nativeScale)]
|
||||
pub fn nativeScale(&self) -> CGFloat;
|
||||
|
||||
#[sel(maximumFramesPerSecond)]
|
||||
#[method(maximumFramesPerSecond)]
|
||||
pub fn maximumFramesPerSecond(&self) -> NSInteger;
|
||||
|
||||
pub fn mirroredScreen(&self) -> Id<Self, Shared> {
|
||||
pub fn mirroredScreen(&self) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::class(), mirroredScreen] }
|
||||
}
|
||||
|
||||
pub fn preferredMode(&self) -> Option<Id<UIScreenMode, Shared>> {
|
||||
pub fn preferredMode(&self) -> Option<Id<UIScreenMode>> {
|
||||
unsafe { msg_send_id![self, preferredMode] }
|
||||
}
|
||||
|
||||
#[sel(setCurrentMode:)]
|
||||
#[method(setCurrentMode:)]
|
||||
pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>);
|
||||
|
||||
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode, Shared>, Shared> {
|
||||
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode>> {
|
||||
unsafe { msg_send_id![self, availableModes] }
|
||||
}
|
||||
|
||||
#[sel(setOverscanCompensation:)]
|
||||
#[method(setOverscanCompensation:)]
|
||||
pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation);
|
||||
|
||||
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace, Shared> {
|
||||
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace> {
|
||||
unsafe { msg_send_id![self, coordinateSpace] }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use objc2::foundation::{CGSize, NSObject};
|
||||
use objc2::{extern_class, extern_methods, ClassType};
|
||||
use icrate::Foundation::{CGSize, NSObject};
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
@@ -7,12 +7,13 @@ extern_class!(
|
||||
|
||||
unsafe impl ClassType for UIScreenMode {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIScreenMode {
|
||||
#[sel(size)]
|
||||
#[method(size)]
|
||||
pub fn size(&self) -> CGSize;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use icrate::Foundation::{CGFloat, CGPoint, NSInteger, NSObject};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::foundation::{CGFloat, CGPoint, NSInteger, NSObject};
|
||||
use objc2::{extern_class, extern_methods, ClassType};
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::UIView;
|
||||
|
||||
@@ -10,27 +10,28 @@ extern_class!(
|
||||
|
||||
unsafe impl ClassType for UITouch {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UITouch {
|
||||
#[sel(locationInView:)]
|
||||
#[method(locationInView:)]
|
||||
pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint;
|
||||
|
||||
#[sel(type)]
|
||||
#[method(type)]
|
||||
pub fn type_(&self) -> UITouchType;
|
||||
|
||||
#[sel(force)]
|
||||
#[method(force)]
|
||||
pub fn force(&self) -> CGFloat;
|
||||
|
||||
#[sel(maximumPossibleForce)]
|
||||
#[method(maximumPossibleForce)]
|
||||
pub fn maximumPossibleForce(&self) -> CGFloat;
|
||||
|
||||
#[sel(altitudeAngle)]
|
||||
#[method(altitudeAngle)]
|
||||
pub fn altitudeAngle(&self) -> CGFloat;
|
||||
|
||||
#[sel(phase)]
|
||||
#[method(phase)]
|
||||
pub fn phase(&self) -> UITouchPhase;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use icrate::Foundation::{NSInteger, NSObject};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::foundation::{NSInteger, NSObject};
|
||||
use objc2::{extern_class, extern_methods, ClassType};
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
@@ -8,12 +8,13 @@ extern_class!(
|
||||
|
||||
unsafe impl ClassType for UITraitCollection {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UITraitCollection {
|
||||
#[sel(forceTouchCapability)]
|
||||
#[method(forceTouchCapability)]
|
||||
pub fn forceTouchCapability(&self) -> UIForceTouchCapability;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use icrate::Foundation::{CGFloat, CGRect, NSObject};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::foundation::{CGFloat, CGRect, NSObject};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::{UICoordinateSpace, UIResponder, UIViewController};
|
||||
|
||||
@@ -12,57 +12,58 @@ extern_class!(
|
||||
unsafe impl ClassType for UIView {
|
||||
#[inherits(NSObject)]
|
||||
type Super = UIResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIView {
|
||||
#[sel(bounds)]
|
||||
#[method(bounds)]
|
||||
pub fn bounds(&self) -> CGRect;
|
||||
|
||||
#[sel(setBounds:)]
|
||||
#[method(setBounds:)]
|
||||
pub fn setBounds(&self, value: CGRect);
|
||||
|
||||
#[sel(frame)]
|
||||
#[method(frame)]
|
||||
pub fn frame(&self) -> CGRect;
|
||||
|
||||
#[sel(setFrame:)]
|
||||
#[method(setFrame:)]
|
||||
pub fn setFrame(&self, value: CGRect);
|
||||
|
||||
#[sel(contentScaleFactor)]
|
||||
#[method(contentScaleFactor)]
|
||||
pub fn contentScaleFactor(&self) -> CGFloat;
|
||||
|
||||
#[sel(setContentScaleFactor:)]
|
||||
#[method(setContentScaleFactor:)]
|
||||
pub fn setContentScaleFactor(&self, val: CGFloat);
|
||||
|
||||
#[sel(setMultipleTouchEnabled:)]
|
||||
#[method(setMultipleTouchEnabled:)]
|
||||
pub fn setMultipleTouchEnabled(&self, val: bool);
|
||||
|
||||
pub fn rootViewController(&self) -> Option<Id<UIViewController, Shared>> {
|
||||
pub fn rootViewController(&self) -> Option<Id<UIViewController>> {
|
||||
unsafe { msg_send_id![self, rootViewController] }
|
||||
}
|
||||
|
||||
#[sel(setRootViewController:)]
|
||||
#[method(setRootViewController:)]
|
||||
pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>);
|
||||
|
||||
#[sel(convertRect:toCoordinateSpace:)]
|
||||
#[method(convertRect:toCoordinateSpace:)]
|
||||
pub fn convertRect_toCoordinateSpace(
|
||||
&self,
|
||||
rect: CGRect,
|
||||
coordinateSpace: &UICoordinateSpace,
|
||||
) -> CGRect;
|
||||
|
||||
#[sel(convertRect:fromCoordinateSpace:)]
|
||||
#[method(convertRect:fromCoordinateSpace:)]
|
||||
pub fn convertRect_fromCoordinateSpace(
|
||||
&self,
|
||||
rect: CGRect,
|
||||
coordinateSpace: &UICoordinateSpace,
|
||||
) -> CGRect;
|
||||
|
||||
#[sel(safeAreaInsets)]
|
||||
#[method(safeAreaInsets)]
|
||||
pub fn safeAreaInsets(&self) -> UIEdgeInsets;
|
||||
|
||||
#[sel(setNeedsDisplay)]
|
||||
#[method(setNeedsDisplay)]
|
||||
pub fn setNeedsDisplay(&self);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use icrate::Foundation::{NSObject, NSUInteger};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::foundation::{NSObject, NSUInteger};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::{UIResponder, UIView};
|
||||
|
||||
@@ -12,28 +12,29 @@ extern_class!(
|
||||
unsafe impl ClassType for UIViewController {
|
||||
#[inherits(NSObject)]
|
||||
type Super = UIResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIViewController {
|
||||
#[sel(attemptRotationToDeviceOrientation)]
|
||||
#[method(attemptRotationToDeviceOrientation)]
|
||||
pub fn attemptRotationToDeviceOrientation();
|
||||
|
||||
#[sel(setNeedsStatusBarAppearanceUpdate)]
|
||||
#[method(setNeedsStatusBarAppearanceUpdate)]
|
||||
pub fn setNeedsStatusBarAppearanceUpdate(&self);
|
||||
|
||||
#[sel(setNeedsUpdateOfHomeIndicatorAutoHidden)]
|
||||
#[method(setNeedsUpdateOfHomeIndicatorAutoHidden)]
|
||||
pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self);
|
||||
|
||||
#[sel(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
|
||||
#[method(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
|
||||
pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self);
|
||||
|
||||
pub fn view(&self) -> Option<Id<UIView, Shared>> {
|
||||
pub fn view(&self) -> Option<Id<UIView>> {
|
||||
unsafe { msg_send_id![self, view] }
|
||||
}
|
||||
|
||||
#[sel(setView:)]
|
||||
#[method(setView:)]
|
||||
pub fn setView(&self, view: Option<&UIView>);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use objc2::foundation::NSObject;
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::{UIResponder, UIScreen, UIView};
|
||||
|
||||
@@ -11,25 +11,26 @@ extern_class!(
|
||||
unsafe impl ClassType for UIWindow {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIView;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIWindow {
|
||||
pub fn screen(&self) -> Id<UIScreen, Shared> {
|
||||
pub fn screen(&self) -> Id<UIScreen> {
|
||||
unsafe { msg_send_id![self, screen] }
|
||||
}
|
||||
|
||||
#[sel(setScreen:)]
|
||||
#[method(setScreen:)]
|
||||
pub fn setScreen(&self, screen: &UIScreen);
|
||||
|
||||
#[sel(setHidden:)]
|
||||
#[method(setHidden:)]
|
||||
pub fn setHidden(&self, flag: bool);
|
||||
|
||||
#[sel(makeKeyAndVisible)]
|
||||
#[method(makeKeyAndVisible)]
|
||||
pub fn makeKeyAndVisible(&self);
|
||||
|
||||
#[sel(isKeyWindow)]
|
||||
#[method(isKeyWindow)]
|
||||
pub fn isKeyWindow(&self) -> bool;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::cell::Cell;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::runtime::Class;
|
||||
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, ClassType};
|
||||
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet};
|
||||
use objc2::declare::{Ivar, IvarDrop};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyClass;
|
||||
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::uikit::{
|
||||
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask,
|
||||
@@ -26,32 +29,28 @@ use crate::{
|
||||
};
|
||||
|
||||
declare_class!(
|
||||
pub(crate) struct WinitView {}
|
||||
pub(crate) struct WinitView;
|
||||
|
||||
unsafe impl ClassType for WinitView {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIView;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
const NAME: &'static str = "WinitUIView";
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[sel(drawRect:)]
|
||||
#[method(drawRect:)]
|
||||
fn draw_rect(&self, rect: CGRect) {
|
||||
let window = self.window().unwrap();
|
||||
unsafe {
|
||||
app_state::handle_nonuser_events(
|
||||
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
|
||||
RootWindowId(window.id()),
|
||||
)))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::RedrawEventsCleared,
|
||||
))),
|
||||
);
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(
|
||||
RootWindowId(window.id()),
|
||||
)));
|
||||
}
|
||||
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
|
||||
}
|
||||
|
||||
#[sel(layoutSubviews)]
|
||||
#[method(layoutSubviews)]
|
||||
fn layout_subviews(&self) {
|
||||
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
|
||||
|
||||
@@ -83,7 +82,7 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
#[sel(setContentScaleFactor:)]
|
||||
#[method(setContentScaleFactor:)]
|
||||
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
|
||||
let _: () =
|
||||
unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] };
|
||||
@@ -133,22 +132,22 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
#[sel(touchesBegan:withEvent:)]
|
||||
#[method(touchesBegan:withEvent:)]
|
||||
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[sel(touchesMoved:withEvent:)]
|
||||
#[method(touchesMoved:withEvent:)]
|
||||
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[sel(touchesEnded:withEvent:)]
|
||||
#[method(touchesEnded:withEvent:)]
|
||||
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[sel(touchesCancelled:withEvent:)]
|
||||
#[method(touchesCancelled:withEvent:)]
|
||||
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
@@ -158,17 +157,17 @@ declare_class!(
|
||||
extern_methods!(
|
||||
#[allow(non_snake_case)]
|
||||
unsafe impl WinitView {
|
||||
fn window(&self) -> Option<Id<WinitUIWindow, Shared>> {
|
||||
fn window(&self) -> Option<Id<WinitUIWindow>> {
|
||||
unsafe { msg_send_id![self, window] }
|
||||
}
|
||||
|
||||
unsafe fn traitCollection(&self) -> Id<UITraitCollection, Shared> {
|
||||
unsafe fn traitCollection(&self) -> Id<UITraitCollection> {
|
||||
msg_send_id![self, traitCollection]
|
||||
}
|
||||
|
||||
// TODO: Allow the user to customize this
|
||||
#[sel(layerClass)]
|
||||
pub(crate) fn layerClass() -> &'static Class;
|
||||
#[method(layerClass)]
|
||||
pub(crate) fn layerClass() -> &'static AnyClass;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -178,9 +177,8 @@ impl WinitView {
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
) -> Id<Self, Shared> {
|
||||
let this: Id<Self, Shared> =
|
||||
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithFrame: frame] };
|
||||
) -> Id<Self> {
|
||||
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
|
||||
|
||||
this.setMultipleTouchEnabled(true);
|
||||
|
||||
@@ -203,7 +201,9 @@ impl WinitView {
|
||||
let trait_collection = unsafe { self.traitCollection() };
|
||||
let touch_capability = trait_collection.forceTouchCapability();
|
||||
// Both the OS _and_ the device need to be checked for force touch support.
|
||||
if touch_capability == UIForceTouchCapability::Available {
|
||||
if touch_capability == UIForceTouchCapability::Available
|
||||
|| touch_type == UITouchType::Pencil
|
||||
{
|
||||
let force = touch.force();
|
||||
let max_possible_force = touch.maximumPossibleForce();
|
||||
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
|
||||
@@ -260,102 +260,108 @@ impl WinitView {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ViewControllerState {
|
||||
prefers_status_bar_hidden: Cell<bool>,
|
||||
prefers_home_indicator_auto_hidden: Cell<bool>,
|
||||
supported_orientations: Cell<UIInterfaceOrientationMask>,
|
||||
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
pub(crate) struct WinitViewController {
|
||||
_prefers_status_bar_hidden: bool,
|
||||
_prefers_home_indicator_auto_hidden: bool,
|
||||
_supported_orientations: UIInterfaceOrientationMask,
|
||||
_preferred_screen_edges_deferring_system_gestures: UIRectEdge,
|
||||
state: IvarDrop<Box<ViewControllerState>, "_state">,
|
||||
}
|
||||
|
||||
mod view_controller_ivars;
|
||||
|
||||
unsafe impl ClassType for WinitViewController {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIViewController;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
const NAME: &'static str = "WinitUIViewController";
|
||||
}
|
||||
|
||||
unsafe impl WinitViewController {
|
||||
#[sel(shouldAutorotate)]
|
||||
#[method(init)]
|
||||
unsafe fn init(this: *mut Self) -> Option<NonNull<Self>> {
|
||||
let this: Option<&mut Self> = msg_send![super(this), init];
|
||||
this.map(|this| {
|
||||
// These are set in WinitViewController::new, it's just to set them
|
||||
// to _something_.
|
||||
Ivar::write(
|
||||
&mut this.state,
|
||||
Box::new(ViewControllerState {
|
||||
prefers_status_bar_hidden: Cell::new(false),
|
||||
prefers_home_indicator_auto_hidden: Cell::new(false),
|
||||
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
|
||||
preferred_screen_edges_deferring_system_gestures: Cell::new(
|
||||
UIRectEdge::NONE,
|
||||
),
|
||||
}),
|
||||
);
|
||||
NonNull::from(this)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl WinitViewController {
|
||||
#[method(shouldAutorotate)]
|
||||
fn should_autorotate(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl WinitViewController {
|
||||
#[sel(prefersStatusBarHidden)]
|
||||
#[method(prefersStatusBarHidden)]
|
||||
fn prefers_status_bar_hidden(&self) -> bool {
|
||||
*self._prefers_status_bar_hidden
|
||||
self.state.prefers_status_bar_hidden.get()
|
||||
}
|
||||
|
||||
#[sel(setPrefersStatusBarHidden:)]
|
||||
fn set_prefers_status_bar_hidden(&mut self, val: bool) {
|
||||
*self._prefers_status_bar_hidden = val;
|
||||
self.setNeedsStatusBarAppearanceUpdate();
|
||||
}
|
||||
|
||||
#[sel(prefersHomeIndicatorAutoHidden)]
|
||||
#[method(prefersHomeIndicatorAutoHidden)]
|
||||
fn prefers_home_indicator_auto_hidden(&self) -> bool {
|
||||
*self._prefers_home_indicator_auto_hidden
|
||||
self.state.prefers_home_indicator_auto_hidden.get()
|
||||
}
|
||||
|
||||
#[sel(setPrefersHomeIndicatorAutoHidden:)]
|
||||
fn set_prefers_home_indicator_auto_hidden(&mut self, val: bool) {
|
||||
*self._prefers_home_indicator_auto_hidden = val;
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.home_indicator_hidden {
|
||||
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
|
||||
} else {
|
||||
os_capabilities.home_indicator_hidden_err_msg("ignoring")
|
||||
}
|
||||
}
|
||||
|
||||
#[sel(supportedInterfaceOrientations)]
|
||||
#[method(supportedInterfaceOrientations)]
|
||||
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
|
||||
*self._supported_orientations
|
||||
self.state.supported_orientations.get()
|
||||
}
|
||||
|
||||
#[sel(setSupportedInterfaceOrientations:)]
|
||||
fn set_supported_orientations(&mut self, val: UIInterfaceOrientationMask) {
|
||||
*self._supported_orientations = val;
|
||||
UIViewController::attemptRotationToDeviceOrientation();
|
||||
}
|
||||
|
||||
#[sel(preferredScreenEdgesDeferringSystemGestures)]
|
||||
#[method(preferredScreenEdgesDeferringSystemGestures)]
|
||||
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
|
||||
*self._preferred_screen_edges_deferring_system_gestures
|
||||
self.state
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.get()
|
||||
}
|
||||
|
||||
#[sel(setPreferredScreenEdgesDeferringSystemGestures:)]
|
||||
fn set_preferred_screen_edges_deferring_system_gestures(&mut self, val: UIRectEdge) {
|
||||
*self._preferred_screen_edges_deferring_system_gestures = val;
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.defer_system_gestures {
|
||||
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
|
||||
} else {
|
||||
os_capabilities.defer_system_gestures_err_msg("ignoring")
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
#[allow(non_snake_case)]
|
||||
unsafe impl WinitViewController {
|
||||
#[sel(setPrefersStatusBarHidden:)]
|
||||
pub(crate) fn setPrefersStatusBarHidden(&self, flag: bool);
|
||||
|
||||
#[sel(setSupportedInterfaceOrientations:)]
|
||||
pub(crate) fn setSupportedInterfaceOrientations(&self, val: UIInterfaceOrientationMask);
|
||||
|
||||
#[sel(setPrefersHomeIndicatorAutoHidden:)]
|
||||
pub(crate) fn setPrefersHomeIndicatorAutoHidden(&self, val: bool);
|
||||
|
||||
#[sel(setPreferredScreenEdgesDeferringSystemGestures:)]
|
||||
pub(crate) fn setPreferredScreenEdgesDeferringSystemGestures(&self, val: UIRectEdge);
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitViewController {
|
||||
pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) {
|
||||
self.state.prefers_status_bar_hidden.set(val);
|
||||
self.setNeedsStatusBarAppearanceUpdate();
|
||||
}
|
||||
|
||||
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
|
||||
self.state.prefers_home_indicator_auto_hidden.set(val);
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.home_indicator_hidden {
|
||||
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
|
||||
} else {
|
||||
os_capabilities.home_indicator_hidden_err_msg("ignoring")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) {
|
||||
self.state
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.set(val);
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.defer_system_gestures {
|
||||
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
|
||||
} else {
|
||||
os_capabilities.defer_system_gestures_err_msg("ignoring")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_supported_interface_orientations(
|
||||
&self,
|
||||
mtm: MainThreadMarker,
|
||||
@@ -378,7 +384,8 @@ impl WinitViewController {
|
||||
| UIInterfaceOrientationMask::PortraitUpsideDown
|
||||
}
|
||||
};
|
||||
self.setSupportedInterfaceOrientations(mask);
|
||||
self.state.supported_orientations.set(mask);
|
||||
UIViewController::attemptRotationToDeviceOrientation();
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
@@ -386,17 +393,18 @@ impl WinitViewController {
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
view: &UIView,
|
||||
) -> Id<Self, Shared> {
|
||||
let this: Id<Self, Shared> =
|
||||
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], init] };
|
||||
) -> Id<Self> {
|
||||
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), init] };
|
||||
|
||||
this.setPrefersStatusBarHidden(platform_attributes.prefers_status_bar_hidden);
|
||||
this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
|
||||
|
||||
this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations);
|
||||
|
||||
this.setPrefersHomeIndicatorAutoHidden(platform_attributes.prefers_home_indicator_hidden);
|
||||
this.set_prefers_home_indicator_auto_hidden(
|
||||
platform_attributes.prefers_home_indicator_hidden,
|
||||
);
|
||||
|
||||
this.setPreferredScreenEdgesDeferringSystemGestures(
|
||||
this.set_preferred_screen_edges_deferring_system_gestures(
|
||||
platform_attributes
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.into(),
|
||||
@@ -410,15 +418,17 @@ impl WinitViewController {
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct WinitUIWindow {}
|
||||
pub(crate) struct WinitUIWindow;
|
||||
|
||||
unsafe impl ClassType for WinitUIWindow {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIWindow;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
const NAME: &'static str = "WinitUIWindow";
|
||||
}
|
||||
|
||||
unsafe impl WinitUIWindow {
|
||||
#[sel(becomeKeyWindow)]
|
||||
#[method(becomeKeyWindow)]
|
||||
fn become_key_window(&self) {
|
||||
unsafe {
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
@@ -429,7 +439,7 @@ declare_class!(
|
||||
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
|
||||
}
|
||||
|
||||
#[sel(resignKeyWindow)]
|
||||
#[method(resignKeyWindow)]
|
||||
fn resign_key_window(&self) {
|
||||
unsafe {
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
@@ -449,9 +459,8 @@ impl WinitUIWindow {
|
||||
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
view_controller: &UIViewController,
|
||||
) -> Id<Self, Shared> {
|
||||
let this: Id<Self, Shared> =
|
||||
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithFrame: frame] };
|
||||
) -> Id<Self> {
|
||||
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
|
||||
|
||||
this.setRootViewController(Some(view_controller));
|
||||
|
||||
@@ -478,15 +487,17 @@ impl WinitUIWindow {
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
pub struct WinitApplicationDelegate {}
|
||||
pub struct WinitApplicationDelegate;
|
||||
|
||||
unsafe impl ClassType for WinitApplicationDelegate {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
const NAME: &'static str = "WinitApplicationDelegate";
|
||||
}
|
||||
|
||||
// UIApplicationDelegate protocol
|
||||
unsafe impl WinitApplicationDelegate {
|
||||
#[sel(application:didFinishLaunchingWithOptions:)]
|
||||
#[method(application:didFinishLaunchingWithOptions:)]
|
||||
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
|
||||
unsafe {
|
||||
app_state::did_finish_launching();
|
||||
@@ -494,22 +505,23 @@ declare_class!(
|
||||
true
|
||||
}
|
||||
|
||||
#[sel(applicationDidBecomeActive:)]
|
||||
#[method(applicationDidBecomeActive:)]
|
||||
fn did_become_active(&self, _application: &UIApplication) {
|
||||
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
|
||||
}
|
||||
|
||||
#[sel(applicationWillResignActive:)]
|
||||
#[method(applicationWillResignActive:)]
|
||||
fn will_resign_active(&self, _application: &UIApplication) {
|
||||
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
|
||||
}
|
||||
|
||||
#[sel(applicationWillEnterForeground:)]
|
||||
#[method(applicationWillEnterForeground:)]
|
||||
fn will_enter_foreground(&self, _application: &UIApplication) {}
|
||||
#[sel(applicationDidEnterBackground:)]
|
||||
|
||||
#[method(applicationDidEnterBackground:)]
|
||||
fn did_enter_background(&self, _application: &UIApplication) {}
|
||||
|
||||
#[sel(applicationWillTerminate:)]
|
||||
#[method(applicationWillTerminate:)]
|
||||
fn will_terminate(&self, application: &UIApplication) {
|
||||
let mut events = Vec::new();
|
||||
for window in application.windows().iter() {
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
ffi::c_void,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use objc2::foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadMarker};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::runtime::Object;
|
||||
use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{class, msg_send};
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
|
||||
|
||||
@@ -23,7 +19,6 @@ use crate::{
|
||||
platform_impl::platform::{
|
||||
app_state,
|
||||
event_loop::{EventProxy, EventWrapper},
|
||||
ffi::UIRectEdge,
|
||||
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
|
||||
},
|
||||
window::{
|
||||
@@ -33,9 +28,9 @@ use crate::{
|
||||
};
|
||||
|
||||
pub struct Inner {
|
||||
pub(crate) window: Id<WinitUIWindow, Shared>,
|
||||
pub(crate) view_controller: Id<WinitViewController, Shared>,
|
||||
pub(crate) view: Id<WinitView, Shared>,
|
||||
window: Id<WinitUIWindow>,
|
||||
view_controller: Id<WinitViewController>,
|
||||
view: Id<WinitView>,
|
||||
gl_or_metal_backed: bool,
|
||||
}
|
||||
|
||||
@@ -75,73 +70,65 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pre_present_notify(&self) {}
|
||||
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
unsafe {
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
let position = LogicalPosition {
|
||||
x: safe_area.origin.x as f64,
|
||||
y: safe_area.origin.y as f64,
|
||||
};
|
||||
let scale_factor = self.scale_factor();
|
||||
Ok(position.to_physical(scale_factor))
|
||||
}
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
let position = LogicalPosition {
|
||||
x: safe_area.origin.x as f64,
|
||||
y: safe_area.origin.y as f64,
|
||||
};
|
||||
let scale_factor = self.scale_factor();
|
||||
Ok(position.to_physical(scale_factor))
|
||||
}
|
||||
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
unsafe {
|
||||
let screen_frame = self.screen_frame();
|
||||
let position = LogicalPosition {
|
||||
x: screen_frame.origin.x as f64,
|
||||
y: screen_frame.origin.y as f64,
|
||||
};
|
||||
let scale_factor = self.scale_factor();
|
||||
Ok(position.to_physical(scale_factor))
|
||||
}
|
||||
let screen_frame = self.screen_frame();
|
||||
let position = LogicalPosition {
|
||||
x: screen_frame.origin.x as f64,
|
||||
y: screen_frame.origin.y as f64,
|
||||
};
|
||||
let scale_factor = self.scale_factor();
|
||||
Ok(position.to_physical(scale_factor))
|
||||
}
|
||||
|
||||
pub fn set_outer_position(&self, physical_position: Position) {
|
||||
unsafe {
|
||||
let scale_factor = self.scale_factor();
|
||||
let position = physical_position.to_logical::<f64>(scale_factor);
|
||||
let screen_frame = self.screen_frame();
|
||||
let new_screen_frame = CGRect {
|
||||
origin: CGPoint {
|
||||
x: position.x as _,
|
||||
y: position.y as _,
|
||||
},
|
||||
size: screen_frame.size,
|
||||
};
|
||||
let bounds = self.rect_from_screen_space(new_screen_frame);
|
||||
self.window.setBounds(bounds);
|
||||
}
|
||||
let scale_factor = self.scale_factor();
|
||||
let position = physical_position.to_logical::<f64>(scale_factor);
|
||||
let screen_frame = self.screen_frame();
|
||||
let new_screen_frame = CGRect {
|
||||
origin: CGPoint {
|
||||
x: position.x as _,
|
||||
y: position.y as _,
|
||||
},
|
||||
size: screen_frame.size,
|
||||
};
|
||||
let bounds = self.rect_from_screen_space(new_screen_frame);
|
||||
self.window.setBounds(bounds);
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
unsafe {
|
||||
let scale_factor = self.scale_factor();
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
let size = LogicalSize {
|
||||
width: safe_area.size.width as f64,
|
||||
height: safe_area.size.height as f64,
|
||||
};
|
||||
size.to_physical(scale_factor)
|
||||
}
|
||||
let scale_factor = self.scale_factor();
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
let size = LogicalSize {
|
||||
width: safe_area.size.width as f64,
|
||||
height: safe_area.size.height as f64,
|
||||
};
|
||||
size.to_physical(scale_factor)
|
||||
}
|
||||
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
unsafe {
|
||||
let scale_factor = self.scale_factor();
|
||||
let screen_frame = self.screen_frame();
|
||||
let size = LogicalSize {
|
||||
width: screen_frame.size.width as f64,
|
||||
height: screen_frame.size.height as f64,
|
||||
};
|
||||
size.to_physical(scale_factor)
|
||||
}
|
||||
let scale_factor = self.scale_factor();
|
||||
let screen_frame = self.screen_frame();
|
||||
let size = LogicalSize {
|
||||
width: screen_frame.size.width as f64,
|
||||
height: screen_frame.size.height as f64,
|
||||
};
|
||||
size.to_physical(scale_factor)
|
||||
}
|
||||
|
||||
pub fn set_inner_size(&self, _size: Size) {
|
||||
warn!("not clear what `Window::set_inner_size` means on iOS");
|
||||
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
|
||||
Some(self.inner_size())
|
||||
}
|
||||
|
||||
pub fn set_min_inner_size(&self, _dimensions: Option<Size>) {
|
||||
@@ -262,22 +249,20 @@ impl Inner {
|
||||
}
|
||||
|
||||
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
unsafe {
|
||||
let monitor = self.current_monitor_inner();
|
||||
let uiscreen = monitor.ui_screen();
|
||||
let screen_space_bounds = self.screen_frame();
|
||||
let screen_bounds = uiscreen.bounds();
|
||||
let monitor = self.current_monitor_inner();
|
||||
let uiscreen = monitor.ui_screen();
|
||||
let screen_space_bounds = self.screen_frame();
|
||||
let screen_bounds = uiscreen.bounds();
|
||||
|
||||
// TODO: track fullscreen instead of relying on brittle float comparisons
|
||||
if screen_space_bounds.origin.x == screen_bounds.origin.x
|
||||
&& screen_space_bounds.origin.y == screen_bounds.origin.y
|
||||
&& screen_space_bounds.size.width == screen_bounds.size.width
|
||||
&& screen_space_bounds.size.height == screen_bounds.size.height
|
||||
{
|
||||
Some(Fullscreen::Borderless(Some(monitor)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
// TODO: track fullscreen instead of relying on brittle float comparisons
|
||||
if screen_space_bounds.origin.x == screen_bounds.origin.x
|
||||
&& screen_space_bounds.origin.y == screen_bounds.origin.y
|
||||
&& screen_space_bounds.size.width == screen_bounds.size.width
|
||||
&& screen_space_bounds.size.height == screen_bounds.size.height
|
||||
{
|
||||
Some(Fullscreen::Borderless(Some(monitor)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,32 +360,7 @@ impl Inner {
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
pub inner: Inner,
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Window {}
|
||||
unsafe impl Sync for Window {}
|
||||
|
||||
impl Deref for Window {
|
||||
type Target = Inner;
|
||||
|
||||
fn deref(&self) -> &Inner {
|
||||
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Window {
|
||||
fn deref_mut(&mut self) -> &mut Inner {
|
||||
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
|
||||
&mut self.inner
|
||||
}
|
||||
inner: MainThreadBound<Inner>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
@@ -497,29 +457,29 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
let inner = Inner {
|
||||
window,
|
||||
view_controller,
|
||||
view,
|
||||
gl_or_metal_backed,
|
||||
};
|
||||
Ok(Window {
|
||||
inner: Inner {
|
||||
window,
|
||||
view_controller,
|
||||
view,
|
||||
gl_or_metal_backed,
|
||||
},
|
||||
inner: MainThreadBound::new(inner, mtm),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) {
|
||||
// For now, don't actually do queuing, since it may be less predictable
|
||||
self.maybe_wait_on_main(f)
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
|
||||
self.inner.get_on_main(|inner, _mtm| f(inner))
|
||||
}
|
||||
}
|
||||
|
||||
// WindowExtIOS
|
||||
impl Inner {
|
||||
pub fn ui_window(&self) -> *mut c_void {
|
||||
Id::as_ptr(&self.window) as *mut c_void
|
||||
}
|
||||
pub fn ui_view_controller(&self) -> *mut c_void {
|
||||
Id::as_ptr(&self.view_controller) as *mut c_void
|
||||
}
|
||||
pub fn ui_view(&self) -> *mut c_void {
|
||||
Id::as_ptr(&self.view) as *mut c_void
|
||||
}
|
||||
|
||||
pub fn set_scale_factor(&self, scale_factor: f64) {
|
||||
assert!(
|
||||
dpi::validate_scale_factor(scale_factor),
|
||||
@@ -538,42 +498,37 @@ impl Inner {
|
||||
|
||||
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||
self.view_controller
|
||||
.setPrefersHomeIndicatorAutoHidden(hidden);
|
||||
.set_prefers_home_indicator_auto_hidden(hidden);
|
||||
}
|
||||
|
||||
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
||||
let edges: UIRectEdge = edges.into();
|
||||
self.view_controller
|
||||
.setPreferredScreenEdgesDeferringSystemGestures(edges);
|
||||
.set_preferred_screen_edges_deferring_system_gestures(edges.into());
|
||||
}
|
||||
|
||||
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||
self.view_controller.setPrefersStatusBarHidden(hidden);
|
||||
self.view_controller.set_prefers_status_bar_hidden(hidden);
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
// requires main thread
|
||||
unsafe fn screen_frame(&self) -> CGRect {
|
||||
fn screen_frame(&self) -> CGRect {
|
||||
self.rect_to_screen_space(self.window.bounds())
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
let screen_space = self.window.screen().coordinateSpace();
|
||||
self.window
|
||||
.convertRect_toCoordinateSpace(rect, &screen_space)
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
let screen_space = self.window.screen().coordinateSpace();
|
||||
self.window
|
||||
.convertRect_fromCoordinateSpace(rect, &screen_space)
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn safe_area_screen_space(&self) -> CGRect {
|
||||
fn safe_area_screen_space(&self) -> CGRect {
|
||||
let bounds = self.window.bounds();
|
||||
if app_state::os_capabilities().safe_area {
|
||||
let safe_area = self.window.safeAreaInsets();
|
||||
@@ -591,7 +546,7 @@ impl Inner {
|
||||
} else {
|
||||
let screen_frame = self.rect_to_screen_space(bounds);
|
||||
let status_bar_frame = {
|
||||
let app = UIApplication::shared(MainThreadMarker::new().unwrap_unchecked()).expect(
|
||||
let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect(
|
||||
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS",
|
||||
);
|
||||
app.statusBarFrame()
|
||||
@@ -648,8 +603,8 @@ impl From<u64> for WindowId {
|
||||
unsafe impl Send for WindowId {}
|
||||
unsafe impl Sync for WindowId {}
|
||||
|
||||
impl From<&Object> for WindowId {
|
||||
fn from(window: &Object) -> WindowId {
|
||||
impl From<&AnyObject> for WindowId {
|
||||
fn from(window: &AnyObject) -> WindowId {
|
||||
WindowId {
|
||||
window: window as *const _ as _,
|
||||
}
|
||||
|
||||
@@ -354,7 +354,6 @@ impl KbdState {
|
||||
self.post_init(state, keymap);
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool {
|
||||
unsafe { (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 }
|
||||
}
|
||||
|
||||
@@ -3,45 +3,45 @@
|
||||
#[cfg(all(not(x11_platform), not(wayland_platform)))]
|
||||
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
use std::error::Error;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{collections::VecDeque, env, fmt};
|
||||
#[cfg(x11_platform)]
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
mem::MaybeUninit,
|
||||
os::raw::*,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
use once_cell::sync::Lazy;
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||
use smol_str::SmolStr;
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub use self::x11::XNotSupported;
|
||||
#[cfg(x11_platform)]
|
||||
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
|
||||
#[cfg(x11_platform)]
|
||||
use crate::platform::x11::XlibErrorHook;
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::{Event, KeyEvent},
|
||||
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
event_loop::{
|
||||
AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
|
||||
EventLoopWindowTarget as RootELW,
|
||||
},
|
||||
icon::Icon,
|
||||
keyboard::{Key, KeyCode},
|
||||
platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode},
|
||||
platform::{
|
||||
modifier_supplement::KeyEventExtModifierSupplement, pump_events::PumpStatus,
|
||||
scancode::KeyCodeExtScancode,
|
||||
},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
WindowAttributes, WindowButtons, WindowLevel,
|
||||
ActivationToken, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme,
|
||||
UserAttentionType, WindowAttributes, WindowButtons, WindowLevel,
|
||||
},
|
||||
};
|
||||
#[cfg(x11_platform)]
|
||||
pub use x11::XNotSupported;
|
||||
#[cfg(x11_platform)]
|
||||
use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError};
|
||||
|
||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||
pub(self) use crate::platform_impl::Fullscreen;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
pub mod common;
|
||||
#[cfg(wayland_platform)]
|
||||
@@ -49,15 +49,6 @@ pub mod wayland;
|
||||
#[cfg(x11_platform)]
|
||||
pub mod x11;
|
||||
|
||||
/// Environment variable specifying which backend should be used on unix platform.
|
||||
///
|
||||
/// Legal values are x11 and wayland. If this variable is set only the named backend
|
||||
/// will be tried by winit. If it is not set, winit will try to connect to a wayland connection,
|
||||
/// and if it fails will fallback on x11.
|
||||
///
|
||||
/// If this variable is set with any other value, winit will panic.
|
||||
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum Backend {
|
||||
#[cfg(x11_platform)]
|
||||
@@ -87,32 +78,38 @@ impl ApplicationName {
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub name: Option<ApplicationName>,
|
||||
pub activation_token: Option<ActivationToken>,
|
||||
#[cfg(x11_platform)]
|
||||
pub visual_infos: Option<XVisualInfo>,
|
||||
#[cfg(x11_platform)]
|
||||
pub x11: X11WindowBuilderAttributes,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(x11_platform)]
|
||||
pub struct X11WindowBuilderAttributes {
|
||||
pub visual_id: Option<x11rb::protocol::xproto::Visualid>,
|
||||
pub screen_id: Option<i32>,
|
||||
#[cfg(x11_platform)]
|
||||
pub base_size: Option<Size>,
|
||||
#[cfg(x11_platform)]
|
||||
pub override_redirect: bool,
|
||||
#[cfg(x11_platform)]
|
||||
pub x11_window_types: Vec<XWindowType>,
|
||||
|
||||
/// The parent window to embed this window into.
|
||||
pub embed_window: Option<x11rb::protocol::xproto::Window>,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: None,
|
||||
activation_token: None,
|
||||
#[cfg(x11_platform)]
|
||||
visual_infos: None,
|
||||
#[cfg(x11_platform)]
|
||||
screen_id: None,
|
||||
#[cfg(x11_platform)]
|
||||
base_size: None,
|
||||
#[cfg(x11_platform)]
|
||||
override_redirect: false,
|
||||
#[cfg(x11_platform)]
|
||||
x11_window_types: vec![XWindowType::Normal],
|
||||
x11: X11WindowBuilderAttributes {
|
||||
visual_id: None,
|
||||
screen_id: None,
|
||||
base_size: None,
|
||||
override_redirect: false,
|
||||
x11_window_types: vec![XWindowType::Normal],
|
||||
embed_window: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,23 +120,21 @@ pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OsError {
|
||||
Misc(&'static str),
|
||||
#[cfg(x11_platform)]
|
||||
XError(XError),
|
||||
#[cfg(x11_platform)]
|
||||
XMisc(&'static str),
|
||||
XError(Arc<X11Error>),
|
||||
#[cfg(wayland_platform)]
|
||||
WaylandMisc(&'static str),
|
||||
WaylandError(Arc<wayland::WaylandError>),
|
||||
}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
OsError::Misc(e) => _f.pad(e),
|
||||
#[cfg(x11_platform)]
|
||||
OsError::XError(ref e) => _f.pad(&e.description),
|
||||
#[cfg(x11_platform)]
|
||||
OsError::XMisc(e) => _f.pad(e),
|
||||
OsError::XError(ref e) => fmt::Display::fmt(e, _f),
|
||||
#[cfg(wayland_platform)]
|
||||
OsError::WaylandMisc(e) => _f.pad(e),
|
||||
OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,6 +306,14 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
|
||||
f(self)
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
|
||||
f(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
match self {
|
||||
@@ -367,8 +370,13 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_inner_size(size))
|
||||
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
|
||||
x11_or_wayland!(match self; Window(w) => w.request_inner_size(size))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
|
||||
x11_or_wayland!(match self; Window(w) => w.request_activation_token())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -541,12 +549,7 @@ impl Window {
|
||||
}
|
||||
}
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
match self {
|
||||
#[cfg(x11_platform)]
|
||||
Window::X(ref w) => w.request_user_attention(request_type),
|
||||
#[cfg(wayland_platform)]
|
||||
Window::Wayland(ref w) => w.request_user_attention(request_type),
|
||||
}
|
||||
x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -554,6 +557,11 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(w) => w.request_redraw())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pre_present_notify(&self) {
|
||||
x11_or_wayland!(match self; Window(w) => w.pre_present_notify())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||
match self {
|
||||
@@ -730,7 +738,9 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
|
||||
pub(crate) fn new(
|
||||
attributes: &PlatformSpecificEventLoopAttributes,
|
||||
) -> Result<Self, EventLoopError> {
|
||||
if !attributes.any_thread && !is_main_thread() {
|
||||
panic!(
|
||||
"Initializing the event loop outside of the main thread is a significant \
|
||||
@@ -740,65 +750,26 @@ impl<T: 'static> EventLoop<T> {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
if attributes.forced_backend == Some(Backend::X) {
|
||||
// TODO: Propagate
|
||||
return EventLoop::new_x11_any_thread().unwrap();
|
||||
}
|
||||
|
||||
// NOTE: Wayland first because of X11 could be present under wayland as well.
|
||||
#[cfg(wayland_platform)]
|
||||
if attributes.forced_backend == Some(Backend::Wayland) {
|
||||
// TODO: Propagate
|
||||
return EventLoop::new_wayland_any_thread().expect("failed to open Wayland connection");
|
||||
if attributes.forced_backend == Some(Backend::Wayland)
|
||||
|| env::var("WAYLAND_DISPLAY").is_ok()
|
||||
{
|
||||
return EventLoop::new_wayland_any_thread().map_err(Into::into);
|
||||
}
|
||||
|
||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
||||
match env_var.as_str() {
|
||||
"x11" => {
|
||||
// TODO: propagate
|
||||
#[cfg(x11_platform)]
|
||||
return EventLoop::new_x11_any_thread()
|
||||
.expect("Failed to initialize X11 backend");
|
||||
#[cfg(not(x11_platform))]
|
||||
panic!("x11 feature is not enabled")
|
||||
}
|
||||
"wayland" => {
|
||||
#[cfg(wayland_platform)]
|
||||
return EventLoop::new_wayland_any_thread()
|
||||
.expect("Failed to initialize Wayland backend");
|
||||
#[cfg(not(wayland_platform))]
|
||||
panic!("wayland feature is not enabled");
|
||||
}
|
||||
_ => panic!(
|
||||
"Unknown environment variable value for {BACKEND_PREFERENCE_ENV_VAR}, try one of `x11`,`wayland`",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
let wayland_err = match EventLoop::new_wayland_any_thread() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
let x11_err = match EventLoop::new_x11_any_thread() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
if attributes.forced_backend == Some(Backend::X) || env::var("DISPLAY").is_ok() {
|
||||
return Ok(EventLoop::new_x11_any_thread().unwrap());
|
||||
}
|
||||
|
||||
#[cfg(not(wayland_platform))]
|
||||
let wayland_err = "backend disabled";
|
||||
#[cfg(not(x11_platform))]
|
||||
let x11_err = "backend disabled";
|
||||
|
||||
panic!(
|
||||
"Failed to initialize any backend! Wayland status: {wayland_err:?} X11 status: {x11_err:?}",
|
||||
);
|
||||
Err(EventLoopError::Os(os_error!(OsError::Misc(
|
||||
"neither WAYLAND_DISPLAY nor DISPLAY is set."
|
||||
))))
|
||||
}
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
|
||||
fn new_wayland_any_thread() -> Result<EventLoop<T>, EventLoopError> {
|
||||
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
|
||||
}
|
||||
|
||||
@@ -816,18 +787,25 @@ impl<T: 'static> EventLoop<T> {
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, callback: F) -> i32
|
||||
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_return(callback))
|
||||
self.run_ondemand(callback)
|
||||
}
|
||||
|
||||
pub fn run<F>(self, callback: F) -> !
|
||||
pub fn run_ondemand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback))
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_ondemand(callback))
|
||||
}
|
||||
|
||||
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
|
||||
@@ -907,12 +885,12 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
}
|
||||
|
||||
fn sticky_exit_callback<T, F>(
|
||||
evt: Event<'_, T>,
|
||||
evt: Event<T>,
|
||||
target: &RootELW<T>,
|
||||
control_flow: &mut ControlFlow,
|
||||
callback: &mut F,
|
||||
) where
|
||||
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
// make ControlFlow::ExitWithCode sticky by providing a dummy
|
||||
// control flow reference if it is already ExitWithCode.
|
||||
@@ -923,11 +901,18 @@ fn sticky_exit_callback<T, F>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the minimum `Option<Duration>`, taking into account that `None`
|
||||
/// equates to an infinite timeout, not a zero timeout (so can't just use
|
||||
/// `Option::min`)
|
||||
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
|
||||
a.map_or(b, |a_timeout| {
|
||||
b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn is_main_thread() -> bool {
|
||||
use libc::{c_long, getpid, syscall, SYS_gettid};
|
||||
|
||||
unsafe { syscall(SYS_gettid) == getpid() as c_long }
|
||||
rustix::thread::gettid() == rustix::process::getpid()
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
//! The event-loop routines.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::error::Error;
|
||||
use std::io::Result as IOResult;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::process;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
|
||||
|
||||
use sctk::reexports::calloop;
|
||||
use sctk::reexports::calloop::Error as CalloopError;
|
||||
use sctk::reexports::client::globals;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource};
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
use crate::event::{Event, StartCause, WindowEvent};
|
||||
use crate::error::{EventLoopError, OsError as RootOsError};
|
||||
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
|
||||
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
|
||||
use crate::platform::pump_events::PumpStatus;
|
||||
use crate::platform_impl::platform::min_timeout;
|
||||
use crate::platform_impl::platform::sticky_exit_callback;
|
||||
use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
|
||||
use crate::platform_impl::{EventLoopWindowTarget as PlatformEventLoopWindowTarget, OsError};
|
||||
|
||||
mod proxy;
|
||||
pub mod sink;
|
||||
@@ -29,12 +32,23 @@ pub use proxy::EventLoopProxy;
|
||||
use sink::EventSink;
|
||||
|
||||
use super::state::{WindowCompositorUpdate, WinitState};
|
||||
use super::{DeviceId, WindowId};
|
||||
use super::window::state::FrameCallbackState;
|
||||
use super::{DeviceId, WaylandError, WindowId};
|
||||
|
||||
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
|
||||
|
||||
/// The Wayland event loop.
|
||||
pub struct EventLoop<T: 'static> {
|
||||
/// Has `run` or `run_ondemand` been called or a call to `pump_events` that starts the loop
|
||||
loop_running: bool,
|
||||
|
||||
/// The application's latest control_flow state
|
||||
control_flow: ControlFlow,
|
||||
|
||||
buffer_sink: EventSink,
|
||||
compositor_updates: Vec<WindowCompositorUpdate>,
|
||||
window_ids: Vec<WindowId>,
|
||||
|
||||
/// Sender of user events.
|
||||
user_events_sender: calloop::channel::Sender<T>,
|
||||
|
||||
@@ -59,50 +73,93 @@ pub struct EventLoop<T: 'static> {
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> Result<EventLoop<T>, Box<dyn Error>> {
|
||||
let connection = Connection::connect_to_env()?;
|
||||
pub fn new() -> Result<EventLoop<T>, EventLoopError> {
|
||||
macro_rules! map_err {
|
||||
($e:expr, $err:expr) => {
|
||||
$e.map_err(|error| os_error!($err(error).into()))
|
||||
};
|
||||
}
|
||||
|
||||
let (globals, mut event_queue) = globals::registry_queue_init(&connection)?;
|
||||
let connection = map_err!(Connection::connect_to_env(), WaylandError::Connection)?;
|
||||
|
||||
let (globals, mut event_queue) = map_err!(
|
||||
globals::registry_queue_init(&connection),
|
||||
WaylandError::Global
|
||||
)?;
|
||||
let queue_handle = event_queue.handle();
|
||||
|
||||
let event_loop = calloop::EventLoop::<WinitState>::try_new()?;
|
||||
let event_loop = map_err!(
|
||||
calloop::EventLoop::<WinitState>::try_new(),
|
||||
WaylandError::Calloop
|
||||
)?;
|
||||
|
||||
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?;
|
||||
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())
|
||||
.map_err(|error| os_error!(error))?;
|
||||
|
||||
// NOTE: do a roundtrip after binding the globals to prevent potential
|
||||
// races with the server.
|
||||
event_queue.roundtrip(&mut winit_state)?;
|
||||
map_err!(
|
||||
event_queue.roundtrip(&mut winit_state),
|
||||
WaylandError::Dispatch
|
||||
)?;
|
||||
|
||||
// Register Wayland source.
|
||||
let wayland_source = WaylandSource::new(event_queue)?;
|
||||
let wayland_source = map_err!(WaylandSource::new(event_queue), WaylandError::Wire)?;
|
||||
let wayland_dispatcher =
|
||||
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
|
||||
queue.dispatch_pending(winit_state)
|
||||
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state: &mut WinitState| {
|
||||
let result = queue.dispatch_pending(winit_state);
|
||||
if result.is_ok()
|
||||
&& (!winit_state.events_sink.is_empty()
|
||||
|| !winit_state.window_compositor_updates.is_empty())
|
||||
{
|
||||
winit_state.dispatched_events = true;
|
||||
}
|
||||
result
|
||||
});
|
||||
|
||||
event_loop
|
||||
.handle()
|
||||
.register_dispatcher(wayland_dispatcher.clone())?;
|
||||
map_err!(
|
||||
event_loop
|
||||
.handle()
|
||||
.register_dispatcher(wayland_dispatcher.clone()),
|
||||
WaylandError::Calloop
|
||||
)?;
|
||||
|
||||
// Setup the user proxy.
|
||||
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
|
||||
let pending_user_events_clone = pending_user_events.clone();
|
||||
let (user_events_sender, user_events_channel) = calloop::channel::channel();
|
||||
event_loop
|
||||
let result = event_loop
|
||||
.handle()
|
||||
.insert_source(user_events_channel, move |event, _, _| {
|
||||
if let calloop::channel::Event::Msg(msg) = event {
|
||||
pending_user_events_clone.borrow_mut().push(msg);
|
||||
}
|
||||
})?;
|
||||
.insert_source(
|
||||
user_events_channel,
|
||||
move |event, _, winit_state: &mut WinitState| {
|
||||
if let calloop::channel::Event::Msg(msg) = event {
|
||||
winit_state.dispatched_events = true;
|
||||
pending_user_events_clone.borrow_mut().push(msg);
|
||||
}
|
||||
},
|
||||
)
|
||||
.map_err(|error| error.error);
|
||||
map_err!(result, WaylandError::Calloop)?;
|
||||
|
||||
// An event's loop awakener to wake up for window events from winit's windows.
|
||||
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
|
||||
event_loop
|
||||
let (event_loop_awakener, event_loop_awakener_source) = map_err!(
|
||||
calloop::ping::make_ping()
|
||||
.map_err(|error| CalloopError::OtherError(Box::new(error).into())),
|
||||
WaylandError::Calloop
|
||||
)?;
|
||||
|
||||
let result = event_loop
|
||||
.handle()
|
||||
.insert_source(event_loop_awakener_source, move |_, _, _| {
|
||||
// No extra handling is required, we just need to wake-up.
|
||||
})?;
|
||||
.insert_source(
|
||||
event_loop_awakener_source,
|
||||
move |_, _, winit_state: &mut WinitState| {
|
||||
// Mark that we have something to dispatch.
|
||||
winit_state.dispatched_events = true;
|
||||
},
|
||||
)
|
||||
.map_err(|error| error.error);
|
||||
map_err!(result, WaylandError::Calloop)?;
|
||||
|
||||
let window_target = EventLoopWindowTarget {
|
||||
connection: connection.clone(),
|
||||
@@ -114,6 +171,11 @@ impl<T: 'static> EventLoop<T> {
|
||||
};
|
||||
|
||||
let event_loop = Self {
|
||||
loop_running: false,
|
||||
control_flow: ControlFlow::default(),
|
||||
compositor_updates: Vec::new(),
|
||||
buffer_sink: EventSink::default(),
|
||||
window_ids: Vec::new(),
|
||||
connection,
|
||||
wayland_dispatcher,
|
||||
user_events_sender,
|
||||
@@ -128,45 +190,87 @@ impl<T: 'static> EventLoop<T> {
|
||||
Ok(event_loop)
|
||||
}
|
||||
|
||||
pub fn run<F>(mut self, callback: F) -> !
|
||||
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
let exit_code = self.run_return(callback);
|
||||
process::exit(exit_code);
|
||||
if self.loop_running {
|
||||
return Err(EventLoopError::AlreadyRunning);
|
||||
}
|
||||
|
||||
let exit = loop {
|
||||
match self.pump_events(None, &mut event_handler) {
|
||||
PumpStatus::Exit(0) => {
|
||||
break Ok(());
|
||||
}
|
||||
PumpStatus::Exit(code) => {
|
||||
break Err(EventLoopError::ExitFailure(code));
|
||||
}
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Applications aren't allowed to carry windows between separate
|
||||
// `run_ondemand` calls but if they have only just dropped their
|
||||
// windows we need to make sure those last requests are sent to the
|
||||
// compositor.
|
||||
let _ = self.roundtrip().map_err(EventLoopError::Os);
|
||||
|
||||
exit
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, mut callback: F) -> i32
|
||||
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
let mut control_flow = ControlFlow::Poll;
|
||||
if !self.loop_running {
|
||||
self.loop_running = true;
|
||||
|
||||
// XXX preallocate certian structures to avoid allocating on each loop iteration.
|
||||
let mut window_ids = Vec::<WindowId>::new();
|
||||
let mut compositor_updates = Vec::<WindowCompositorUpdate>::new();
|
||||
let mut buffer_sink = EventSink::new();
|
||||
// Reset the internal state for the loop as we start running to
|
||||
// ensure consistent behaviour in case the loop runs and exits more
|
||||
// than once.
|
||||
self.control_flow = ControlFlow::Poll;
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::Init),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
// Run the initial loop iteration.
|
||||
self.single_iteration(&mut callback, StartCause::Init);
|
||||
}
|
||||
|
||||
// XXX For consistency all platforms must emit a 'Resumed' event even though Wayland
|
||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
||||
callback(Event::Resumed, &self.window_target, &mut control_flow);
|
||||
// Consider the possibility that the `StartCause::Init` iteration could
|
||||
// request to Exit.
|
||||
if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) {
|
||||
self.poll_events_with_timeout(timeout, &mut callback);
|
||||
}
|
||||
if let ControlFlow::ExitWithCode(code) = self.control_flow {
|
||||
self.loop_running = false;
|
||||
|
||||
// XXX We break on errors from dispatches, since if we've got protocol error
|
||||
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
|
||||
// really an option. Instead we inform that the event loop got destroyed. We may
|
||||
// communicate an error that something was terminated, but winit doesn't provide us
|
||||
// with an API to do that via some event.
|
||||
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
|
||||
let exit_code = loop {
|
||||
// Flush the connection.
|
||||
let _ = self.connection.flush();
|
||||
let mut dummy = self.control_flow;
|
||||
sticky_exit_callback(
|
||||
Event::LoopExiting,
|
||||
self.window_target(),
|
||||
&mut dummy,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
PumpStatus::Exit(code)
|
||||
} else {
|
||||
PumpStatus::Continue
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
|
||||
where
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
let cause = loop {
|
||||
let start = Instant::now();
|
||||
|
||||
// TODO(rib): remove this workaround and instead make sure that the calloop
|
||||
// WaylandSource correctly implements the cooperative prepare_read protocol
|
||||
// that support multithreaded wayland clients that may all read from the
|
||||
// same socket.
|
||||
//
|
||||
// During the run of the user callback, some other code monitoring and reading the
|
||||
// Wayland socket may have been run (mesa for example does this with vsync), if that
|
||||
// is the case, some events may have been enqueued in our event queue.
|
||||
@@ -186,272 +290,321 @@ impl<T: 'static> EventLoop<T> {
|
||||
};
|
||||
|
||||
match queue.dispatch_pending(state) {
|
||||
Ok(dispatched) => dispatched > 0,
|
||||
Ok(dispatched) => {
|
||||
state.dispatched_events |= !state.events_sink.is_empty()
|
||||
|| !state.window_compositor_updates.is_empty();
|
||||
dispatched > 0
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Error dispatching wayland queue: {}", error);
|
||||
break 1;
|
||||
self.control_flow = ControlFlow::ExitWithCode(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match control_flow {
|
||||
ControlFlow::ExitWithCode(code) => break code,
|
||||
ControlFlow::Poll => {
|
||||
// Non-blocking dispatch.
|
||||
let timeout = Duration::ZERO;
|
||||
if let Err(error) = self.loop_dispatch(Some(timeout)) {
|
||||
break error.raw_os_error().unwrap_or(1);
|
||||
timeout = if instant_wakeup {
|
||||
Some(Duration::ZERO)
|
||||
} else {
|
||||
let control_flow_timeout = match self.control_flow {
|
||||
ControlFlow::Wait => None,
|
||||
ControlFlow::Poll => Some(Duration::ZERO),
|
||||
ControlFlow::WaitUntil(wait_deadline) => {
|
||||
Some(wait_deadline.saturating_duration_since(start))
|
||||
}
|
||||
// This function shouldn't have to handle any requests to exit
|
||||
// the application (there should be no need to poll for events
|
||||
// if the application has requested to exit) so we consider
|
||||
// it a bug in the backend if we ever see `ExitWithCode` here.
|
||||
ControlFlow::ExitWithCode(_code) => unreachable!(),
|
||||
};
|
||||
min_timeout(control_flow_timeout, timeout)
|
||||
};
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::Poll),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
let timeout = if instant_wakeup {
|
||||
Some(Duration::ZERO)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// NOTE Ideally we should flush as the last thing we do before polling
|
||||
// to wait for events, and this should be done by the calloop
|
||||
// WaylandSource but we currently need to flush writes manually.
|
||||
let _ = self.connection.flush();
|
||||
|
||||
if let Err(error) = self.loop_dispatch(timeout) {
|
||||
break error.raw_os_error().unwrap_or(1);
|
||||
}
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::WaitCancelled {
|
||||
start: Instant::now(),
|
||||
requested_resume: None,
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
ControlFlow::WaitUntil(deadline) => {
|
||||
let start = Instant::now();
|
||||
|
||||
// Compute the amount of time we'll block for.
|
||||
let duration = if deadline > start && !instant_wakeup {
|
||||
deadline - start
|
||||
} else {
|
||||
Duration::ZERO
|
||||
};
|
||||
|
||||
if let Err(error) = self.loop_dispatch(Some(duration)) {
|
||||
break error.raw_os_error().unwrap_or(1);
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
if now < deadline {
|
||||
callback(
|
||||
Event::NewEvents(StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(deadline),
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
)
|
||||
} else {
|
||||
callback(
|
||||
Event::NewEvents(StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume: deadline,
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
)
|
||||
}
|
||||
}
|
||||
if let Err(error) = self.loop_dispatch(timeout) {
|
||||
// NOTE We exit on errors from dispatches, since if we've got protocol error
|
||||
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
|
||||
// really an option. Instead we inform that the event loop got destroyed. We may
|
||||
// communicate an error that something was terminated, but winit doesn't provide us
|
||||
// with an API to do that via some event.
|
||||
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
|
||||
let exit_code = error.raw_os_error().unwrap_or(1);
|
||||
self.control_flow = ControlFlow::ExitWithCode(exit_code);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle pending user events. We don't need back buffer, since we can't dispatch
|
||||
// user events indirectly via callback to the user.
|
||||
for user_event in self.pending_user_events.borrow_mut().drain(..) {
|
||||
// NB: `StartCause::Init` is handled as a special case and doesn't need
|
||||
// to be considered here
|
||||
let cause = match self.control_flow {
|
||||
ControlFlow::Poll => StartCause::Poll,
|
||||
ControlFlow::Wait => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
},
|
||||
ControlFlow::WaitUntil(deadline) => {
|
||||
if Instant::now() < deadline {
|
||||
StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(deadline),
|
||||
}
|
||||
} else {
|
||||
StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume: deadline,
|
||||
}
|
||||
}
|
||||
}
|
||||
// This function shouldn't have to handle any requests to exit
|
||||
// the application (there should be no need to poll for events
|
||||
// if the application has requested to exit) so we consider
|
||||
// it a bug in the backend if we ever see `ExitWithCode` here.
|
||||
ControlFlow::ExitWithCode(_code) => unreachable!(),
|
||||
};
|
||||
|
||||
// Reduce spurious wake-ups.
|
||||
let dispatched_events = self.with_state(|state| state.dispatched_events);
|
||||
if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events {
|
||||
continue;
|
||||
}
|
||||
|
||||
break cause;
|
||||
};
|
||||
|
||||
self.single_iteration(&mut callback, cause);
|
||||
}
|
||||
|
||||
fn single_iteration<F>(&mut self, mut callback: &mut F, cause: StartCause)
|
||||
where
|
||||
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
// NOTE currently just indented to simplify the diff
|
||||
|
||||
let mut control_flow = self.control_flow;
|
||||
|
||||
// We retain these grow-only scratch buffers as part of the EventLoop
|
||||
// for the sake of avoiding lots of reallocs. We take them here to avoid
|
||||
// trying to mutably borrow `self` more than once and we swap them back
|
||||
// when finished.
|
||||
let mut compositor_updates = std::mem::take(&mut self.compositor_updates);
|
||||
let mut buffer_sink = std::mem::take(&mut self.buffer_sink);
|
||||
let mut window_ids = std::mem::take(&mut self.window_ids);
|
||||
|
||||
sticky_exit_callback(
|
||||
Event::NewEvents(cause),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
|
||||
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
|
||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
||||
if cause == StartCause::Init {
|
||||
sticky_exit_callback(
|
||||
Event::Resumed,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
|
||||
// Handle pending user events. We don't need back buffer, since we can't dispatch
|
||||
// user events indirectly via callback to the user.
|
||||
for user_event in self.pending_user_events.borrow_mut().drain(..) {
|
||||
sticky_exit_callback(
|
||||
Event::UserEvent(user_event),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
|
||||
// Drain the pending compositor updates.
|
||||
self.with_state(|state| compositor_updates.append(&mut state.window_compositor_updates));
|
||||
|
||||
for mut compositor_update in compositor_updates.drain(..) {
|
||||
let window_id = compositor_update.window_id;
|
||||
if let Some(scale_factor) = compositor_update.scale_factor {
|
||||
let physical_size = self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
// Set the new scale factor.
|
||||
window.set_scale_factor(scale_factor);
|
||||
let window_size = compositor_update.size.unwrap_or(window.inner_size());
|
||||
logical_to_physical_rounded(window_size, scale_factor)
|
||||
});
|
||||
|
||||
// Stash the old window size.
|
||||
let old_physical_size = physical_size;
|
||||
|
||||
let new_inner_size = Arc::new(Mutex::new(physical_size));
|
||||
sticky_exit_callback(
|
||||
Event::UserEvent(user_event),
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
|
||||
&new_inner_size,
|
||||
)),
|
||||
},
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
let new_logical_size = physical_size.to_logical(scale_factor);
|
||||
|
||||
// Resize the window when user altered the size.
|
||||
if old_physical_size != physical_size {
|
||||
self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
window.resize(new_logical_size);
|
||||
});
|
||||
}
|
||||
|
||||
// Make it queue resize.
|
||||
compositor_update.size = Some(new_logical_size);
|
||||
}
|
||||
|
||||
if let Some(size) = compositor_update.size.take() {
|
||||
let physical_size = self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
let physical_size = logical_to_physical_rounded(size, scale_factor);
|
||||
|
||||
// TODO could probably bring back size reporting optimization.
|
||||
|
||||
// Mark the window as needed a redraw.
|
||||
state
|
||||
.window_requests
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
|
||||
physical_size
|
||||
});
|
||||
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
|
||||
// Drain the pending compositor updates.
|
||||
self.with_state(|state| {
|
||||
compositor_updates.append(&mut state.window_compositor_updates)
|
||||
});
|
||||
|
||||
for mut compositor_update in compositor_updates.drain(..) {
|
||||
let window_id = compositor_update.window_id;
|
||||
if let Some(scale_factor) = compositor_update.scale_factor {
|
||||
let mut physical_size = self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
// Set the new scale factor.
|
||||
window.set_scale_factor(scale_factor);
|
||||
let window_size = compositor_update.size.unwrap_or(window.inner_size());
|
||||
logical_to_physical_rounded(window_size, scale_factor)
|
||||
});
|
||||
|
||||
// Stash the old window size.
|
||||
let old_physical_size = physical_size;
|
||||
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size: &mut physical_size,
|
||||
},
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
let new_logical_size = physical_size.to_logical(scale_factor);
|
||||
|
||||
// Resize the window when user altered the size.
|
||||
if old_physical_size != physical_size {
|
||||
self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
window.resize(new_logical_size);
|
||||
});
|
||||
}
|
||||
|
||||
// Make it queue resize.
|
||||
compositor_update.size = Some(new_logical_size);
|
||||
}
|
||||
|
||||
if let Some(size) = compositor_update.size.take() {
|
||||
let physical_size = self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
let physical_size = logical_to_physical_rounded(size, scale_factor);
|
||||
|
||||
// TODO could probably bring back size reporting optimization.
|
||||
|
||||
// Mark the window as needed a redraw.
|
||||
state
|
||||
.window_requests
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
|
||||
physical_size
|
||||
});
|
||||
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
|
||||
if compositor_update.close_window {
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::CloseRequested,
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
if compositor_update.close_window {
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::CloseRequested,
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Push the events directly from the window.
|
||||
self.with_state(|state| {
|
||||
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
|
||||
});
|
||||
for event in buffer_sink.drain() {
|
||||
let event = event.map_nonuser_event().unwrap();
|
||||
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
|
||||
}
|
||||
// Push the events directly from the window.
|
||||
self.with_state(|state| {
|
||||
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
|
||||
});
|
||||
for event in buffer_sink.drain() {
|
||||
let event = event.map_nonuser_event().unwrap();
|
||||
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
|
||||
}
|
||||
|
||||
// Handle non-synthetic events.
|
||||
self.with_state(|state| {
|
||||
buffer_sink.append(&mut state.events_sink);
|
||||
});
|
||||
for event in buffer_sink.drain() {
|
||||
let event = event.map_nonuser_event().unwrap();
|
||||
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
|
||||
}
|
||||
// Handle non-synthetic events.
|
||||
self.with_state(|state| {
|
||||
buffer_sink.append(&mut state.events_sink);
|
||||
});
|
||||
for event in buffer_sink.drain() {
|
||||
let event = event.map_nonuser_event().unwrap();
|
||||
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
|
||||
}
|
||||
|
||||
// Send events cleared.
|
||||
sticky_exit_callback(
|
||||
Event::MainEventsCleared,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
// Collect the window ids
|
||||
self.with_state(|state| {
|
||||
window_ids.extend(state.window_requests.get_mut().keys());
|
||||
});
|
||||
|
||||
// Collect the window ids
|
||||
self.with_state(|state| {
|
||||
window_ids.extend(state.window_requests.get_mut().keys());
|
||||
});
|
||||
for window_id in window_ids.drain(..) {
|
||||
let request_redraw = self.with_state(|state| {
|
||||
let window_requests = state.window_requests.get_mut();
|
||||
if window_requests.get(&window_id).unwrap().take_closed() {
|
||||
mem::drop(window_requests.remove(&window_id));
|
||||
mem::drop(state.windows.get_mut().remove(&window_id));
|
||||
false
|
||||
} else {
|
||||
let mut window = state
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
for window_id in window_ids.drain(..) {
|
||||
let request_redraw = self.with_state(|state| {
|
||||
let window_requests = state.window_requests.get_mut();
|
||||
if window_requests.get(&window_id).unwrap().take_closed() {
|
||||
mem::drop(window_requests.remove(&window_id));
|
||||
mem::drop(state.windows.get_mut().remove(&window_id));
|
||||
if window.frame_callback_state() == FrameCallbackState::Requested {
|
||||
false
|
||||
} else {
|
||||
// Reset the frame callbacks state.
|
||||
window.frame_callback_reset();
|
||||
let mut redraw_requested = window_requests
|
||||
.get(&window_id)
|
||||
.unwrap()
|
||||
.take_redraw_requested();
|
||||
|
||||
// Redraw the frames while at it.
|
||||
redraw_requested |= state
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.refresh_frame();
|
||||
// Redraw the frame while at it.
|
||||
redraw_requested |= window.refresh_frame();
|
||||
|
||||
redraw_requested
|
||||
}
|
||||
});
|
||||
|
||||
if request_redraw {
|
||||
sticky_exit_callback(
|
||||
Event::RedrawRequested(crate::window::WindowId(window_id)),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if request_redraw {
|
||||
sticky_exit_callback(
|
||||
Event::RedrawRequested(crate::window::WindowId(window_id)),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Send RedrawEventCleared.
|
||||
sticky_exit_callback(
|
||||
Event::RedrawEventsCleared,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
};
|
||||
// Reset the hint that we've dispatched events.
|
||||
self.with_state(|state| {
|
||||
state.dispatched_events = false;
|
||||
});
|
||||
|
||||
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
|
||||
exit_code
|
||||
// This is always the last event we dispatch before poll again
|
||||
sticky_exit_callback(
|
||||
Event::AboutToWait,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
self.control_flow = control_flow;
|
||||
std::mem::swap(&mut self.compositor_updates, &mut compositor_updates);
|
||||
std::mem::swap(&mut self.buffer_sink, &mut buffer_sink);
|
||||
std::mem::swap(&mut self.window_ids, &mut window_ids);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -486,6 +639,22 @@ impl<T: 'static> EventLoop<T> {
|
||||
error.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn roundtrip(&mut self) -> Result<usize, RootOsError> {
|
||||
let state = match &mut self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
|
||||
#[cfg(feature = "x11")]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
|
||||
let event_queue = wayland_source.queue();
|
||||
event_queue.roundtrip(state).map_err(|error| {
|
||||
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(
|
||||
error
|
||||
))))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
|
||||
@@ -12,7 +12,7 @@ use super::{DeviceId, WindowId};
|
||||
/// to the winit's user.
|
||||
#[derive(Default)]
|
||||
pub struct EventSink {
|
||||
pub window_events: Vec<Event<'static, ()>>,
|
||||
pub window_events: Vec<Event<()>>,
|
||||
}
|
||||
|
||||
impl EventSink {
|
||||
@@ -20,6 +20,12 @@ impl EventSink {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Return `true` if there're pending events.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.window_events.is_empty()
|
||||
}
|
||||
|
||||
/// Add new device event to a queue.
|
||||
#[inline]
|
||||
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
|
||||
@@ -31,7 +37,7 @@ impl EventSink {
|
||||
|
||||
/// Add new window event to a queue.
|
||||
#[inline]
|
||||
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
|
||||
pub fn push_window_event(&mut self, event: WindowEvent, window_id: WindowId) {
|
||||
self.window_events.push(Event::WindowEvent {
|
||||
event,
|
||||
window_id: RootWindowId(window_id),
|
||||
@@ -44,7 +50,7 @@ impl EventSink {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drain(&mut self) -> Drain<'_, Event<'static, ()>> {
|
||||
pub fn drain(&mut self) -> Drain<'_, Event<()>> {
|
||||
self.window_events.drain(..)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
|
||||
//! Winit's Wayland backend.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Proxy;
|
||||
use std::fmt::Display;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use crate::platform_impl::platform::WindowId;
|
||||
use sctk::reexports::client::globals::{BindError, GlobalError};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy};
|
||||
|
||||
pub use crate::platform_impl::platform::{OsError, WindowId};
|
||||
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
pub use output::{MonitorHandle, VideoMode};
|
||||
pub use window::Window;
|
||||
@@ -17,6 +21,46 @@ mod state;
|
||||
mod types;
|
||||
mod window;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WaylandError {
|
||||
/// Error connecting to the socket.
|
||||
Connection(ConnectError),
|
||||
|
||||
/// Error binding the global.
|
||||
Global(GlobalError),
|
||||
|
||||
// Bind error.
|
||||
Bind(BindError),
|
||||
|
||||
/// Error during the dispatching the event queue.
|
||||
Dispatch(DispatchError),
|
||||
|
||||
/// Calloop error.
|
||||
Calloop(calloop::Error),
|
||||
|
||||
/// Wayland
|
||||
Wire(client::backend::WaylandError),
|
||||
}
|
||||
|
||||
impl Display for WaylandError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WaylandError::Connection(error) => error.fmt(f),
|
||||
WaylandError::Global(error) => error.fmt(f),
|
||||
WaylandError::Bind(error) => error.fmt(f),
|
||||
WaylandError::Dispatch(error) => error.fmt(f),
|
||||
WaylandError::Calloop(error) => error.fmt(f),
|
||||
WaylandError::Wire(error) => error.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WaylandError> for OsError {
|
||||
fn from(value: WaylandError) -> Self {
|
||||
Self::WaylandError(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy device id, since Wayland doesn't have device events.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
@@ -151,6 +151,9 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
keyboard_state.repeat_token = keyboard_state
|
||||
.loop_handle
|
||||
.insert_source(timer, move |_, _, state| {
|
||||
// Required to handle the wakeups from the repeat sources.
|
||||
state.dispatched_events = true;
|
||||
|
||||
let data = wl_keyboard.data::<KeyboardData>().unwrap();
|
||||
let seat_state = state.seats.get_mut(&data.seat.id()).unwrap();
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ impl PointerHandler for WinitState {
|
||||
if parent_surface != surface =>
|
||||
{
|
||||
if let Some(icon) =
|
||||
window.frame_point_moved(surface, event.position.0, event.position.1)
|
||||
window.frame_point_moved(seat, surface, event.position.0, event.position.1)
|
||||
{
|
||||
if let Some(pointer) = seat_state.pointer.as_ref() {
|
||||
let surface = pointer
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::cell::RefCell;
|
||||
use std::error::Error;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
@@ -23,6 +23,7 @@ use sctk::shm::{Shm, ShmHandler};
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::platform_impl::OsError;
|
||||
|
||||
use super::event_loop::sink::EventSink;
|
||||
use super::output::MonitorHandle;
|
||||
@@ -34,7 +35,7 @@ use super::types::wp_fractional_scaling::FractionalScalingManager;
|
||||
use super::types::wp_viewporter::ViewporterState;
|
||||
use super::types::xdg_activation::XdgActivationState;
|
||||
use super::window::{WindowRequests, WindowState};
|
||||
use super::WindowId;
|
||||
use super::{WaylandError, WindowId};
|
||||
|
||||
/// Winit's Wayland state.
|
||||
pub struct WinitState {
|
||||
@@ -104,6 +105,10 @@ pub struct WinitState {
|
||||
|
||||
/// Loop handle to re-register event sources, such as keyboard repeat.
|
||||
pub loop_handle: LoopHandle<'static, Self>,
|
||||
|
||||
/// Whether we have dispatched events to the user thus we want to
|
||||
/// send `AboutToWait` and normally wakeup the user.
|
||||
pub dispatched_events: bool,
|
||||
}
|
||||
|
||||
impl WinitState {
|
||||
@@ -111,14 +116,16 @@ impl WinitState {
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<Self>,
|
||||
loop_handle: LoopHandle<'static, WinitState>,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
) -> Result<Self, OsError> {
|
||||
let registry_state = RegistryState::new(globals);
|
||||
let compositor_state = CompositorState::bind(globals, queue_handle)?;
|
||||
let compositor_state =
|
||||
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
|
||||
let subcompositor_state = SubcompositorState::bind(
|
||||
compositor_state.wl_compositor().clone(),
|
||||
globals,
|
||||
queue_handle,
|
||||
)?;
|
||||
)
|
||||
.map_err(WaylandError::Bind)?;
|
||||
|
||||
let output_state = OutputState::new(globals, queue_handle);
|
||||
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
|
||||
@@ -143,9 +150,9 @@ impl WinitState {
|
||||
subcompositor_state: Arc::new(subcompositor_state),
|
||||
output_state,
|
||||
seat_state,
|
||||
shm: Shm::bind(globals, queue_handle)?,
|
||||
shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
|
||||
|
||||
xdg_shell: XdgShell::bind(globals, queue_handle)?,
|
||||
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
|
||||
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
|
||||
|
||||
windows: Default::default(),
|
||||
@@ -167,6 +174,8 @@ impl WinitState {
|
||||
monitors: Arc::new(Mutex::new(monitors)),
|
||||
events_sink: EventSink::new(),
|
||||
loop_handle,
|
||||
// Make it true by default.
|
||||
dispatched_events: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -321,7 +330,27 @@ impl CompositorHandler for WinitState {
|
||||
self.scale_factor_changed(surface, scale_factor as f64, true)
|
||||
}
|
||||
|
||||
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: u32) {}
|
||||
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, surface: &WlSurface, _: u32) {
|
||||
let window_id = super::make_wid(surface);
|
||||
let window = match self.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// In case we have a redraw requested we must indicate the wake up.
|
||||
if self
|
||||
.window_requests
|
||||
.get_mut()
|
||||
.get(&window_id)
|
||||
.unwrap()
|
||||
.redraw_requested
|
||||
.load(Ordering::Relaxed)
|
||||
{
|
||||
self.dispatched_events = true;
|
||||
}
|
||||
|
||||
window.lock().unwrap().frame_callback_received();
|
||||
}
|
||||
}
|
||||
|
||||
impl ProvidesRegistryState for WinitState {
|
||||
|
||||
@@ -16,7 +16,10 @@ use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::
|
||||
|
||||
use sctk::globals::GlobalData;
|
||||
|
||||
use crate::event_loop::AsyncRequestSerial;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
use crate::platform_impl::WindowId;
|
||||
use crate::window::ActivationToken;
|
||||
|
||||
pub struct XdgActivationState {
|
||||
xdg_activation: XdgActivationV1,
|
||||
@@ -62,16 +65,29 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
|
||||
_ => return,
|
||||
};
|
||||
|
||||
state
|
||||
let global = state
|
||||
.xdg_activation
|
||||
.as_ref()
|
||||
.expect("got xdg_activation event without global.")
|
||||
.global()
|
||||
.activate(token, &data.surface);
|
||||
.global();
|
||||
|
||||
// Mark that no request attention is in process.
|
||||
if let Some(attention_requested) = data.attention_requested.upgrade() {
|
||||
attention_requested.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
match data {
|
||||
XdgActivationTokenData::Attention((surface, fence)) => {
|
||||
global.activate(token, surface);
|
||||
// Mark that no request attention is in process.
|
||||
if let Some(attention_requested) = fence.upgrade() {
|
||||
attention_requested.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
XdgActivationTokenData::Obtain((window_id, serial)) => {
|
||||
state.events_sink.push_window_event(
|
||||
crate::event::WindowEvent::ActivationTokenDone {
|
||||
serial: *serial,
|
||||
token: ActivationToken::_new(token),
|
||||
},
|
||||
*window_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
proxy.destroy();
|
||||
@@ -79,24 +95,11 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
|
||||
}
|
||||
|
||||
/// The data associated with the activation request.
|
||||
pub struct XdgActivationTokenData {
|
||||
/// The surface we're raising.
|
||||
surface: WlSurface,
|
||||
|
||||
/// Flag to throttle attention requests.
|
||||
attention_requested: Weak<AtomicBool>,
|
||||
}
|
||||
|
||||
impl XdgActivationTokenData {
|
||||
/// Create a new data.
|
||||
///
|
||||
/// The `attenteion_requested` is marked as `false` on complition.
|
||||
pub fn new(surface: WlSurface, attention_requested: Weak<AtomicBool>) -> Self {
|
||||
Self {
|
||||
surface,
|
||||
attention_requested,
|
||||
}
|
||||
}
|
||||
pub enum XdgActivationTokenData {
|
||||
/// Request user attention for the given surface.
|
||||
Attention((WlSurface, Weak<AtomicBool>)),
|
||||
/// Get a token to be passed outside of the winit.
|
||||
Obtain((WindowId, AsyncRequestSerial)),
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ XdgActivationV1: GlobalData] => XdgActivationState);
|
||||
|
||||
@@ -22,6 +22,7 @@ use sctk::shell::WaylandSurface;
|
||||
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
use crate::event_loop::AsyncRequestSerial;
|
||||
use crate::platform_impl::{
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError,
|
||||
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
|
||||
@@ -35,9 +36,9 @@ use super::event_loop::sink::EventSink;
|
||||
use super::output::MonitorHandle;
|
||||
use super::state::WinitState;
|
||||
use super::types::xdg_activation::XdgActivationTokenData;
|
||||
use super::{EventLoopWindowTarget, WindowId};
|
||||
use super::{EventLoopWindowTarget, WaylandError, WindowId};
|
||||
|
||||
mod state;
|
||||
pub(crate) mod state;
|
||||
|
||||
pub use state::WindowState;
|
||||
|
||||
@@ -168,6 +169,14 @@ impl Window {
|
||||
_ => (),
|
||||
};
|
||||
|
||||
// Activate the window when the token is passed.
|
||||
if let (Some(xdg_activation), Some(token)) = (
|
||||
xdg_activation.as_ref(),
|
||||
platform_attributes.activation_token,
|
||||
) {
|
||||
xdg_activation.activate(token._token, &surface);
|
||||
}
|
||||
|
||||
// XXX Do initial commit.
|
||||
window.commit();
|
||||
|
||||
@@ -196,18 +205,18 @@ impl Window {
|
||||
let event_queue = wayland_source.queue();
|
||||
|
||||
// Do a roundtrip.
|
||||
event_queue.roundtrip(&mut state).map_err(|_| {
|
||||
os_error!(OsError::WaylandMisc(
|
||||
"failed to do initial roundtrip for the window."
|
||||
))
|
||||
event_queue.roundtrip(&mut state).map_err(|error| {
|
||||
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(
|
||||
error
|
||||
))))
|
||||
})?;
|
||||
|
||||
// XXX Wait for the initial configure to arrive.
|
||||
while !window_state.lock().unwrap().is_configured() {
|
||||
event_queue.blocking_dispatch(&mut state).map_err(|_| {
|
||||
os_error!(OsError::WaylandMisc(
|
||||
"failed to dispatch queue while waiting for initial configure."
|
||||
))
|
||||
event_queue.blocking_dispatch(&mut state).map_err(|error| {
|
||||
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(
|
||||
error
|
||||
))))
|
||||
})?;
|
||||
}
|
||||
|
||||
@@ -278,10 +287,23 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn request_redraw(&self) {
|
||||
self.window_requests
|
||||
// NOTE: try to not wake up the loop when the event was already scheduled and not yet
|
||||
// processed by the loop, because if at this point the value was `true` it could only
|
||||
// mean that the loop still haven't dispatched the value to the client and will do
|
||||
// eventually, resetting it to `false`.
|
||||
if self
|
||||
.window_requests
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
self.event_loop_awakener.ping();
|
||||
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pre_present_notify(&self) {
|
||||
self.window_state.lock().unwrap().request_frame_callback();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -292,13 +314,14 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
// TODO should we issue the resize event? I don't think other platforms do so.
|
||||
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state.resize(size.to_logical::<u32>(scale_factor));
|
||||
|
||||
self.request_redraw();
|
||||
|
||||
Some(window_state.inner_size().to_physical(scale_factor))
|
||||
}
|
||||
|
||||
/// Set the minimum inner size for the window.
|
||||
@@ -495,13 +518,31 @@ impl Window {
|
||||
|
||||
self.attention_requested.store(true, Ordering::Relaxed);
|
||||
let surface = self.surface().clone();
|
||||
let data =
|
||||
XdgActivationTokenData::new(surface.clone(), Arc::downgrade(&self.attention_requested));
|
||||
let data = XdgActivationTokenData::Attention((
|
||||
surface.clone(),
|
||||
Arc::downgrade(&self.attention_requested),
|
||||
));
|
||||
let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
|
||||
xdg_activation_token.set_surface(&surface);
|
||||
xdg_activation_token.commit();
|
||||
}
|
||||
|
||||
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
|
||||
let xdg_activation = match self.xdg_activation.as_ref() {
|
||||
Some(xdg_activation) => xdg_activation,
|
||||
None => return Err(NotSupportedError::new()),
|
||||
};
|
||||
|
||||
let serial = AsyncRequestSerial::get();
|
||||
|
||||
let data = XdgActivationTokenData::Obtain((self.window_id, serial));
|
||||
let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
|
||||
xdg_activation_token.set_surface(self.surface());
|
||||
xdg_activation_token.commit();
|
||||
|
||||
Ok(serial)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
self.window_state.lock().unwrap().set_cursor_grab(mode)
|
||||
@@ -533,9 +574,7 @@ impl Window {
|
||||
Ok(())
|
||||
} else {
|
||||
let region = Region::new(&*self.compositor).map_err(|_| {
|
||||
ExternalError::Os(os_error!(OsError::WaylandMisc(
|
||||
"failed to set input region."
|
||||
)))
|
||||
ExternalError::Os(os_error!(OsError::Misc("failed to set input region.")))
|
||||
})?;
|
||||
region.add(0, 0, 0, 0);
|
||||
surface.set_input_region(Some(region.wl_region()));
|
||||
@@ -573,11 +612,6 @@ impl Window {
|
||||
self.window_state.lock().unwrap().set_ime_purpose(purpose);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn display(&self) -> &WlDisplay {
|
||||
&self.display
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn surface(&self) -> &WlSurface {
|
||||
self.window.wl_surface()
|
||||
|
||||
@@ -125,30 +125,74 @@ pub struct WindowState {
|
||||
/// sends `None` for the new size in the configure.
|
||||
stateless_size: LogicalSize<u32>,
|
||||
|
||||
/// The state of the frame callback.
|
||||
frame_callback_state: FrameCallbackState,
|
||||
|
||||
viewport: Option<WpViewport>,
|
||||
fractional_scale: Option<WpFractionalScaleV1>,
|
||||
}
|
||||
|
||||
/// The state of the cursor grabs.
|
||||
#[derive(Clone, Copy)]
|
||||
struct GrabState {
|
||||
/// The grab mode requested by the user.
|
||||
user_grab_mode: CursorGrabMode,
|
||||
|
||||
/// The current grab mode.
|
||||
current_grab_mode: CursorGrabMode,
|
||||
}
|
||||
|
||||
impl GrabState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
user_grab_mode: CursorGrabMode::None,
|
||||
current_grab_mode: CursorGrabMode::None,
|
||||
}
|
||||
}
|
||||
/// Whether the client side decorations have pending move operations.
|
||||
///
|
||||
/// The value is the serial of the event triggered moved.
|
||||
has_pending_move: Option<u32>,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
/// Create new window state.
|
||||
pub fn new(
|
||||
connection: Connection,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
winit_state: &WinitState,
|
||||
size: LogicalSize<u32>,
|
||||
window: Window,
|
||||
theme: Option<Theme>,
|
||||
) -> Self {
|
||||
let compositor = winit_state.compositor_state.clone();
|
||||
let pointer_constraints = winit_state.pointer_constraints.clone();
|
||||
let viewport = winit_state
|
||||
.viewporter_state
|
||||
.as_ref()
|
||||
.map(|state| state.get_viewport(window.wl_surface(), queue_handle));
|
||||
let fractional_scale = winit_state
|
||||
.fractional_scaling_manager
|
||||
.as_ref()
|
||||
.map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
|
||||
|
||||
Self {
|
||||
compositor,
|
||||
connection,
|
||||
csd_fails: false,
|
||||
cursor_grab_mode: GrabState::new(),
|
||||
cursor_icon: CursorIcon::Default,
|
||||
cursor_visible: true,
|
||||
decorate: true,
|
||||
fractional_scale,
|
||||
frame: None,
|
||||
frame_callback_state: FrameCallbackState::None,
|
||||
has_focus: false,
|
||||
has_pending_move: None,
|
||||
ime_allowed: false,
|
||||
ime_purpose: ImePurpose::Normal,
|
||||
last_configure: None,
|
||||
max_inner_size: None,
|
||||
min_inner_size: MIN_WINDOW_SIZE,
|
||||
pointer_constraints,
|
||||
pointers: Default::default(),
|
||||
queue_handle: queue_handle.clone(),
|
||||
resizable: true,
|
||||
scale_factor: 1.,
|
||||
shm: winit_state.shm.wl_shm().clone(),
|
||||
size,
|
||||
stateless_size: size,
|
||||
text_inputs: Vec::new(),
|
||||
theme,
|
||||
title: String::default(),
|
||||
transparent: false,
|
||||
viewport,
|
||||
window: ManuallyDrop::new(window),
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply closure on the given pointer.
|
||||
fn apply_on_poiner<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
|
||||
&self,
|
||||
@@ -163,6 +207,33 @@ impl WindowState {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the current state of the frame callback.
|
||||
pub fn frame_callback_state(&self) -> FrameCallbackState {
|
||||
self.frame_callback_state
|
||||
}
|
||||
|
||||
/// The frame callback was received, but not yet sent to the user.
|
||||
pub fn frame_callback_received(&mut self) {
|
||||
self.frame_callback_state = FrameCallbackState::Received;
|
||||
}
|
||||
|
||||
/// Reset the frame callbacks state.
|
||||
pub fn frame_callback_reset(&mut self) {
|
||||
self.frame_callback_state = FrameCallbackState::None;
|
||||
}
|
||||
|
||||
/// Request a frame callback if we don't have one for this window in flight.
|
||||
pub fn request_frame_callback(&mut self) {
|
||||
let surface = self.window.wl_surface();
|
||||
match self.frame_callback_state {
|
||||
FrameCallbackState::None | FrameCallbackState::Received => {
|
||||
self.frame_callback_state = FrameCallbackState::Requested;
|
||||
surface.frame(&self.queue_handle, surface.clone());
|
||||
}
|
||||
FrameCallbackState::Requested => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure(
|
||||
&mut self,
|
||||
configure: WindowConfigure,
|
||||
@@ -279,7 +350,7 @@ impl WindowState {
|
||||
FrameAction::Maximize => self.window.set_maximized(),
|
||||
FrameAction::UnMaximize => self.window.unset_maximized(),
|
||||
FrameAction::Close => WinitState::queue_close(updates, window_id),
|
||||
FrameAction::Move => self.window.move_(seat, serial),
|
||||
FrameAction::Move => self.has_pending_move = Some(serial),
|
||||
FrameAction::Resize(edge) => self.window.resize(seat, serial, edge),
|
||||
FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
|
||||
};
|
||||
@@ -294,9 +365,26 @@ impl WindowState {
|
||||
}
|
||||
|
||||
// Move the point over decorations.
|
||||
pub fn frame_point_moved(&mut self, surface: &WlSurface, x: f64, y: f64) -> Option<&str> {
|
||||
pub fn frame_point_moved(
|
||||
&mut self,
|
||||
seat: &WlSeat,
|
||||
surface: &WlSurface,
|
||||
x: f64,
|
||||
y: f64,
|
||||
) -> Option<&str> {
|
||||
// Take the serial if we had any, so it doesn't stick around.
|
||||
let serial = self.has_pending_move.take();
|
||||
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
frame.click_point_moved(surface, x, y)
|
||||
let cursor = frame.click_point_moved(surface, x, y);
|
||||
// If we have a cursor change, that means that cursor is over the decorations,
|
||||
// so try to apply move.
|
||||
if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
|
||||
self.window.move_(seat, serial);
|
||||
None
|
||||
} else {
|
||||
cursor
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -362,66 +450,13 @@ impl WindowState {
|
||||
.map(|configure| configure.decoration_mode == DecorationMode::Client)
|
||||
.unwrap_or(false);
|
||||
if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
|
||||
frame.is_hidden()
|
||||
!frame.is_hidden()
|
||||
} else {
|
||||
// Server side decorations.
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new window state.
|
||||
pub fn new(
|
||||
connection: Connection,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
winit_state: &WinitState,
|
||||
size: LogicalSize<u32>,
|
||||
window: Window,
|
||||
theme: Option<Theme>,
|
||||
) -> Self {
|
||||
let compositor = winit_state.compositor_state.clone();
|
||||
let pointer_constraints = winit_state.pointer_constraints.clone();
|
||||
let viewport = winit_state
|
||||
.viewporter_state
|
||||
.as_ref()
|
||||
.map(|state| state.get_viewport(window.wl_surface(), queue_handle));
|
||||
let fractional_scale = winit_state
|
||||
.fractional_scaling_manager
|
||||
.as_ref()
|
||||
.map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
|
||||
|
||||
Self {
|
||||
compositor,
|
||||
connection,
|
||||
theme,
|
||||
csd_fails: false,
|
||||
decorate: true,
|
||||
cursor_grab_mode: GrabState::new(),
|
||||
cursor_icon: CursorIcon::Default,
|
||||
cursor_visible: true,
|
||||
fractional_scale,
|
||||
frame: None,
|
||||
has_focus: false,
|
||||
ime_allowed: false,
|
||||
ime_purpose: ImePurpose::Normal,
|
||||
last_configure: None,
|
||||
max_inner_size: None,
|
||||
min_inner_size: MIN_WINDOW_SIZE,
|
||||
pointer_constraints,
|
||||
pointers: Default::default(),
|
||||
queue_handle: queue_handle.clone(),
|
||||
scale_factor: 1.,
|
||||
shm: winit_state.shm.wl_shm().clone(),
|
||||
size,
|
||||
stateless_size: size,
|
||||
text_inputs: Vec::new(),
|
||||
title: String::default(),
|
||||
transparent: false,
|
||||
resizable: true,
|
||||
viewport,
|
||||
window: ManuallyDrop::new(window),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the outer size of the window.
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> LogicalSize<u32> {
|
||||
@@ -677,7 +712,7 @@ impl WindowState {
|
||||
// Positon can be set only for locked cursor.
|
||||
if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
|
||||
return Err(ExternalError::Os(os_error!(
|
||||
crate::platform_impl::OsError::WaylandMisc(
|
||||
crate::platform_impl::OsError::Misc(
|
||||
"cursor position can be set only for locked cursor."
|
||||
)
|
||||
)));
|
||||
@@ -869,6 +904,37 @@ impl Drop for WindowState {
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of the cursor grabs.
|
||||
#[derive(Clone, Copy)]
|
||||
struct GrabState {
|
||||
/// The grab mode requested by the user.
|
||||
user_grab_mode: CursorGrabMode,
|
||||
|
||||
/// The current grab mode.
|
||||
current_grab_mode: CursorGrabMode,
|
||||
}
|
||||
|
||||
impl GrabState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
user_grab_mode: CursorGrabMode::None,
|
||||
current_grab_mode: CursorGrabMode::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of the frame callback.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FrameCallbackState {
|
||||
/// No frame callback was requsted.
|
||||
#[default]
|
||||
None,
|
||||
/// The frame callback was requested, but not yet arrived, the redraw events are throttled.
|
||||
Requested,
|
||||
/// The callback was marked as done, and user could receive redraw requested
|
||||
Received,
|
||||
}
|
||||
|
||||
impl From<ResizeDirection> for ResizeEdge {
|
||||
fn from(value: ResizeDirection) -> Self {
|
||||
match value {
|
||||
|
||||
200
src/platform_impl/linux/x11/activation.rs
Normal file
200
src/platform_impl/linux/x11/activation.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! X11 activation handling.
|
||||
//!
|
||||
//! X11 has a "startup notification" specification similar to Wayland's, see this URL:
|
||||
//! <https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt>
|
||||
|
||||
use super::{atoms::*, VoidCookie, X11Error, XConnection};
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::fmt::Write;
|
||||
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt as _};
|
||||
|
||||
impl XConnection {
|
||||
/// "Request" a new activation token from the server.
|
||||
pub(crate) fn request_activation_token(&self, window_title: &str) -> Result<String, X11Error> {
|
||||
// The specification recommends the format "hostname+pid+"_TIME"+current time"
|
||||
let uname = rustix::system::uname();
|
||||
let pid = rustix::process::getpid();
|
||||
let time = self.timestamp();
|
||||
|
||||
let activation_token = format!(
|
||||
"{}{}_TIME{}",
|
||||
uname.nodename().to_str().unwrap_or("winit"),
|
||||
pid.as_raw_nonzero(),
|
||||
time
|
||||
);
|
||||
|
||||
// Set up the new startup notification.
|
||||
let notification = {
|
||||
let mut buffer = Vec::new();
|
||||
buffer.extend_from_slice(b"new: ID=");
|
||||
quote_string(&activation_token, &mut buffer);
|
||||
buffer.extend_from_slice(b" NAME=");
|
||||
quote_string(window_title, &mut buffer);
|
||||
buffer.extend_from_slice(b" SCREEN=");
|
||||
push_display(&mut buffer, &self.default_screen_index());
|
||||
|
||||
CString::new(buffer)
|
||||
.map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))?
|
||||
.into_bytes_with_nul()
|
||||
};
|
||||
self.send_message(¬ification)?;
|
||||
|
||||
Ok(activation_token)
|
||||
}
|
||||
|
||||
/// Finish launching a window with the given startup ID.
|
||||
pub(crate) fn remove_activation_token(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
startup_id: &str,
|
||||
) -> Result<(), X11Error> {
|
||||
let atoms = self.atoms();
|
||||
|
||||
// Set the _NET_STARTUP_ID property on the window.
|
||||
self.xcb_connection()
|
||||
.change_property(
|
||||
xproto::PropMode::REPLACE,
|
||||
window,
|
||||
atoms[_NET_STARTUP_ID],
|
||||
xproto::AtomEnum::STRING,
|
||||
8,
|
||||
startup_id.len().try_into().unwrap(),
|
||||
startup_id.as_bytes(),
|
||||
)?
|
||||
.check()?;
|
||||
|
||||
// Send the message indicating that the startup is over.
|
||||
let message = {
|
||||
const MESSAGE_ROOT: &str = "remove: ID=";
|
||||
|
||||
let mut buffer = Vec::with_capacity(
|
||||
MESSAGE_ROOT
|
||||
.len()
|
||||
.checked_add(startup_id.len())
|
||||
.and_then(|x| x.checked_add(1))
|
||||
.unwrap(),
|
||||
);
|
||||
buffer.extend_from_slice(MESSAGE_ROOT.as_bytes());
|
||||
quote_string(startup_id, &mut buffer);
|
||||
CString::new(buffer)
|
||||
.map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))?
|
||||
.into_bytes_with_nul()
|
||||
};
|
||||
|
||||
self.send_message(&message)
|
||||
}
|
||||
|
||||
/// Send a startup notification message to the window manager.
|
||||
fn send_message(&self, message: &[u8]) -> Result<(), X11Error> {
|
||||
let atoms = self.atoms();
|
||||
|
||||
// Create a new window to send the message over.
|
||||
let screen = self.default_root();
|
||||
let window = xproto::WindowWrapper::create_window(
|
||||
self.xcb_connection(),
|
||||
screen.root_depth,
|
||||
screen.root,
|
||||
-100,
|
||||
-100,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
xproto::WindowClass::INPUT_OUTPUT,
|
||||
screen.root_visual,
|
||||
&xproto::CreateWindowAux::new()
|
||||
.override_redirect(1)
|
||||
.event_mask(
|
||||
xproto::EventMask::STRUCTURE_NOTIFY | xproto::EventMask::PROPERTY_CHANGE,
|
||||
),
|
||||
)?;
|
||||
|
||||
// Serialize the messages in 20-byte chunks.
|
||||
let mut message_type = atoms[_NET_STARTUP_INFO_BEGIN];
|
||||
message
|
||||
.chunks(20)
|
||||
.map(|chunk| {
|
||||
let mut buffer = [0u8; 20];
|
||||
buffer[..chunk.len()].copy_from_slice(chunk);
|
||||
let event =
|
||||
xproto::ClientMessageEvent::new(8, window.window(), message_type, buffer);
|
||||
|
||||
// Set the message type to the continuation atom for the next chunk.
|
||||
message_type = atoms[_NET_STARTUP_INFO];
|
||||
|
||||
event
|
||||
})
|
||||
.try_for_each(|event| {
|
||||
// Send each event in order.
|
||||
self.xcb_connection()
|
||||
.send_event(
|
||||
false,
|
||||
screen.root,
|
||||
xproto::EventMask::PROPERTY_CHANGE,
|
||||
event,
|
||||
)
|
||||
.map(VoidCookie::ignore_error)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Quote a literal string as per the startup notification specification.
|
||||
fn quote_string(s: &str, target: &mut Vec<u8>) {
|
||||
let total_len = s.len().checked_add(3).expect("quote string overflow");
|
||||
target.reserve(total_len);
|
||||
|
||||
// Add the opening quote.
|
||||
target.push(b'"');
|
||||
|
||||
// Iterate over the string split by literal quotes.
|
||||
s.as_bytes().split(|&b| b == b'"').for_each(|part| {
|
||||
// Add the part.
|
||||
target.extend_from_slice(part);
|
||||
|
||||
// Escape the quote.
|
||||
target.push(b'\\');
|
||||
target.push(b'"');
|
||||
});
|
||||
|
||||
// Un-escape the last quote.
|
||||
target.remove(target.len() - 2);
|
||||
}
|
||||
|
||||
/// Push a `Display` implementation to the buffer.
|
||||
fn push_display(buffer: &mut Vec<u8>, display: &impl std::fmt::Display) {
|
||||
struct Writer<'a> {
|
||||
buffer: &'a mut Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Write for Writer<'a> {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
self.buffer.extend_from_slice(s.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
write!(Writer { buffer }, "{}", display).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn properly_escapes_x11_messages() {
|
||||
let assert_eq = |input: &str, output: &[u8]| {
|
||||
let mut buf = vec![];
|
||||
quote_string(input, &mut buf);
|
||||
assert_eq!(buf, output);
|
||||
};
|
||||
|
||||
assert_eq("", b"\"\"");
|
||||
assert_eq("foo", b"\"foo\"");
|
||||
assert_eq("foo\"bar", b"\"foo\\\"bar\"");
|
||||
}
|
||||
}
|
||||
116
src/platform_impl/linux/x11/atoms.rs
Normal file
116
src/platform_impl/linux/x11/atoms.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
//! Collects every atom used by the platform implementation.
|
||||
|
||||
use core::ops::Index;
|
||||
|
||||
macro_rules! atom_manager {
|
||||
($($name:ident $(:$lit:literal)?),*) => {
|
||||
x11rb::atom_manager! {
|
||||
/// The atoms used by `winit`
|
||||
pub(crate) Atoms: AtomsCookie {
|
||||
$($name $(:$lit)?,)*
|
||||
}
|
||||
}
|
||||
|
||||
/// Indices into the `Atoms` struct.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub(crate) enum AtomName {
|
||||
$($name,)*
|
||||
}
|
||||
|
||||
impl AtomName {
|
||||
pub(crate) fn atom_from(
|
||||
self,
|
||||
atoms: &Atoms
|
||||
) -> &x11rb::protocol::xproto::Atom {
|
||||
match self {
|
||||
$(AtomName::$name => &atoms.$name,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
atom_manager! {
|
||||
// General Use Atoms
|
||||
CARD32,
|
||||
UTF8_STRING,
|
||||
WM_CHANGE_STATE,
|
||||
WM_CLIENT_MACHINE,
|
||||
WM_DELETE_WINDOW,
|
||||
WM_PROTOCOLS,
|
||||
WM_STATE,
|
||||
XIM_SERVERS,
|
||||
|
||||
// Assorted ICCCM Atoms
|
||||
_NET_WM_ICON,
|
||||
_NET_WM_MOVERESIZE,
|
||||
_NET_WM_NAME,
|
||||
_NET_WM_PID,
|
||||
_NET_WM_PING,
|
||||
_NET_WM_STATE,
|
||||
_NET_WM_STATE_ABOVE,
|
||||
_NET_WM_STATE_BELOW,
|
||||
_NET_WM_STATE_FULLSCREEN,
|
||||
_NET_WM_STATE_HIDDEN,
|
||||
_NET_WM_STATE_MAXIMIZED_HORZ,
|
||||
_NET_WM_STATE_MAXIMIZED_VERT,
|
||||
_NET_WM_WINDOW_TYPE,
|
||||
|
||||
// Activation atoms.
|
||||
_NET_STARTUP_INFO_BEGIN,
|
||||
_NET_STARTUP_INFO,
|
||||
_NET_STARTUP_ID,
|
||||
|
||||
// WM window types.
|
||||
_NET_WM_WINDOW_TYPE_DESKTOP,
|
||||
_NET_WM_WINDOW_TYPE_DOCK,
|
||||
_NET_WM_WINDOW_TYPE_TOOLBAR,
|
||||
_NET_WM_WINDOW_TYPE_MENU,
|
||||
_NET_WM_WINDOW_TYPE_UTILITY,
|
||||
_NET_WM_WINDOW_TYPE_SPLASH,
|
||||
_NET_WM_WINDOW_TYPE_DIALOG,
|
||||
_NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
|
||||
_NET_WM_WINDOW_TYPE_POPUP_MENU,
|
||||
_NET_WM_WINDOW_TYPE_TOOLTIP,
|
||||
_NET_WM_WINDOW_TYPE_NOTIFICATION,
|
||||
_NET_WM_WINDOW_TYPE_COMBO,
|
||||
_NET_WM_WINDOW_TYPE_DND,
|
||||
_NET_WM_WINDOW_TYPE_NORMAL,
|
||||
|
||||
// Drag-N-Drop Atoms
|
||||
XdndAware,
|
||||
XdndEnter,
|
||||
XdndLeave,
|
||||
XdndDrop,
|
||||
XdndPosition,
|
||||
XdndStatus,
|
||||
XdndActionPrivate,
|
||||
XdndSelection,
|
||||
XdndFinished,
|
||||
XdndTypeList,
|
||||
TextUriList: b"text/uri-list",
|
||||
None: b"None",
|
||||
|
||||
// Miscellaneous Atoms
|
||||
_GTK_THEME_VARIANT,
|
||||
_MOTIF_WM_HINTS,
|
||||
_NET_ACTIVE_WINDOW,
|
||||
_NET_CLIENT_LIST,
|
||||
_NET_FRAME_EXTENTS,
|
||||
_NET_SUPPORTED,
|
||||
_NET_SUPPORTING_WM_CHECK,
|
||||
_XEMBED
|
||||
}
|
||||
|
||||
impl Index<AtomName> for Atoms {
|
||||
type Output = x11rb::protocol::xproto::Atom;
|
||||
|
||||
fn index(&self, index: AtomName) -> &Self::Output {
|
||||
index.atom_from(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use AtomName::*;
|
||||
// Make sure `None` is still defined.
|
||||
pub(crate) use core::option::Option::None;
|
||||
@@ -7,55 +7,12 @@ use std::{
|
||||
};
|
||||
|
||||
use percent_encoding::percent_decode;
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt};
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DndAtoms {
|
||||
pub enter: ffi::Atom,
|
||||
pub leave: ffi::Atom,
|
||||
pub drop: ffi::Atom,
|
||||
pub position: ffi::Atom,
|
||||
pub status: ffi::Atom,
|
||||
pub action_private: ffi::Atom,
|
||||
pub selection: ffi::Atom,
|
||||
pub finished: ffi::Atom,
|
||||
pub type_list: ffi::Atom,
|
||||
pub uri_list: ffi::Atom,
|
||||
pub none: ffi::Atom,
|
||||
}
|
||||
|
||||
impl DndAtoms {
|
||||
pub fn new(xconn: &Arc<XConnection>) -> Result<Self, XError> {
|
||||
let names = [
|
||||
b"XdndEnter\0".as_ptr() as *mut c_char,
|
||||
b"XdndLeave\0".as_ptr() as *mut c_char,
|
||||
b"XdndDrop\0".as_ptr() as *mut c_char,
|
||||
b"XdndPosition\0".as_ptr() as *mut c_char,
|
||||
b"XdndStatus\0".as_ptr() as *mut c_char,
|
||||
b"XdndActionPrivate\0".as_ptr() as *mut c_char,
|
||||
b"XdndSelection\0".as_ptr() as *mut c_char,
|
||||
b"XdndFinished\0".as_ptr() as *mut c_char,
|
||||
b"XdndTypeList\0".as_ptr() as *mut c_char,
|
||||
b"text/uri-list\0".as_ptr() as *mut c_char,
|
||||
b"None\0".as_ptr() as *mut c_char,
|
||||
];
|
||||
let atoms = unsafe { xconn.get_atoms(&names) }?;
|
||||
Ok(DndAtoms {
|
||||
enter: atoms[0],
|
||||
leave: atoms[1],
|
||||
drop: atoms[2],
|
||||
position: atoms[3],
|
||||
status: atoms[4],
|
||||
action_private: atoms[5],
|
||||
selection: atoms[6],
|
||||
finished: atoms[7],
|
||||
type_list: atoms[8],
|
||||
uri_list: atoms[9],
|
||||
none: atoms[10],
|
||||
})
|
||||
}
|
||||
}
|
||||
use super::{
|
||||
atoms::{AtomName::None as DndNone, *},
|
||||
util, CookieResultExt, X11Error, XConnection,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum DndState {
|
||||
@@ -86,22 +43,19 @@ impl From<io::Error> for DndDataParseError {
|
||||
|
||||
pub(crate) struct Dnd {
|
||||
xconn: Arc<XConnection>,
|
||||
pub atoms: DndAtoms,
|
||||
// Populated by XdndEnter event handler
|
||||
pub version: Option<c_long>,
|
||||
pub type_list: Option<Vec<c_ulong>>,
|
||||
pub type_list: Option<Vec<xproto::Atom>>,
|
||||
// Populated by XdndPosition event handler
|
||||
pub source_window: Option<c_ulong>,
|
||||
pub source_window: Option<xproto::Window>,
|
||||
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
|
||||
pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>,
|
||||
}
|
||||
|
||||
impl Dnd {
|
||||
pub fn new(xconn: Arc<XConnection>) -> Result<Self, XError> {
|
||||
let atoms = DndAtoms::new(&xconn)?;
|
||||
pub fn new(xconn: Arc<XConnection>) -> Result<Self, X11Error> {
|
||||
Ok(Dnd {
|
||||
xconn,
|
||||
atoms,
|
||||
version: None,
|
||||
type_list: None,
|
||||
source_window: None,
|
||||
@@ -118,71 +72,85 @@ impl Dnd {
|
||||
|
||||
pub unsafe fn send_status(
|
||||
&self,
|
||||
this_window: c_ulong,
|
||||
target_window: c_ulong,
|
||||
this_window: xproto::Window,
|
||||
target_window: xproto::Window,
|
||||
state: DndState,
|
||||
) -> Result<(), XError> {
|
||||
) -> Result<(), X11Error> {
|
||||
let atoms = self.xconn.atoms();
|
||||
let (accepted, action) = match state {
|
||||
DndState::Accepted => (1, self.atoms.action_private as c_long),
|
||||
DndState::Rejected => (0, self.atoms.none as c_long),
|
||||
DndState::Accepted => (1, atoms[XdndActionPrivate]),
|
||||
DndState::Rejected => (0, atoms[DndNone]),
|
||||
};
|
||||
self.xconn
|
||||
.send_client_msg(
|
||||
target_window,
|
||||
target_window,
|
||||
self.atoms.status,
|
||||
atoms[XdndStatus] as _,
|
||||
None,
|
||||
[this_window as c_long, accepted, 0, 0, action],
|
||||
)
|
||||
.flush()
|
||||
[this_window, accepted, 0, 0, action as _],
|
||||
)?
|
||||
.ignore_error();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe fn send_finished(
|
||||
&self,
|
||||
this_window: c_ulong,
|
||||
target_window: c_ulong,
|
||||
this_window: xproto::Window,
|
||||
target_window: xproto::Window,
|
||||
state: DndState,
|
||||
) -> Result<(), XError> {
|
||||
) -> Result<(), X11Error> {
|
||||
let atoms = self.xconn.atoms();
|
||||
let (accepted, action) = match state {
|
||||
DndState::Accepted => (1, self.atoms.action_private as c_long),
|
||||
DndState::Rejected => (0, self.atoms.none as c_long),
|
||||
DndState::Accepted => (1, atoms[XdndActionPrivate]),
|
||||
DndState::Rejected => (0, atoms[DndNone]),
|
||||
};
|
||||
self.xconn
|
||||
.send_client_msg(
|
||||
target_window,
|
||||
target_window,
|
||||
self.atoms.finished,
|
||||
atoms[XdndFinished] as _,
|
||||
None,
|
||||
[this_window as c_long, accepted, action, 0, 0],
|
||||
)
|
||||
.flush()
|
||||
[this_window, accepted, action as _, 0, 0],
|
||||
)?
|
||||
.ignore_error();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe fn get_type_list(
|
||||
&self,
|
||||
source_window: c_ulong,
|
||||
) -> Result<Vec<ffi::Atom>, util::GetPropertyError> {
|
||||
self.xconn
|
||||
.get_property(source_window, self.atoms.type_list, ffi::XA_ATOM)
|
||||
source_window: xproto::Window,
|
||||
) -> Result<Vec<xproto::Atom>, util::GetPropertyError> {
|
||||
let atoms = self.xconn.atoms();
|
||||
self.xconn.get_property(
|
||||
source_window,
|
||||
atoms[XdndTypeList],
|
||||
xproto::Atom::from(xproto::AtomEnum::ATOM),
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn convert_selection(&self, window: c_ulong, time: c_ulong) {
|
||||
(self.xconn.xlib.XConvertSelection)(
|
||||
self.xconn.display,
|
||||
self.atoms.selection,
|
||||
self.atoms.uri_list,
|
||||
self.atoms.selection,
|
||||
window,
|
||||
time,
|
||||
);
|
||||
pub unsafe fn convert_selection(&self, window: xproto::Window, time: xproto::Timestamp) {
|
||||
let atoms = self.xconn.atoms();
|
||||
self.xconn
|
||||
.xcb_connection()
|
||||
.convert_selection(
|
||||
window,
|
||||
atoms[XdndSelection],
|
||||
atoms[TextUriList],
|
||||
atoms[XdndSelection],
|
||||
time,
|
||||
)
|
||||
.expect_then_ignore_error("Failed to send XdndSelection event")
|
||||
}
|
||||
|
||||
pub unsafe fn read_data(
|
||||
&self,
|
||||
window: c_ulong,
|
||||
window: xproto::Window,
|
||||
) -> Result<Vec<c_uchar>, util::GetPropertyError> {
|
||||
let atoms = self.xconn.atoms();
|
||||
self.xconn
|
||||
.get_property(window, self.atoms.selection, self.atoms.uri_list)
|
||||
.get_property(window, atoms[XdndSelection], atoms[TextUriList])
|
||||
}
|
||||
|
||||
pub fn parse_data(&self, data: &mut [c_uchar]) -> Result<Vec<PathBuf>, DndDataParseError> {
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc};
|
||||
|
||||
use libc::{c_char, c_int, c_long, c_ulong};
|
||||
|
||||
use super::{
|
||||
ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd, DndState,
|
||||
GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, XExtension,
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
os::raw::{c_char, c_int, c_long, c_ulong},
|
||||
rc::Rc,
|
||||
slice,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt as _};
|
||||
use x11rb::x11_utils::Serialize;
|
||||
|
||||
use super::{
|
||||
atoms::*, ffi, get_xtarget, mkdid, mkwid, monitor, util, CookieResultExt, Device, DeviceId,
|
||||
DeviceInfo, Dnd, DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow,
|
||||
WindowId, XExtension,
|
||||
};
|
||||
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest};
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
event::{DeviceEvent, ElementState, Event, Ime, RawKeyEvent, TouchPhase, WindowEvent},
|
||||
@@ -15,6 +23,10 @@ use crate::{
|
||||
keyboard::ModifiersState,
|
||||
platform_impl::platform::common::{keymap, xkb_state::KbdState},
|
||||
};
|
||||
use crate::{
|
||||
event::InnerSizeWriter,
|
||||
platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest},
|
||||
};
|
||||
|
||||
/// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`".
|
||||
const KEYCODE_OFFSET: u8 = 8;
|
||||
@@ -31,11 +43,14 @@ pub(super) struct EventProcessor<T: 'static> {
|
||||
pub(super) kb_state: KbdState,
|
||||
// Number of touch events currently in progress
|
||||
pub(super) num_touch: u32,
|
||||
// Whether we've got a key release for the key press.
|
||||
pub(super) got_key_release: bool,
|
||||
// This is the last pressed key that is repeatable (if it hasn't been
|
||||
// released).
|
||||
//
|
||||
// Used to detect key repeats.
|
||||
pub(super) held_key_press: Option<u32>,
|
||||
pub(super) first_touch: Option<u64>,
|
||||
// Currently focused window belonging to this process
|
||||
pub(super) active_window: Option<ffi::Window>,
|
||||
pub(super) active_window: Option<xproto::Window>,
|
||||
pub(super) is_composing: bool,
|
||||
}
|
||||
|
||||
@@ -50,7 +65,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn with_window<F, Ret>(&self, window_id: ffi::Window, callback: F) -> Option<Ret>
|
||||
pub(crate) fn with_window<F, Ret>(&self, window_id: xproto::Window, callback: F) -> Option<Ret>
|
||||
where
|
||||
F: Fn(&Arc<UnownedWindow>) -> Ret,
|
||||
{
|
||||
@@ -74,7 +89,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
result
|
||||
}
|
||||
|
||||
fn window_exists(&self, window_id: ffi::Window) -> bool {
|
||||
fn window_exists(&self, window_id: xproto::Window) -> bool {
|
||||
self.with_window(window_id, |_| ()).is_some()
|
||||
}
|
||||
|
||||
@@ -114,9 +129,10 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
pub(super) fn process_event<F>(&mut self, xev: &mut ffi::XEvent, mut callback: F)
|
||||
where
|
||||
F: FnMut(Event<'_, T>),
|
||||
F: FnMut(Event<T>),
|
||||
{
|
||||
let wt = get_xtarget(&self.target);
|
||||
let atoms = wt.x_connection().atoms();
|
||||
// XFilterEvent tells us when an event has been discarded by the input method.
|
||||
// Specifically, this involves all of the KeyPress events in compose/pre-edit sequences,
|
||||
// along with an extra copy of the KeyRelease events. This also prevents backspace and
|
||||
@@ -137,42 +153,57 @@ impl<T: 'static> EventProcessor<T> {
|
||||
ffi::ClientMessage => {
|
||||
let client_msg: &ffi::XClientMessageEvent = xev.as_ref();
|
||||
|
||||
let window = client_msg.window;
|
||||
let window = client_msg.window as xproto::Window;
|
||||
let window_id = mkwid(window);
|
||||
|
||||
if client_msg.data.get_long(0) as ffi::Atom == wt.wm_delete_window {
|
||||
if client_msg.data.get_long(0) as xproto::Atom == wt.wm_delete_window {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::CloseRequested,
|
||||
});
|
||||
} else if client_msg.data.get_long(0) as ffi::Atom == wt.net_wm_ping {
|
||||
} else if client_msg.data.get_long(0) as xproto::Atom == wt.net_wm_ping {
|
||||
let response_msg: &mut ffi::XClientMessageEvent = xev.as_mut();
|
||||
response_msg.window = wt.root;
|
||||
let client_msg = xproto::ClientMessageEvent {
|
||||
response_type: xproto::CLIENT_MESSAGE_EVENT,
|
||||
format: response_msg.format as _,
|
||||
sequence: response_msg.serial as _,
|
||||
window: wt.root,
|
||||
type_: response_msg.message_type as _,
|
||||
data: xproto::ClientMessageData::from({
|
||||
let [a, b, c, d, e]: [c_long; 5] =
|
||||
response_msg.data.as_longs().try_into().unwrap();
|
||||
[a as u32, b as u32, c as u32, d as u32, e as u32]
|
||||
}),
|
||||
};
|
||||
|
||||
wt.xconn
|
||||
.xcb_connection()
|
||||
.send_event(
|
||||
false,
|
||||
wt.root,
|
||||
Some(ffi::SubstructureNotifyMask | ffi::SubstructureRedirectMask),
|
||||
*response_msg,
|
||||
xproto::EventMask::SUBSTRUCTURE_NOTIFY
|
||||
| xproto::EventMask::SUBSTRUCTURE_REDIRECT,
|
||||
client_msg.serialize(),
|
||||
)
|
||||
.queue();
|
||||
} else if client_msg.message_type == self.dnd.atoms.enter {
|
||||
let source_window = client_msg.data.get_long(0) as c_ulong;
|
||||
.expect_then_ignore_error("Failed to send `ClientMessage` event.");
|
||||
} else if client_msg.message_type == atoms[XdndEnter] as c_ulong {
|
||||
let source_window = client_msg.data.get_long(0) as xproto::Window;
|
||||
let flags = client_msg.data.get_long(1);
|
||||
let version = flags >> 24;
|
||||
self.dnd.version = Some(version);
|
||||
let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1;
|
||||
if !has_more_types {
|
||||
let type_list = vec![
|
||||
client_msg.data.get_long(2) as c_ulong,
|
||||
client_msg.data.get_long(3) as c_ulong,
|
||||
client_msg.data.get_long(4) as c_ulong,
|
||||
client_msg.data.get_long(2) as xproto::Atom,
|
||||
client_msg.data.get_long(3) as xproto::Atom,
|
||||
client_msg.data.get_long(4) as xproto::Atom,
|
||||
];
|
||||
self.dnd.type_list = Some(type_list);
|
||||
} else if let Ok(more_types) = unsafe { self.dnd.get_type_list(source_window) }
|
||||
{
|
||||
self.dnd.type_list = Some(more_types);
|
||||
}
|
||||
} else if client_msg.message_type == self.dnd.atoms.position {
|
||||
} else if client_msg.message_type == atoms[XdndPosition] as c_ulong {
|
||||
// This event occurs every time the mouse moves while a file's being dragged
|
||||
// over our window. We emit HoveredFile in response; while the macOS backend
|
||||
// does that upon a drag entering, XDND doesn't have access to the actual drop
|
||||
@@ -181,7 +212,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
// supply position updates with `HoveredFile` or another event, implementing
|
||||
// that here would be trivial.
|
||||
|
||||
let source_window = client_msg.data.get_long(0) as c_ulong;
|
||||
let source_window = client_msg.data.get_long(0) as xproto::Window;
|
||||
|
||||
// Equivalent to `(x << shift) | y`
|
||||
// where `shift = mem::size_of::<c_short>() * 8`
|
||||
@@ -199,7 +230,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
//let action = client_msg.data.get_long(4);
|
||||
|
||||
let accepted = if let Some(ref type_list) = self.dnd.type_list {
|
||||
type_list.contains(&self.dnd.atoms.uri_list)
|
||||
type_list.contains(&atoms[TextUriList])
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@@ -209,11 +240,15 @@ impl<T: 'static> EventProcessor<T> {
|
||||
unsafe {
|
||||
if self.dnd.result.is_none() {
|
||||
let time = if version >= 1 {
|
||||
client_msg.data.get_long(3) as c_ulong
|
||||
client_msg.data.get_long(3) as xproto::Timestamp
|
||||
} else {
|
||||
// In version 0, time isn't specified
|
||||
ffi::CurrentTime
|
||||
x11rb::CURRENT_TIME
|
||||
};
|
||||
|
||||
// Log this timestamp.
|
||||
wt.xconn.set_timestamp(time);
|
||||
|
||||
// This results in the `SelectionNotify` event below
|
||||
self.dnd.convert_selection(window, time);
|
||||
}
|
||||
@@ -229,7 +264,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
self.dnd.reset();
|
||||
}
|
||||
} else if client_msg.message_type == self.dnd.atoms.drop {
|
||||
} else if client_msg.message_type == atoms[XdndDrop] as c_ulong {
|
||||
let (source_window, state) = if let Some(source_window) = self.dnd.source_window
|
||||
{
|
||||
if let Some(Ok(ref path_list)) = self.dnd.result {
|
||||
@@ -244,7 +279,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
} else {
|
||||
// `source_window` won't be part of our DND state if we already rejected the drop in our
|
||||
// `XdndPosition` handler.
|
||||
let source_window = client_msg.data.get_long(0) as c_ulong;
|
||||
let source_window = client_msg.data.get_long(0) as xproto::Window;
|
||||
(source_window, DndState::Rejected)
|
||||
};
|
||||
unsafe {
|
||||
@@ -253,7 +288,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
.expect("Failed to send `XdndFinished` message.");
|
||||
}
|
||||
self.dnd.reset();
|
||||
} else if client_msg.message_type == self.dnd.atoms.leave {
|
||||
} else if client_msg.message_type == atoms[XdndLeave] as c_ulong {
|
||||
self.dnd.reset();
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
@@ -265,10 +300,13 @@ impl<T: 'static> EventProcessor<T> {
|
||||
ffi::SelectionNotify => {
|
||||
let xsel: &ffi::XSelectionEvent = xev.as_ref();
|
||||
|
||||
let window = xsel.requestor;
|
||||
let window = xsel.requestor as xproto::Window;
|
||||
let window_id = mkwid(window);
|
||||
|
||||
if xsel.property == self.dnd.atoms.selection {
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xsel.time as xproto::Timestamp);
|
||||
|
||||
if xsel.property == atoms[XdndSelection] as c_ulong {
|
||||
let mut result = None;
|
||||
|
||||
// This is where we receive data from drag and drop
|
||||
@@ -291,7 +329,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
ffi::ConfigureNotify => {
|
||||
let xev: &ffi::XConfigureEvent = xev.as_ref();
|
||||
let xwindow = xev.window;
|
||||
let xwindow = xev.window as xproto::Window;
|
||||
let window_id = mkwid(xwindow);
|
||||
|
||||
if let Some(window) = self.with_window(xwindow, Arc::clone) {
|
||||
@@ -402,21 +440,27 @@ impl<T: 'static> EventProcessor<T> {
|
||||
);
|
||||
|
||||
let old_inner_size = PhysicalSize::new(width, height);
|
||||
let mut new_inner_size = PhysicalSize::new(new_width, new_height);
|
||||
let new_inner_size = PhysicalSize::new(new_width, new_height);
|
||||
|
||||
// Unlock shared state to prevent deadlock in callback below
|
||||
drop(shared_state_lock);
|
||||
|
||||
let inner_size = Arc::new(Mutex::new(new_inner_size));
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: new_scale_factor,
|
||||
new_inner_size: &mut new_inner_size,
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
|
||||
&inner_size,
|
||||
)),
|
||||
},
|
||||
});
|
||||
|
||||
let new_inner_size = *inner_size.lock().unwrap();
|
||||
drop(inner_size);
|
||||
|
||||
if new_inner_size != old_inner_size {
|
||||
window.set_inner_size_physical(
|
||||
window.request_inner_size_physical(
|
||||
new_inner_size.width,
|
||||
new_inner_size.height,
|
||||
);
|
||||
@@ -440,7 +484,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
// When this finally happens, the event will not be synthetic.
|
||||
shared_state_lock.dpi_adjusted = None;
|
||||
} else {
|
||||
window.set_inner_size_physical(adjusted_size.0, adjusted_size.1);
|
||||
window.request_inner_size_physical(adjusted_size.0, adjusted_size.1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,13 +510,13 @@ impl<T: 'static> EventProcessor<T> {
|
||||
// effect is that we waste some time trying to query unsupported properties.
|
||||
wt.xconn.update_cached_wm_info(wt.root);
|
||||
|
||||
self.with_window(xev.window, |window| {
|
||||
self.with_window(xev.window as xproto::Window, |window| {
|
||||
window.invalidate_cached_frame_extents();
|
||||
});
|
||||
}
|
||||
ffi::MapNotify => {
|
||||
let xev: &ffi::XMapEvent = xev.as_ref();
|
||||
let window = xev.window;
|
||||
let window = xev.window as xproto::Window;
|
||||
let window_id = mkwid(window);
|
||||
|
||||
// XXX re-issue the focus state when mapping the window.
|
||||
@@ -491,7 +535,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
ffi::DestroyNotify => {
|
||||
let xev: &ffi::XDestroyWindowEvent = xev.as_ref();
|
||||
|
||||
let window = xev.window;
|
||||
let window = xev.window as xproto::Window;
|
||||
let window_id = mkwid(window);
|
||||
|
||||
// In the event that the window's been destroyed without being dropped first, we
|
||||
@@ -502,7 +546,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
// context here instead of when dropping the window.
|
||||
wt.ime
|
||||
.borrow_mut()
|
||||
.remove_context(window)
|
||||
.remove_context(window as ffi::Window)
|
||||
.expect("Failed to destroy input context");
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
@@ -513,7 +557,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
ffi::VisibilityNotify => {
|
||||
let xev: &ffi::XVisibilityEvent = xev.as_ref();
|
||||
let xwindow = xev.window;
|
||||
let xwindow = xev.window as xproto::Window;
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(xwindow),
|
||||
event: WindowEvent::Occluded(xev.state == ffi::VisibilityFullyObscured),
|
||||
@@ -529,7 +573,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
// Multiple Expose events may be received for subareas of a window.
|
||||
// We issue `RedrawRequested` only for the last event of such a series.
|
||||
if xev.count == 0 {
|
||||
let window = xev.window;
|
||||
let window = xev.window as xproto::Window;
|
||||
let window_id = mkwid(window);
|
||||
|
||||
callback(Event::RedrawRequested(window_id));
|
||||
@@ -539,22 +583,52 @@ impl<T: 'static> EventProcessor<T> {
|
||||
// Note that in compose/pre-edit sequences, we'll always receive KeyRelease events
|
||||
ty @ ffi::KeyPress | ty @ ffi::KeyRelease => {
|
||||
let xkev: &mut ffi::XKeyEvent = xev.as_mut();
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xkev.time as xproto::Timestamp);
|
||||
|
||||
let window = match self.active_window {
|
||||
Some(window) => window,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let window_id = mkwid(window);
|
||||
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
|
||||
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD.into());
|
||||
|
||||
let keycode = xkev.keycode as _;
|
||||
let repeat = ty == ffi::KeyPress && !self.got_key_release;
|
||||
// Update state after the repeat setting.
|
||||
|
||||
// Update state to track key repeats and determine whether this key was a repeat.
|
||||
//
|
||||
// Note, when a key is held before focusing on this window the first
|
||||
// (non-synthetic) event will not be flagged as a repeat (also note that the
|
||||
// synthetic press event that is generated before this when the window gains focus
|
||||
// will also not be flagged as a repeat).
|
||||
//
|
||||
// Only keys that can repeat should change the held_key_press state since a
|
||||
// continuously held repeatable key may continue repeating after the press of a
|
||||
// non-repeatable key.
|
||||
let repeat = if self.kb_state.key_repeats(keycode) {
|
||||
let is_latest_held = self.held_key_press == Some(keycode);
|
||||
|
||||
if ty == ffi::KeyPress {
|
||||
self.held_key_press = Some(keycode);
|
||||
is_latest_held
|
||||
} else {
|
||||
// Check that the released key is the latest repeatable key that has been
|
||||
// pressed, since repeats will continue for the latest key press if a
|
||||
// different previously pressed key is released.
|
||||
if is_latest_held {
|
||||
self.held_key_press = None;
|
||||
}
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let state = if ty == ffi::KeyPress {
|
||||
self.got_key_release = false;
|
||||
ElementState::Pressed
|
||||
} else {
|
||||
self.got_key_release = true;
|
||||
ElementState::Released
|
||||
};
|
||||
|
||||
@@ -568,7 +642,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
is_synthetic: false,
|
||||
},
|
||||
});
|
||||
} else if let Some(ic) = wt.ime.borrow().get_context(window) {
|
||||
} else if let Some(ic) = wt.ime.borrow().get_context(window as ffi::Window) {
|
||||
let written = wt.xconn.lookup_utf8(ic, xkev);
|
||||
if !written.is_empty() {
|
||||
let event = Event::WindowEvent {
|
||||
@@ -613,8 +687,12 @@ impl<T: 'static> EventProcessor<T> {
|
||||
match xev.evtype {
|
||||
ffi::XI_ButtonPress | ffi::XI_ButtonRelease => {
|
||||
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
||||
let window_id = mkwid(xev.event);
|
||||
let window_id = mkwid(xev.event as xproto::Window);
|
||||
let device_id = mkdid(xev.deviceid);
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
if (xev.flags & ffi::XIPointerEmulated) != 0 {
|
||||
// Deliver multi-touch events instead of emulated mouse events.
|
||||
return;
|
||||
@@ -702,11 +780,16 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
ffi::XI_Motion => {
|
||||
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
let device_id = mkdid(xev.deviceid);
|
||||
let window_id = mkwid(xev.event);
|
||||
let window = xev.event as xproto::Window;
|
||||
let window_id = mkwid(window);
|
||||
let new_cursor_pos = (xev.event_x, xev.event_y);
|
||||
|
||||
let cursor_moved = self.with_window(xev.event, |window| {
|
||||
let cursor_moved = self.with_window(window, |window| {
|
||||
let mut shared_state_lock = window.shared_state_lock();
|
||||
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
|
||||
});
|
||||
@@ -788,7 +871,11 @@ impl<T: 'static> EventProcessor<T> {
|
||||
ffi::XI_Enter => {
|
||||
let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) };
|
||||
|
||||
let window_id = mkwid(xev.event);
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
let window = xev.event as xproto::Window;
|
||||
let window_id = mkwid(window);
|
||||
let device_id = mkdid(xev.deviceid);
|
||||
|
||||
if let Some(all_info) = DeviceInfo::get(&wt.xconn, ffi::XIAllDevices) {
|
||||
@@ -809,7 +896,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.window_exists(xev.event) {
|
||||
if self.window_exists(window) {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: CursorEntered { device_id },
|
||||
@@ -828,13 +915,17 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
ffi::XI_Leave => {
|
||||
let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) };
|
||||
let window = xev.event as xproto::Window;
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
// Leave, FocusIn, and FocusOut can be received by a window that's already
|
||||
// been destroyed, which the user presumably doesn't want to deal with.
|
||||
let window_closed = !self.window_exists(xev.event);
|
||||
let window_closed = !self.window_exists(window);
|
||||
if !window_closed {
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(xev.event),
|
||||
window_id: mkwid(window),
|
||||
event: CursorLeft {
|
||||
device_id: mkdid(xev.deviceid),
|
||||
},
|
||||
@@ -843,21 +934,25 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
ffi::XI_FocusIn => {
|
||||
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
|
||||
let window = xev.event as xproto::Window;
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
wt.ime
|
||||
.borrow_mut()
|
||||
.focus(xev.event)
|
||||
.expect("Failed to focus input context");
|
||||
|
||||
if self.active_window != Some(xev.event) {
|
||||
self.active_window = Some(xev.event);
|
||||
if self.active_window != Some(window) {
|
||||
self.active_window = Some(window);
|
||||
|
||||
wt.update_listen_device_events(true);
|
||||
|
||||
let window_id = mkwid(xev.event);
|
||||
let window_id = mkwid(window);
|
||||
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
|
||||
|
||||
if let Some(window) = self.with_window(xev.event, Arc::clone) {
|
||||
if let Some(window) = self.with_window(window, Arc::clone) {
|
||||
window.shared_state_lock().has_focus = true;
|
||||
}
|
||||
|
||||
@@ -904,7 +999,12 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
ffi::XI_FocusOut => {
|
||||
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
|
||||
if !self.window_exists(xev.event) {
|
||||
let window = xev.event as xproto::Window;
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
if !self.window_exists(window) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -913,8 +1013,8 @@ impl<T: 'static> EventProcessor<T> {
|
||||
.unfocus(xev.event)
|
||||
.expect("Failed to unfocus input context");
|
||||
|
||||
if self.active_window.take() == Some(xev.event) {
|
||||
let window_id = mkwid(xev.event);
|
||||
if self.active_window.take() == Some(window) {
|
||||
let window_id = mkwid(window);
|
||||
|
||||
wt.update_listen_device_events(false);
|
||||
|
||||
@@ -926,6 +1026,9 @@ impl<T: 'static> EventProcessor<T> {
|
||||
&mut self.kb_state,
|
||||
&mut callback,
|
||||
);
|
||||
// Clear this so detecting key repeats is consistently handled when the
|
||||
// window regains focus.
|
||||
self.held_key_press = None;
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
@@ -934,7 +1037,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
),
|
||||
});
|
||||
|
||||
if let Some(window) = self.with_window(xev.event, Arc::clone) {
|
||||
if let Some(window) = self.with_window(window, Arc::clone) {
|
||||
window.shared_state_lock().has_focus = false;
|
||||
}
|
||||
|
||||
@@ -947,14 +1050,19 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => {
|
||||
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
||||
let window_id = mkwid(xev.event);
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
let window = xev.event as xproto::Window;
|
||||
let window_id = mkwid(window);
|
||||
let phase = match xev.evtype {
|
||||
ffi::XI_TouchBegin => TouchPhase::Started,
|
||||
ffi::XI_TouchUpdate => TouchPhase::Moved,
|
||||
ffi::XI_TouchEnd => TouchPhase::Ended,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if self.window_exists(xev.event) {
|
||||
if self.window_exists(window) {
|
||||
let id = xev.detail as u64;
|
||||
let location = PhysicalPosition::new(xev.event_x, xev.event_y);
|
||||
|
||||
@@ -965,7 +1073,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::CursorMoved {
|
||||
device_id: mkdid(util::VIRTUAL_CORE_POINTER),
|
||||
device_id: mkdid(util::VIRTUAL_CORE_POINTER.into()),
|
||||
position: location.cast(),
|
||||
},
|
||||
});
|
||||
@@ -986,6 +1094,10 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => {
|
||||
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
if xev.flags & ffi::XIPointerEmulated == 0 {
|
||||
callback(Event::DeviceEvent {
|
||||
device_id: mkdid(xev.deviceid),
|
||||
@@ -1003,6 +1115,10 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
ffi::XI_RawMotion => {
|
||||
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
let did = mkdid(xev.deviceid);
|
||||
|
||||
let mask = unsafe {
|
||||
@@ -1054,6 +1170,9 @@ impl<T: 'static> EventProcessor<T> {
|
||||
ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => {
|
||||
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
let state = match xev.evtype {
|
||||
ffi::XI_RawKeyPress => Pressed,
|
||||
ffi::XI_RawKeyRelease => Released,
|
||||
@@ -1078,6 +1197,10 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
ffi::XI_HierarchyChanged => {
|
||||
let xev: &ffi::XIHierarchyEvent = unsafe { &*(xev.data as *const _) };
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
for info in
|
||||
unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) }
|
||||
{
|
||||
@@ -1110,6 +1233,10 @@ impl<T: 'static> EventProcessor<T> {
|
||||
let xev = unsafe {
|
||||
&*(xev as *const _ as *const ffi::XkbNewKeyboardNotifyEvent)
|
||||
};
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
let keycodes_changed_flag = 0x1;
|
||||
let geometry_changed_flag = 0x1 << 1;
|
||||
|
||||
@@ -1128,6 +1255,9 @@ impl<T: 'static> EventProcessor<T> {
|
||||
let xev =
|
||||
unsafe { &*(xev as *const _ as *const ffi::XkbStateNotifyEvent) };
|
||||
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
let prev_mods = self.kb_state.mods_state();
|
||||
self.kb_state.update_modifiers(
|
||||
xev.base_mods,
|
||||
@@ -1187,21 +1317,27 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
let window_id = crate::window::WindowId(*window_id);
|
||||
let old_inner_size = PhysicalSize::new(width, height);
|
||||
let mut new_inner_size =
|
||||
PhysicalSize::new(new_width, new_height);
|
||||
|
||||
let inner_size = Arc::new(Mutex::new(
|
||||
PhysicalSize::new(new_width, new_height),
|
||||
));
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: new_monitor.scale_factor,
|
||||
new_inner_size: &mut new_inner_size,
|
||||
inner_size_writer: InnerSizeWriter::new(
|
||||
Arc::downgrade(&inner_size),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
let new_inner_size = *inner_size.lock().unwrap();
|
||||
drop(inner_size);
|
||||
|
||||
if new_inner_size != old_inner_size {
|
||||
let (new_width, new_height) = new_inner_size.into();
|
||||
window
|
||||
.set_inner_size_physical(new_width, new_height);
|
||||
window.request_inner_size_physical(
|
||||
new_width, new_height,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1227,7 +1363,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
|
||||
let (window, event) = match self.ime_event_receiver.try_recv() {
|
||||
Ok((window, event)) => (window, event),
|
||||
Ok((window, event)) => (window as xproto::Window, event),
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
@@ -1278,9 +1414,9 @@ impl<T: 'static> EventProcessor<T> {
|
||||
kb_state: &mut KbdState,
|
||||
callback: &mut F,
|
||||
) where
|
||||
F: FnMut(Event<'_, T>),
|
||||
F: FnMut(Event<T>),
|
||||
{
|
||||
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
|
||||
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD.into());
|
||||
|
||||
// Update modifiers state and emit key events based on which keys are currently pressed.
|
||||
for keycode in wt
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user