Compare commits
104 Commits
rwh-send-s
...
notgull/sp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d28c867b61 | ||
|
|
cfd35d2708 | ||
|
|
1b9c576f53 | ||
|
|
52532d2579 | ||
|
|
4b28f10470 | ||
|
|
daf1304879 | ||
|
|
c6e57430bb | ||
|
|
284f4c0558 | ||
|
|
859a6f109c | ||
|
|
a232f7bc77 | ||
|
|
48831f7910 | ||
|
|
114e6999d3 | ||
|
|
2f197315d9 | ||
|
|
557464489e | ||
|
|
623b3e5070 | ||
|
|
a97255e0fc | ||
|
|
3a817647d5 | ||
|
|
56035e1f13 | ||
|
|
08fc4099e8 | ||
|
|
4d4d6e5052 | ||
|
|
cf5f4de19e | ||
|
|
21df84b7f4 | ||
|
|
dd13ccda4c | ||
|
|
df8805c0d2 | ||
|
|
db1ca45a17 | ||
|
|
ff731197dc | ||
|
|
f526a47152 | ||
|
|
f204467838 | ||
|
|
6641dfa412 | ||
|
|
c1168b4f58 | ||
|
|
f8b7c4b78f | ||
|
|
3830b492c4 | ||
|
|
8862ce0163 | ||
|
|
569c44a632 | ||
|
|
ef2ec904ce | ||
|
|
98d3391f2d | ||
|
|
d0a1917603 | ||
|
|
b36d8d1e52 | ||
|
|
a5b08fc48c | ||
|
|
0482d9cfce | ||
|
|
10a785019c | ||
|
|
0cc19716f3 | ||
|
|
572d7ee77c | ||
|
|
b0c59c8416 | ||
|
|
d7c7ba1d6c | ||
|
|
aec608f93c | ||
|
|
d1717b6a01 | ||
|
|
6b29253797 | ||
|
|
30775f4982 | ||
|
|
41070d7c67 | ||
|
|
6df972d108 | ||
|
|
73910d20cc | ||
|
|
16d860736b | ||
|
|
2ee44246ae | ||
|
|
14b418a3a7 | ||
|
|
c86b0daf7f | ||
|
|
40b61d2d92 | ||
|
|
22311802b5 | ||
|
|
169cd39f93 | ||
|
|
bdeb2574dc | ||
|
|
4fe38d8067 | ||
|
|
816798bfd1 | ||
|
|
f99c810bec | ||
|
|
d39528aa69 | ||
|
|
787b2d7362 | ||
|
|
37b6243289 | ||
|
|
8ea1da7879 | ||
|
|
b1209bc253 | ||
|
|
021fd23c34 | ||
|
|
dd127463c5 | ||
|
|
ac247cd081 | ||
|
|
ea1bfd254d | ||
|
|
178f5fda05 | ||
|
|
42dbc4748e | ||
|
|
8b3de7cedf | ||
|
|
8b0ffb7e7d | ||
|
|
c55a2c779b | ||
|
|
c5a422eed6 | ||
|
|
1893b0ec42 | ||
|
|
5e106b4dbb | ||
|
|
5a1d3e4656 | ||
|
|
8f6de4ef4b | ||
|
|
ad1843aea6 | ||
|
|
ca1674519a | ||
|
|
f78edc7ef1 | ||
|
|
4f295e0c94 | ||
|
|
658f49b014 | ||
|
|
34e42ff94d | ||
|
|
ba654bb61e | ||
|
|
f5c691467b | ||
|
|
a87cfb62c3 | ||
|
|
25d6a1d46d | ||
|
|
e0fea25b06 | ||
|
|
843d7904d6 | ||
|
|
28a811bbba | ||
|
|
61a873d79a | ||
|
|
be4a660011 | ||
|
|
34dd2cdba9 | ||
|
|
775c8ece70 | ||
|
|
c12c7b82e8 | ||
|
|
8cc5cb9d9b | ||
|
|
9a28bb4b49 | ||
|
|
4f6fd44c6c | ||
|
|
5a43ea8cd6 |
16
.github/CODEOWNERS
vendored
@@ -1,12 +1,6 @@
|
||||
# Core maintainers:
|
||||
# - @msiglreith
|
||||
# - @kchibisov
|
||||
# - @madsmtm
|
||||
# - @maroider
|
||||
|
||||
# Android
|
||||
/src/platform/android.rs @msiglreith
|
||||
/src/platform_impl/android @msiglreith
|
||||
/src/platform/android.rs @msiglreith @MarijnS95
|
||||
/src/platform_impl/android @msiglreith @MarijnS95
|
||||
|
||||
# iOS
|
||||
/src/platform/ios.rs @madsmtm
|
||||
@@ -20,14 +14,14 @@
|
||||
/src/platform_impl/linux/wayland @kchibisov
|
||||
|
||||
# X11
|
||||
/src/platform/x11.rs @kchibisov
|
||||
/src/platform_impl/linux/x11 @kchibisov
|
||||
/src/platform/x11.rs @kchibisov @notgull
|
||||
/src/platform_impl/linux/x11 @kchibisov @notgull
|
||||
|
||||
# macOS
|
||||
/src/platform/macos.rs @madsmtm
|
||||
/src/platform_impl/macos @madsmtm
|
||||
|
||||
# Web (no maintainer)
|
||||
# Web
|
||||
/src/platform/web.rs @daxpedda
|
||||
/src/platform_impl/web @daxpedda
|
||||
|
||||
|
||||
30
.github/workflows/ci.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
|
||||
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
|
||||
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--features=android-native-activity', cmd: 'apk --' }
|
||||
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
|
||||
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
|
||||
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
|
||||
@@ -48,6 +48,15 @@ jobs:
|
||||
include:
|
||||
- toolchain: '1.70.0'
|
||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
- toolchain: 'nightly'
|
||||
platform: {
|
||||
name: 'web Atomic',
|
||||
target: wasm32-unknown-unknown,
|
||||
os: ubuntu-latest,
|
||||
options: '-Zbuild-std=panic_abort,std',
|
||||
rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
|
||||
components: rust-src,
|
||||
}
|
||||
|
||||
env:
|
||||
# Set more verbose terminal output
|
||||
@@ -55,8 +64,7 @@ jobs:
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
# Faster compilation and error on warnings
|
||||
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings'
|
||||
RUSTDOCFLAGS: '--deny=warnings'
|
||||
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
|
||||
|
||||
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
|
||||
CMD: ${{ matrix.platform.cmd }}
|
||||
@@ -109,19 +117,21 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
|
||||
targets: ${{ matrix.platform.target }}
|
||||
components: clippy
|
||||
components: clippy, ${{ matrix.platform.components }}
|
||||
|
||||
- name: Check documentation
|
||||
run: cargo doc --no-deps $OPTIONS --document-private-items
|
||||
env:
|
||||
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}'
|
||||
|
||||
- name: Build crate
|
||||
run: cargo $CMD build $OPTIONS
|
||||
run: cargo $CMD build -p winit $OPTIONS
|
||||
|
||||
- name: Build tests
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.70.0'
|
||||
run: cargo $CMD test --no-run $OPTIONS
|
||||
run: cargo $CMD test -p winit --no-run $OPTIONS
|
||||
|
||||
- name: Run tests
|
||||
if: >
|
||||
@@ -140,7 +150,7 @@ jobs:
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.70.0'
|
||||
run: cargo $CMD test --no-run $OPTIONS --features serde
|
||||
run: cargo $CMD test --no-run $OPTIONS -p winit --features serde
|
||||
|
||||
- name: Run tests with serde enabled
|
||||
if: >
|
||||
@@ -151,6 +161,12 @@ jobs:
|
||||
matrix.toolchain != '1.70.0'
|
||||
run: cargo $CMD test $OPTIONS --features serde
|
||||
|
||||
- name: Check docs.rs documentation
|
||||
if: matrix.toolchain == 'nightly'
|
||||
run: cargo doc --no-deps $OPTIONS --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
|
||||
env:
|
||||
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs'
|
||||
|
||||
# See restore step above
|
||||
- name: Save cache of cargo folder
|
||||
uses: actions/cache/save@v3
|
||||
|
||||
50
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Documentation
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}winit
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Run Rustdoc
|
||||
env:
|
||||
RUSTDOCFLAGS: --crate-version master --cfg=docsrs
|
||||
run: |
|
||||
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Fix permissions
|
||||
run: |
|
||||
chmod -c -R +rX "target/doc" | while read line; do
|
||||
echo "::warning title=Invalid file permissions automatically fixed::$line"
|
||||
done
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: target/doc
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
71
CHANGELOG.md
@@ -11,18 +11,84 @@ Unreleased` header.
|
||||
|
||||
# Unreleased
|
||||
|
||||
- On X11, fix swapped instance and general class names.
|
||||
- **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`.
|
||||
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
|
||||
- Add `Window::set_custom_cursor`
|
||||
- **Breaking:** Remove `Window::set_cursor_icon`
|
||||
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`
|
||||
- Add `CustomCursor`
|
||||
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
|
||||
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
|
||||
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s.
|
||||
- On macOS, add services menu.
|
||||
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
|
||||
- On Web, fix setting cursor icon overriding cursor visibility.
|
||||
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
|
||||
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
|
||||
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
|
||||
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
|
||||
- On Web, add the ability to toggle calling `Event.preventDefault()` on `Window`.
|
||||
- **Breaking:** Remove `WindowAttributes::fullscreen()` and expose as field directly.
|
||||
- **Breaking:** Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold static data.
|
||||
- **Breaking:** No longer export `platform::x11::XNotSupported`.
|
||||
- **Breaking:** Renamed `platform::x11::XWindowType` to `platform::x11::WindowType`.
|
||||
- Add the `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases.
|
||||
- **Breaking:** Rename `TouchpadMagnify` to `PinchGesture`, `SmartMagnify` to `DoubleTapGesture` and `TouchpadRotate` to `RotationGesture` to represent the action rather than the intent.
|
||||
- on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`.
|
||||
- on Windows: add `with_system_backdrop`, `with_border_color`, `with_title_background_color`, `with_title_text_color` and `with_corner_preference`
|
||||
- On Windows, Remove `WS_CAPTION`, `WS_BORDER` and `WS_EX_WINDOWEDGE` styles for child windows without decorations.
|
||||
- On Windows, fixed a race condition when sending an event through the loop proxy.
|
||||
- **Breaking:** Removed `EventLoopError::AlreadyRunning`, which can't happen as it is already prevented by the type system.
|
||||
- On Wayland, disable `Occluded` event handling.
|
||||
- Added `EventLoop::builder`, which is intended to replace the (now deprecated) `EventLoopBuilder::new`.
|
||||
- **Breaking:** Changed the signature of `EventLoop::with_user_event` to return a builder.
|
||||
- **Breaking:** Removed `EventLoopBuilder::with_user_event`, the functionality is now available in `EventLoop::with_user_event`.
|
||||
- Add `Window::builder`, which is intended to replace the (now deprecated) `WindowBuilder::new`.
|
||||
- On X11, reload dpi on `_XSETTINGS_SETTINGS` update.
|
||||
- On X11, fix deadlock when adjusting DPI and resizing at the same time.
|
||||
- On Wayland, fix `Focused(false)` being send when other seats still have window focused.
|
||||
- On Wayland, fix `Window::set_{min,max}_inner_size` not always applied.
|
||||
- On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop.
|
||||
- On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
|
||||
- **Breaking:** Move some types to the `winit-core` crate. Most types are
|
||||
re-exported verbatim; however:
|
||||
- `Event`, `WindowEvent` and `KeyEvent` now take a generic that `winit`
|
||||
implements.
|
||||
- `ActivationToken` and `InnerSizeWriter` expose new methods to make them
|
||||
useful.
|
||||
- `InnerSizeWriter::request_inner_size` returns an `InnerSizeIgnored` error
|
||||
instead of an `ExternalError`.
|
||||
|
||||
# 0.29.10
|
||||
|
||||
- On Web, account for canvas being focused already before event loop starts.
|
||||
- On Web, increase cursor position accuracy.
|
||||
|
||||
# 0.29.9
|
||||
|
||||
- On X11, fix `NotSupported` error not propagated when creating event loop.
|
||||
- On Wayland, fix resize not issued when scale changes
|
||||
- On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`.
|
||||
- On macOS, report correct logical key when Ctrl or Cmd is pressed.
|
||||
|
||||
# 0.29.8
|
||||
|
||||
- On X11, fix IME input lagging behind.
|
||||
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
|
||||
- On X11, fix keymap not updated from xmodmap.
|
||||
- On X11, reduce the amount of time spent fetching screen resources.
|
||||
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
|
||||
- On Wayland, fix `Window::inner_size` not using the correct rounding.
|
||||
|
||||
# 0.29.7
|
||||
|
||||
- On X11, fix `Xft.dpi` reload during runtime.
|
||||
- On X11, fix window minimize.
|
||||
|
||||
# 0.29.6
|
||||
|
||||
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
|
||||
- On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window.
|
||||
- Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation
|
||||
|
||||
# 0.29.5
|
||||
|
||||
@@ -47,6 +113,7 @@ Unreleased` header.
|
||||
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
|
||||
- On Wayland, fix `wl_surface` being destroyed before associated objects.
|
||||
- On macOS, fix assertion when pressing `Fn` key.
|
||||
- On Windows, add `WindowBuilderExtWindows::with_clip_children` to control `WS_CLIPCHILDREN` style.
|
||||
|
||||
# 0.29.3
|
||||
|
||||
|
||||
263
Cargo.toml
@@ -1,255 +1,16 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.29.5"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2021"
|
||||
keywords = ["windowing"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
rust-version = "1.70.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [
|
||||
"rwh_04",
|
||||
"rwh_05",
|
||||
"rwh_06",
|
||||
"serde",
|
||||
# Enabled to get docs to compile
|
||||
"android-native-activity",
|
||||
]
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
# These are all tested in CI
|
||||
targets = [
|
||||
# Windows
|
||||
"i686-pc-windows-msvc",
|
||||
"x86_64-pc-windows-msvc",
|
||||
# macOS
|
||||
"x86_64-apple-darwin",
|
||||
# Unix (X11 & Wayland)
|
||||
"i686-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
# iOS
|
||||
"x86_64-apple-ios",
|
||||
# Android
|
||||
"aarch64-linux-android",
|
||||
# WebAssembly
|
||||
"wasm32-unknown-unknown",
|
||||
]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
|
||||
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
|
||||
wayland-dlopen = ["wayland-backend/dlopen"]
|
||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
|
||||
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
|
||||
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
|
||||
android-native-activity = ["android-activity/native-activity"]
|
||||
android-game-activity = ["android-activity/game-activity"]
|
||||
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
|
||||
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
|
||||
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
|
||||
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
|
||||
|
||||
[build-dependencies]
|
||||
cfg_aliases = "0.1.1"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
cursor-icon = "1.1.0"
|
||||
log = "0.4"
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
once_cell = "1.12"
|
||||
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
|
||||
rwh_05 = { package = "raw-window-handle", version = "0.5", features = ["std"], optional = true }
|
||||
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
smol_str = "0.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
image = { version = "0.24.0", default-features = false, features = ["png"] }
|
||||
simple_logger = { version = "4.2.0", default_features = false }
|
||||
winit = { path = ".", features = ["rwh_05"] }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
|
||||
softbuffer = "0.3.0"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android-activity = "0.5.0"
|
||||
ndk = { version = "0.8.0", default-features = false }
|
||||
ndk-sys = "0.5.0"
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
core-foundation = "0.9.3"
|
||||
objc2 = "0.5.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.23.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.icrate]
|
||||
version = "0.1.0"
|
||||
features = [
|
||||
"dispatch",
|
||||
"Foundation",
|
||||
"Foundation_NSArray",
|
||||
"Foundation_NSAttributedString",
|
||||
"Foundation_NSMutableAttributedString",
|
||||
"Foundation_NSData",
|
||||
"Foundation_NSDictionary",
|
||||
"Foundation_NSString",
|
||||
"Foundation_NSProcessInfo",
|
||||
"Foundation_NSThread",
|
||||
"Foundation_NSNumber",
|
||||
"AppKit",
|
||||
"AppKit_NSAppearance",
|
||||
"AppKit_NSApplication",
|
||||
"AppKit_NSBitmapImageRep",
|
||||
"AppKit_NSButton",
|
||||
"AppKit_NSColor",
|
||||
"AppKit_NSControl",
|
||||
"AppKit_NSCursor",
|
||||
"AppKit_NSEvent",
|
||||
"AppKit_NSGraphicsContext",
|
||||
"AppKit_NSImage",
|
||||
"AppKit_NSImageRep",
|
||||
"AppKit_NSMenu",
|
||||
"AppKit_NSMenuItem",
|
||||
"AppKit_NSPasteboard",
|
||||
"AppKit_NSResponder",
|
||||
"AppKit_NSScreen",
|
||||
"AppKit_NSTextInputContext",
|
||||
"AppKit_NSView",
|
||||
"AppKit_NSWindow",
|
||||
"AppKit_NSWindowTabGroup",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies.icrate]
|
||||
version = "0.1.0"
|
||||
features = [
|
||||
"dispatch",
|
||||
"Foundation",
|
||||
"Foundation_NSArray",
|
||||
"Foundation_NSString",
|
||||
"Foundation_NSProcessInfo",
|
||||
"Foundation_NSThread",
|
||||
"Foundation_NSSet",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
unicode-segmentation = "1.7.1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||
version = "0.48"
|
||||
features = [
|
||||
"Win32_Devices_HumanInterfaceDevice",
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Media",
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Ole",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_WindowsProgramming",
|
||||
"Win32_UI_Accessibility",
|
||||
"Win32_UI_Controls",
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_Input_Ime",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Input_Pointer",
|
||||
"Win32_UI_Input_Touch",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_TextServices",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
]
|
||||
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
|
||||
ahash = { version = "0.8.3", features = ["no-rng"], optional = true }
|
||||
bytemuck = { version = "1.13.1", default-features = false, optional = true }
|
||||
calloop = "0.12.3"
|
||||
libc = "0.2.64"
|
||||
memmap2 = { version = "0.9.0", optional = true }
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
|
||||
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
|
||||
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
|
||||
wayland-client = { version = "0.31.1", optional = true }
|
||||
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
|
||||
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
||||
xkbcommon-dl = "0.4.0"
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
orbclient = { version = "0.3.42", default-features = false }
|
||||
redox_syscall = "0.3"
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
|
||||
package = "web-sys"
|
||||
version = "0.3.64"
|
||||
features = [
|
||||
'AbortController',
|
||||
'AbortSignal',
|
||||
'Blob',
|
||||
'console',
|
||||
'CssStyleDeclaration',
|
||||
'Document',
|
||||
'DomRect',
|
||||
'DomRectReadOnly',
|
||||
'Element',
|
||||
'Event',
|
||||
'EventTarget',
|
||||
'FocusEvent',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'HtmlImageElement',
|
||||
'ImageBitmap',
|
||||
'ImageBitmapOptions',
|
||||
'ImageBitmapRenderingContext',
|
||||
'ImageData',
|
||||
'IntersectionObserver',
|
||||
'IntersectionObserverEntry',
|
||||
'KeyboardEvent',
|
||||
'MediaQueryList',
|
||||
'MessageChannel',
|
||||
'MessagePort',
|
||||
'Node',
|
||||
'PageTransitionEvent',
|
||||
'PointerEvent',
|
||||
'PremultiplyAlpha',
|
||||
'ResizeObserver',
|
||||
'ResizeObserverBoxOptions',
|
||||
'ResizeObserverEntry',
|
||||
'ResizeObserverOptions',
|
||||
'ResizeObserverSize',
|
||||
'VisibilityState',
|
||||
'Window',
|
||||
'WheelEvent',
|
||||
'Url',
|
||||
]
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
atomic-waker = "1"
|
||||
js-sys = "0.3.64"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-time = "0.2"
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dev-dependencies]
|
||||
console_log = "1"
|
||||
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"run-wasm",
|
||||
"winit",
|
||||
"winit-core"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
bitflags = "2"
|
||||
cfg_aliases = "0.2.0"
|
||||
cursor-icon = "1.1.0"
|
||||
serde = { version = "1", features = ["serde_derive"] }
|
||||
smol_str = "0.2.0"
|
||||
web-time = "1"
|
||||
winit-core = { path = "./winit-core", default-features = false, features = ["std"] }
|
||||
|
||||
@@ -126,6 +126,11 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
* Setting a menu bar
|
||||
* `WS_EX_NOREDIRECTIONBITMAP` support
|
||||
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
|
||||
* Changing a system-drawn backdrop
|
||||
* Setting the window border color
|
||||
* Setting the title bar background color
|
||||
* Setting the title color
|
||||
* Setting the corner rounding preference
|
||||
|
||||
### macOS
|
||||
* Window activation policy
|
||||
|
||||
17
README.md
@@ -2,11 +2,13 @@
|
||||
|
||||
[](https://crates.io/crates/winit)
|
||||
[](https://docs.rs/winit)
|
||||
[](https://rust-windowing.github.io/winit/winit/index.html)
|
||||
[](https://github.com/rust-windowing/winit/actions)
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.29.5"
|
||||
winit = "0.29.10"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -17,10 +19,9 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
|
||||
|
||||
## Contact Us
|
||||
|
||||
Join us in any of these:
|
||||
Join us in our [](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [](https://web.libera.chat/#winit).
|
||||
|
||||
[](https://matrix.to/#/#rust-windowing:matrix.org)
|
||||
[](https://web.libera.chat/#winit)
|
||||
The maintainers have a meeting every friday at UTC 14. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -74,7 +75,7 @@ same MSRV policy.
|
||||
|
||||
Note that windows don't appear on Wayland until you draw/present to them.
|
||||
|
||||
#### WebAssembly
|
||||
#### Web
|
||||
|
||||
To run the web example: `cargo run-wasm --example web`
|
||||
|
||||
@@ -85,7 +86,7 @@ either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
|
||||
create a `<canvas>` element which you can then retrieve][web canvas getter] and
|
||||
insert it into the DOM yourself.
|
||||
|
||||
For the example code using Winit with WebAssembly, check out the [web example]. For
|
||||
For the example code using Winit on Web, check out the [web example]. For
|
||||
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
|
||||
book].
|
||||
|
||||
@@ -150,13 +151,13 @@ class. Your application _must_ specify the base class it needs via a feature fla
|
||||
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
|
||||
[Gradle]: https://developer.android.com/studio/build
|
||||
|
||||
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
|
||||
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
|
||||
|
||||
##### Converting from `ndk-glue` to `android-activity`
|
||||
|
||||
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
|
||||
1. Remove `ndk-glue` from your `Cargo.toml`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.5", features = [ "android-native-activity" ] }`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.10", features = [ "android-native-activity" ] }`
|
||||
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
|
||||
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
|
||||
|
||||
|
||||
@@ -11,4 +11,5 @@ disallowed-methods = [
|
||||
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
|
||||
{ path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
|
||||
{ path = "icrate::AppKit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
|
||||
]
|
||||
|
||||
@@ -34,9 +34,10 @@ skip = [
|
||||
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
|
||||
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
|
||||
{ name = "libloading" }, # x11rb uses a different version until the next update
|
||||
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
|
||||
]
|
||||
skip-tree = []
|
||||
skip-tree = [
|
||||
{ name = "run-wasm", version = "0.1.0" }
|
||||
]
|
||||
|
||||
|
||||
[licenses]
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
#![allow(clippy::single_match, clippy::disallowed_methods)]
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
keyboard::Key,
|
||||
window::{CustomCursor, WindowBuilder},
|
||||
};
|
||||
|
||||
fn decode_cursor<T>(bytes: &[u8], window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
|
||||
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
|
||||
let samples = img.into_flat_samples();
|
||||
let (_, w, h) = samples.extents();
|
||||
let (w, h) = (w as u16, h as u16);
|
||||
let builder = CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap();
|
||||
|
||||
builder.build(window_target)
|
||||
}
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
#[cfg(not(wasm_platform))]
|
||||
SimpleLogger::new()
|
||||
.with_level(log::LevelFilter::Info)
|
||||
.init()
|
||||
.unwrap();
|
||||
#[cfg(wasm_platform)]
|
||||
console_log::init_with_level(log::Level::Debug).unwrap();
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let builder = WindowBuilder::new().with_title("A fantastic window!");
|
||||
#[cfg(wasm_platform)]
|
||||
let builder = {
|
||||
use winit::platform::web::WindowBuilderExtWebSys;
|
||||
builder.with_append(true)
|
||||
};
|
||||
let window = builder.build(&event_loop).unwrap();
|
||||
|
||||
let mut cursor_idx = 0;
|
||||
let mut cursor_visible = true;
|
||||
|
||||
let custom_cursors = [
|
||||
decode_cursor(include_bytes!("data/cross.png"), &event_loop),
|
||||
decode_cursor(include_bytes!("data/cross2.png"), &event_loop),
|
||||
];
|
||||
|
||||
event_loop.run(move |event, _elwt| match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
state: ElementState::Pressed,
|
||||
logical_key: key,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key.as_ref() {
|
||||
Key::Character("1") => {
|
||||
log::debug!("Setting cursor to {:?}", cursor_idx);
|
||||
window.set_custom_cursor(&custom_cursors[cursor_idx]);
|
||||
cursor_idx = (cursor_idx + 1) % 2;
|
||||
}
|
||||
Key::Character("2") => {
|
||||
log::debug!("Setting cursor icon to default");
|
||||
window.set_cursor_icon(Default::default());
|
||||
}
|
||||
Key::Character("3") => {
|
||||
cursor_visible = !cursor_visible;
|
||||
log::debug!("Setting cursor visibility to {:?}", cursor_visible);
|
||||
window.set_cursor_visible(cursor_visible);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
#[cfg(not(wasm_platform))]
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
#[cfg(not(wasm_platform))]
|
||||
_elwt.exit();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
//! Fill the window buffer with a solid color.
|
||||
//!
|
||||
//! Launching a window without drawing to it has unpredictable results varying from platform to
|
||||
//! platform. In order to have well-defined examples, this module provides an easy way to
|
||||
//! fill the window buffer with a solid color.
|
||||
//!
|
||||
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
|
||||
//! also be used to fill the window buffer, but they are more complicated to use.
|
||||
|
||||
use winit::window::Window;
|
||||
|
||||
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
|
||||
pub(super) fn fill_window(window: &Window) {
|
||||
use softbuffer::{Context, Surface};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU32;
|
||||
use winit::window::WindowId;
|
||||
|
||||
/// The graphics context used to draw to a window.
|
||||
struct GraphicsContext {
|
||||
/// The global softbuffer context.
|
||||
context: Context,
|
||||
|
||||
/// The hash map of window IDs to surfaces.
|
||||
surfaces: HashMap<WindowId, Surface>,
|
||||
}
|
||||
|
||||
impl GraphicsContext {
|
||||
fn new(w: &Window) -> Self {
|
||||
Self {
|
||||
context: unsafe { Context::new(w) }.expect("Failed to create a softbuffer context"),
|
||||
surfaces: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn surface(&mut self, w: &Window) -> &mut Surface {
|
||||
self.surfaces.entry(w.id()).or_insert_with(|| {
|
||||
unsafe { Surface::new(&self.context, w) }
|
||||
.expect("Failed to create a softbuffer surface")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
// NOTE: You should never do things like that, create context and drop it before
|
||||
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
||||
// ManuallyDrop to prevent destructors from running.
|
||||
//
|
||||
// A static, thread-local map of graphics contexts to open windows.
|
||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
|
||||
}
|
||||
|
||||
GC.with(|gc| {
|
||||
let size = window.inner_size();
|
||||
let (Some(width), Some(height)) =
|
||||
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface = gc
|
||||
.get_or_insert_with(|| GraphicsContext::new(window))
|
||||
.surface(window);
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xFF181818;
|
||||
|
||||
surface
|
||||
.resize(width, height)
|
||||
.expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface
|
||||
.buffer_mut()
|
||||
.expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(DARK_GRAY);
|
||||
buffer
|
||||
.present()
|
||||
.expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
|
||||
pub(super) fn fill_window(_window: &Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
@@ -2,8 +2,7 @@
|
||||
name = "run-wasm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
cargo-run-wasm = "0.2.0"
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
//! Contains traits with platform-specific methods in them.
|
||||
//!
|
||||
//! Contains the follow OS-specific modules:
|
||||
//!
|
||||
//! - `android`
|
||||
//! - `ios`
|
||||
//! - `macos`
|
||||
//! - `unix`
|
||||
//! - `windows`
|
||||
//! - `web`
|
||||
//!
|
||||
//! And the following platform-specific modules:
|
||||
//!
|
||||
//! - `run_on_demand` (available on `windows`, `unix`, `macos`, `android`)
|
||||
//! - `pump_events` (available on `windows`, `unix`, `macos`, `android`)
|
||||
//!
|
||||
//! However only the module corresponding to the platform you're compiling to will be available.
|
||||
|
||||
#[cfg(android_platform)]
|
||||
pub mod android;
|
||||
#[cfg(ios_platform)]
|
||||
pub mod ios;
|
||||
#[cfg(macos_platform)]
|
||||
pub mod macos;
|
||||
#[cfg(orbital_platform)]
|
||||
pub mod orbital;
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
pub mod startup_notify;
|
||||
#[cfg(wayland_platform)]
|
||||
pub mod wayland;
|
||||
#[cfg(wasm_platform)]
|
||||
pub mod web;
|
||||
#[cfg(windows_platform)]
|
||||
pub mod windows;
|
||||
#[cfg(x11_platform)]
|
||||
pub mod x11;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform
|
||||
))]
|
||||
pub mod run_on_demand;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform
|
||||
))]
|
||||
pub mod pump_events;
|
||||
|
||||
pub mod modifier_supplement;
|
||||
pub mod scancode;
|
||||
@@ -1,65 +0,0 @@
|
||||
use icrate::AppKit::{NSApplicationActivationPolicy, NSApplicationDelegate};
|
||||
use icrate::Foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
|
||||
use super::app_state::AppState;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct State {
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
pub(super) struct ApplicationDelegate;
|
||||
|
||||
unsafe impl ClassType for ApplicationDelegate {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitApplicationDelegate";
|
||||
}
|
||||
|
||||
impl DeclaredClass for ApplicationDelegate {
|
||||
type Ivars = State;
|
||||
}
|
||||
|
||||
unsafe impl NSObjectProtocol for ApplicationDelegate {}
|
||||
|
||||
unsafe impl NSApplicationDelegate for ApplicationDelegate {
|
||||
#[method(applicationDidFinishLaunching:)]
|
||||
fn did_finish_launching(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("applicationDidFinishLaunching:");
|
||||
AppState::launched(
|
||||
self.ivars().activation_policy,
|
||||
self.ivars().default_menu,
|
||||
self.ivars().activate_ignoring_other_apps,
|
||||
);
|
||||
}
|
||||
|
||||
#[method(applicationWillTerminate:)]
|
||||
fn will_terminate(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("applicationWillTerminate:");
|
||||
// TODO: Notify every window that it will be destroyed, like done in iOS?
|
||||
AppState::internal_exit();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl ApplicationDelegate {
|
||||
pub(super) fn new(
|
||||
mtm: MainThreadMarker,
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
) -> Id<Self> {
|
||||
let this = mtm.alloc().set_ivars(State {
|
||||
activation_policy,
|
||||
default_menu,
|
||||
activate_ignoring_other_apps,
|
||||
});
|
||||
unsafe { msg_send_id![super(this), init] }
|
||||
}
|
||||
}
|
||||
@@ -1,703 +0,0 @@
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
collections::VecDeque,
|
||||
fmt::{self, Debug},
|
||||
mem,
|
||||
rc::{Rc, Weak},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc, Arc, Mutex, MutexGuard,
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};
|
||||
use icrate::AppKit::{NSApplication, NSApplicationActivationPolicy};
|
||||
use icrate::Foundation::{is_main_thread, MainThreadMarker, NSSize};
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::{
|
||||
event::dummy_event, event_loop::PanicInfo, menu, observer::EventLoopWaker, util::Never,
|
||||
window::WinitWindow,
|
||||
};
|
||||
use crate::{
|
||||
dpi::PhysicalSize,
|
||||
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
|
||||
window::WindowId,
|
||||
};
|
||||
|
||||
static HANDLER: Lazy<Handler> = Lazy::new(Default::default);
|
||||
|
||||
impl<Never> Event<Never> {
|
||||
fn userify<T: 'static>(self) -> Event<T> {
|
||||
self.map_nonuser_event()
|
||||
// `Never` can't be constructed, so the `UserEvent` variant can't
|
||||
// be present here.
|
||||
.unwrap_or_else(|_| unreachable!())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventHandler: Debug {
|
||||
// Not sure probably it should accept Event<'static, Never>
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>);
|
||||
fn handle_user_events(&mut self);
|
||||
}
|
||||
|
||||
pub(crate) type Callback<T> = RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>)>;
|
||||
|
||||
struct EventLoopHandler<T: 'static> {
|
||||
callback: Weak<Callback<T>>,
|
||||
window_target: Rc<RootWindowTarget<T>>,
|
||||
receiver: Rc<mpsc::Receiver<T>>,
|
||||
}
|
||||
|
||||
impl<T> EventLoopHandler<T> {
|
||||
fn with_callback<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut EventLoopHandler<T>, RefMut<'_, dyn FnMut(Event<T>, &RootWindowTarget<T>)>),
|
||||
{
|
||||
// `NSApplication` and our `HANDLER` are global state and so it's possible
|
||||
// that we could get a delegate callback after the application has exit an
|
||||
// `EventLoop`. If the loop has been exit then our weak `self.callback`
|
||||
// will fail to upgrade.
|
||||
//
|
||||
// We don't want to panic or output any verbose logging if we fail to
|
||||
// upgrade the weak reference since it might be valid that the application
|
||||
// re-starts the `NSApplication` after exiting a Winit `EventLoop`
|
||||
if let Some(callback) = self.callback.upgrade() {
|
||||
let callback = callback.borrow_mut();
|
||||
(f)(self, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for EventLoopHandler<T> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter
|
||||
.debug_struct("EventLoopHandler")
|
||||
.field("window_target", &self.window_target)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventHandler for EventLoopHandler<T> {
|
||||
fn handle_nonuser_event(&mut self, event: Event<Never>) {
|
||||
self.with_callback(|this, mut callback| {
|
||||
(callback)(event.userify(), &this.window_target);
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_user_events(&mut self) {
|
||||
self.with_callback(|this, mut callback| {
|
||||
for event in this.receiver.try_iter() {
|
||||
(callback)(Event::UserEvent(event), &this.window_target);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum EventWrapper {
|
||||
StaticEvent(Event<Never>),
|
||||
ScaleFactorChanged {
|
||||
window: Id<WinitWindow>,
|
||||
suggested_size: PhysicalSize<u32>,
|
||||
scale_factor: f64,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Handler {
|
||||
stop_app_on_launch: AtomicBool,
|
||||
stop_app_before_wait: AtomicBool,
|
||||
stop_app_after_wait: AtomicBool,
|
||||
stop_app_on_redraw: AtomicBool,
|
||||
launched: AtomicBool,
|
||||
running: AtomicBool,
|
||||
in_callback: AtomicBool,
|
||||
control_flow: Mutex<ControlFlow>,
|
||||
exit: AtomicBool,
|
||||
start_time: Mutex<Option<Instant>>,
|
||||
callback: Mutex<Option<Box<dyn EventHandler>>>,
|
||||
pending_events: Mutex<VecDeque<EventWrapper>>,
|
||||
pending_redraw: Mutex<Vec<WindowId>>,
|
||||
wait_timeout: Mutex<Option<Instant>>,
|
||||
waker: Mutex<EventLoopWaker>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Handler {}
|
||||
unsafe impl Sync for Handler {}
|
||||
|
||||
impl Handler {
|
||||
fn events(&self) -> MutexGuard<'_, VecDeque<EventWrapper>> {
|
||||
self.pending_events.lock().unwrap()
|
||||
}
|
||||
|
||||
fn redraw(&self) -> MutexGuard<'_, Vec<WindowId>> {
|
||||
self.pending_redraw.lock().unwrap()
|
||||
}
|
||||
|
||||
fn waker(&self) -> MutexGuard<'_, EventLoopWaker> {
|
||||
self.waker.lock().unwrap()
|
||||
}
|
||||
|
||||
/// `true` after `ApplicationDelegate::applicationDidFinishLaunching` called
|
||||
///
|
||||
/// NB: This is global / `NSApplication` state and since the app will only
|
||||
/// be launched once but an `EventLoop` may be run more than once then only
|
||||
/// the first `EventLoop` will observe the application before it is launched.
|
||||
fn is_launched(&self) -> bool {
|
||||
self.launched.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
/// Set via `ApplicationDelegate::applicationDidFinishLaunching`
|
||||
fn set_launched(&self) {
|
||||
self.launched.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
/// `true` if an `EventLoop` is currently running
|
||||
///
|
||||
/// NB: This is global / `NSApplication` state and may persist beyond the
|
||||
/// lifetime of a running `EventLoop`.
|
||||
///
|
||||
/// # Caveat
|
||||
/// This is only intended to be called from the main thread
|
||||
fn is_running(&self) -> bool {
|
||||
self.running.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Set when an `EventLoop` starts running, after the `NSApplication` is launched
|
||||
///
|
||||
/// # Caveat
|
||||
/// This is only intended to be called from the main thread
|
||||
fn set_running(&self) {
|
||||
self.running.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits
|
||||
///
|
||||
/// Since an `EventLoop` may be run more than once we need make sure to reset the
|
||||
/// `control_flow` state back to `Poll` each time the loop exits.
|
||||
///
|
||||
/// Note: that if the `NSApplication` has been launched then that state is preserved,
|
||||
/// and we won't need to re-launch the app if subsequent EventLoops are run.
|
||||
///
|
||||
/// # Caveat
|
||||
/// This is only intended to be called from the main thread
|
||||
fn internal_exit(&self) {
|
||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
||||
// interiour mutability
|
||||
//
|
||||
// XXX: As an aside; having each individual bit of state for `Handler` be atomic or wrapped in a
|
||||
// `Mutex` for the sake of interior mutability seems a bit odd, and also a potential foot
|
||||
// gun in case the state is unwittingly accessed across threads because the fine-grained locking
|
||||
// wouldn't ensure that there's interior consistency.
|
||||
//
|
||||
// Maybe the whole thing should just be put in a static `Mutex<>` to make it clear
|
||||
// the we can mutate more than one peice of state while maintaining consistency. (though it
|
||||
// looks like there have been recuring re-entrancy issues with callback handling that might
|
||||
// make that awkward)
|
||||
self.running.store(false, Ordering::Relaxed);
|
||||
self.set_stop_app_on_redraw_requested(false);
|
||||
self.set_stop_app_before_wait(false);
|
||||
self.set_stop_app_after_wait(false);
|
||||
self.set_wait_timeout(None);
|
||||
}
|
||||
|
||||
pub fn exit(&self) {
|
||||
self.exit.store(true, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn exiting(&self) -> bool {
|
||||
self.exit.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn request_stop_app_on_launch(&self) {
|
||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
||||
// interior mutability
|
||||
self.stop_app_on_launch.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn should_stop_app_on_launch(&self) -> bool {
|
||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
||||
// interior mutability
|
||||
self.stop_app_on_launch.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn set_stop_app_before_wait(&self, stop_before_wait: bool) {
|
||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
||||
// interior mutability
|
||||
self.stop_app_before_wait
|
||||
.store(stop_before_wait, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn should_stop_app_before_wait(&self) -> bool {
|
||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
||||
// interior mutability
|
||||
self.stop_app_before_wait.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn set_stop_app_after_wait(&self, stop_after_wait: bool) {
|
||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
||||
// interior mutability
|
||||
self.stop_app_after_wait
|
||||
.store(stop_after_wait, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn set_wait_timeout(&self, new_timeout: Option<Instant>) {
|
||||
let mut timeout = self.wait_timeout.lock().unwrap();
|
||||
*timeout = new_timeout;
|
||||
}
|
||||
|
||||
pub fn wait_timeout(&self) -> Option<Instant> {
|
||||
*self.wait_timeout.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn should_stop_app_after_wait(&self) -> bool {
|
||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
||||
// interior mutability
|
||||
self.stop_app_after_wait.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn set_stop_app_on_redraw_requested(&self, stop_on_redraw: bool) {
|
||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
||||
// interior mutability
|
||||
self.stop_app_on_redraw
|
||||
.store(stop_on_redraw, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn should_stop_app_on_redraw_requested(&self) -> bool {
|
||||
// Relaxed ordering because we don't actually have multiple threads involved, we just want
|
||||
// interior mutability
|
||||
self.stop_app_on_redraw.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn set_control_flow(&self, new_control_flow: ControlFlow) {
|
||||
*self.control_flow.lock().unwrap() = new_control_flow
|
||||
}
|
||||
|
||||
fn control_flow(&self) -> ControlFlow {
|
||||
*self.control_flow.lock().unwrap()
|
||||
}
|
||||
|
||||
fn get_start_time(&self) -> Option<Instant> {
|
||||
*self.start_time.lock().unwrap()
|
||||
}
|
||||
|
||||
fn update_start_time(&self) {
|
||||
*self.start_time.lock().unwrap() = Some(Instant::now());
|
||||
}
|
||||
|
||||
fn take_events(&self) -> VecDeque<EventWrapper> {
|
||||
mem::take(&mut *self.events())
|
||||
}
|
||||
|
||||
fn should_redraw(&self) -> Vec<WindowId> {
|
||||
mem::take(&mut *self.redraw())
|
||||
}
|
||||
|
||||
fn get_in_callback(&self) -> bool {
|
||||
self.in_callback.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
fn set_in_callback(&self, in_callback: bool) {
|
||||
self.in_callback.store(in_callback, Ordering::Release);
|
||||
}
|
||||
|
||||
fn have_callback(&self) -> bool {
|
||||
self.callback.lock().unwrap().is_some()
|
||||
}
|
||||
|
||||
fn handle_nonuser_event(&self, event: Event<Never>) {
|
||||
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
|
||||
callback.handle_nonuser_event(event)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_user_events(&self) {
|
||||
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
|
||||
callback.handle_user_events();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_scale_factor_changed_event(
|
||||
&self,
|
||||
window: &WinitWindow,
|
||||
suggested_size: PhysicalSize<u32>,
|
||||
scale_factor: f64,
|
||||
) {
|
||||
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
|
||||
let new_inner_size = Arc::new(Mutex::new(suggested_size));
|
||||
let scale_factor_changed_event = Event::WindowEvent {
|
||||
window_id: WindowId(window.id()),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
|
||||
},
|
||||
};
|
||||
|
||||
callback.handle_nonuser_event(scale_factor_changed_event);
|
||||
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
let logical_size = physical_size.to_logical(scale_factor);
|
||||
let size = NSSize::new(logical_size.width, logical_size.height);
|
||||
window.setContentSize(size);
|
||||
|
||||
let resized_event = Event::WindowEvent {
|
||||
window_id: WindowId(window.id()),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
};
|
||||
callback.handle_nonuser_event(resized_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum AppState {}
|
||||
|
||||
impl AppState {
|
||||
/// Associate the application's event callback with the (global static) Handler state
|
||||
///
|
||||
/// # Safety
|
||||
/// This is ignoring the lifetime of the application callback (which may not be 'static)
|
||||
/// and can lead to undefined behaviour if the callback is not cleared before the end of
|
||||
/// its real lifetime.
|
||||
///
|
||||
/// All public APIs that take an event callback (`run`, `run_on_demand`,
|
||||
/// `pump_events`) _must_ pair a call to `set_callback` with
|
||||
/// a call to `clear_callback` before returning to avoid undefined behaviour.
|
||||
pub unsafe fn set_callback<T>(
|
||||
callback: Weak<Callback<T>>,
|
||||
window_target: Rc<RootWindowTarget<T>>,
|
||||
receiver: Rc<mpsc::Receiver<T>>,
|
||||
) {
|
||||
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
|
||||
callback,
|
||||
window_target,
|
||||
receiver,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn clear_callback() {
|
||||
HANDLER.callback.lock().unwrap().take();
|
||||
}
|
||||
|
||||
pub fn is_launched() -> bool {
|
||||
HANDLER.is_launched()
|
||||
}
|
||||
|
||||
pub fn is_running() -> bool {
|
||||
HANDLER.is_running()
|
||||
}
|
||||
|
||||
// If `pump_events` is called to progress the event loop then we bootstrap the event
|
||||
// loop via `-[NSAppplication run]` but will use `CFRunLoopRunInMode` for subsequent calls to
|
||||
// `pump_events`
|
||||
pub fn request_stop_on_launch() {
|
||||
HANDLER.request_stop_app_on_launch();
|
||||
}
|
||||
|
||||
pub fn set_stop_app_before_wait(stop_before_wait: bool) {
|
||||
HANDLER.set_stop_app_before_wait(stop_before_wait);
|
||||
}
|
||||
|
||||
pub fn set_stop_app_after_wait(stop_after_wait: bool) {
|
||||
HANDLER.set_stop_app_after_wait(stop_after_wait);
|
||||
}
|
||||
|
||||
pub fn set_wait_timeout(timeout: Option<Instant>) {
|
||||
HANDLER.set_wait_timeout(timeout);
|
||||
}
|
||||
|
||||
pub fn set_stop_app_on_redraw_requested(stop_on_redraw: bool) {
|
||||
HANDLER.set_stop_app_on_redraw_requested(stop_on_redraw);
|
||||
}
|
||||
|
||||
pub fn set_control_flow(control_flow: ControlFlow) {
|
||||
HANDLER.set_control_flow(control_flow)
|
||||
}
|
||||
|
||||
pub fn control_flow() -> ControlFlow {
|
||||
HANDLER.control_flow()
|
||||
}
|
||||
|
||||
pub fn internal_exit() {
|
||||
HANDLER.set_in_callback(true);
|
||||
HANDLER.handle_nonuser_event(Event::LoopExiting);
|
||||
HANDLER.set_in_callback(false);
|
||||
HANDLER.internal_exit();
|
||||
Self::clear_callback();
|
||||
}
|
||||
|
||||
pub fn exit() {
|
||||
HANDLER.exit()
|
||||
}
|
||||
|
||||
pub fn exiting() -> bool {
|
||||
HANDLER.exiting()
|
||||
}
|
||||
|
||||
pub fn dispatch_init_events() {
|
||||
HANDLER.set_in_callback(true);
|
||||
HANDLER.handle_nonuser_event(Event::NewEvents(StartCause::Init));
|
||||
// NB: For consistency all platforms must emit a 'resumed' event even though macOS
|
||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
||||
HANDLER.handle_nonuser_event(Event::Resumed);
|
||||
HANDLER.set_in_callback(false);
|
||||
}
|
||||
|
||||
pub fn start_running() {
|
||||
debug_assert!(HANDLER.is_launched());
|
||||
|
||||
HANDLER.set_running();
|
||||
Self::dispatch_init_events()
|
||||
}
|
||||
|
||||
pub fn launched(
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
create_default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
// We need to delay setting the activation policy and activating the app
|
||||
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
||||
// menu bar is initially unresponsive on macOS 10.15.
|
||||
app.setActivationPolicy(activation_policy);
|
||||
|
||||
window_activation_hack(&app);
|
||||
#[allow(deprecated)]
|
||||
app.activateIgnoringOtherApps(activate_ignoring_other_apps);
|
||||
|
||||
HANDLER.set_launched();
|
||||
HANDLER.waker().start();
|
||||
if create_default_menu {
|
||||
// The menubar initialization should be before the `NewEvents` event, to allow
|
||||
// overriding of the default menu even if it's created
|
||||
menu::initialize(&app);
|
||||
}
|
||||
|
||||
Self::start_running();
|
||||
|
||||
// If the application is being launched via `EventLoop::pump_events()` then we'll
|
||||
// want to stop the app once it is launched (and return to the external loop)
|
||||
//
|
||||
// In this case we still want to consider Winit's `EventLoop` to be "running",
|
||||
// so we call `start_running()` above.
|
||||
if HANDLER.should_stop_app_on_launch() {
|
||||
// Note: the original idea had been to only stop the underlying `RunLoop`
|
||||
// for the app but that didn't work as expected (`-[NSApplication run]`
|
||||
// effectively ignored the attempt to stop the RunLoop and re-started it).
|
||||
//
|
||||
// So we return from `pump_events` by stopping the application.
|
||||
Self::stop();
|
||||
}
|
||||
}
|
||||
|
||||
// Called by RunLoopObserver after finishing waiting for new events
|
||||
pub fn wakeup(panic_info: Weak<PanicInfo>) {
|
||||
let panic_info = panic_info
|
||||
.upgrade()
|
||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
||||
|
||||
// Return when in callback due to https://github.com/rust-windowing/winit/issues/1779
|
||||
if panic_info.is_panicking()
|
||||
|| HANDLER.get_in_callback()
|
||||
|| !HANDLER.have_callback()
|
||||
|| !HANDLER.is_running()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if HANDLER.should_stop_app_after_wait() {
|
||||
Self::stop();
|
||||
}
|
||||
|
||||
let start = HANDLER.get_start_time().unwrap();
|
||||
let cause = match HANDLER.control_flow() {
|
||||
ControlFlow::Poll => StartCause::Poll,
|
||||
ControlFlow::Wait => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
},
|
||||
ControlFlow::WaitUntil(requested_resume) => {
|
||||
if Instant::now() >= requested_resume {
|
||||
StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume,
|
||||
}
|
||||
} else {
|
||||
StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(requested_resume),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
HANDLER.set_in_callback(true);
|
||||
HANDLER.handle_nonuser_event(Event::NewEvents(cause));
|
||||
HANDLER.set_in_callback(false);
|
||||
}
|
||||
|
||||
// This is called from multiple threads at present
|
||||
pub fn queue_redraw(window_id: WindowId) {
|
||||
let mut pending_redraw = HANDLER.redraw();
|
||||
if !pending_redraw.contains(&window_id) {
|
||||
pending_redraw.push(window_id);
|
||||
}
|
||||
unsafe {
|
||||
let rl = CFRunLoopGetMain();
|
||||
CFRunLoopWakeUp(rl);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_redraw(window_id: WindowId) {
|
||||
// Redraw request might come out of order from the OS.
|
||||
// -> Don't go back into the callback when our callstack originates from there
|
||||
if !HANDLER.in_callback.swap(true, Ordering::AcqRel) {
|
||||
HANDLER.handle_nonuser_event(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::RedrawRequested,
|
||||
});
|
||||
HANDLER.set_in_callback(false);
|
||||
|
||||
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested events
|
||||
// as a way to ensure that `pump_events` can't block an external loop indefinitely
|
||||
if HANDLER.should_stop_app_on_redraw_requested() {
|
||||
AppState::stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_event(event: Event<Never>) {
|
||||
if !is_main_thread() {
|
||||
panic!("Event queued from different thread: {event:#?}");
|
||||
}
|
||||
HANDLER.events().push_back(EventWrapper::StaticEvent(event));
|
||||
}
|
||||
|
||||
pub fn queue_static_scale_factor_changed_event(
|
||||
window: Id<WinitWindow>,
|
||||
suggested_size: PhysicalSize<u32>,
|
||||
scale_factor: f64,
|
||||
) {
|
||||
HANDLER
|
||||
.events()
|
||||
.push_back(EventWrapper::ScaleFactorChanged {
|
||||
window,
|
||||
suggested_size,
|
||||
scale_factor,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn stop() {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
autoreleasepool(|_| {
|
||||
app.stop(None);
|
||||
// To stop event loop immediately, we need to post some event here.
|
||||
app.postEvent_atStart(&dummy_event().unwrap(), true);
|
||||
});
|
||||
}
|
||||
|
||||
// Called by RunLoopObserver before waiting for new events
|
||||
pub fn cleared(panic_info: Weak<PanicInfo>) {
|
||||
let panic_info = panic_info
|
||||
.upgrade()
|
||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
||||
|
||||
// Return when in callback due to https://github.com/rust-windowing/winit/issues/1779
|
||||
// XXX: how does it make sense that `get_in_callback()` can ever return `true` here if we're
|
||||
// about to return to the `CFRunLoop` to poll for new events?
|
||||
if panic_info.is_panicking()
|
||||
|| HANDLER.get_in_callback()
|
||||
|| !HANDLER.have_callback()
|
||||
|| !HANDLER.is_running()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLER.set_in_callback(true);
|
||||
HANDLER.handle_user_events();
|
||||
for event in HANDLER.take_events() {
|
||||
match event {
|
||||
EventWrapper::StaticEvent(event) => {
|
||||
HANDLER.handle_nonuser_event(event);
|
||||
}
|
||||
EventWrapper::ScaleFactorChanged {
|
||||
window,
|
||||
suggested_size,
|
||||
scale_factor,
|
||||
} => {
|
||||
HANDLER.handle_scale_factor_changed_event(
|
||||
&window,
|
||||
suggested_size,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for window_id in HANDLER.should_redraw() {
|
||||
HANDLER.handle_nonuser_event(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::RedrawRequested,
|
||||
});
|
||||
}
|
||||
|
||||
HANDLER.handle_nonuser_event(Event::AboutToWait);
|
||||
HANDLER.set_in_callback(false);
|
||||
|
||||
if HANDLER.exiting() {
|
||||
Self::stop();
|
||||
}
|
||||
|
||||
if HANDLER.should_stop_app_before_wait() {
|
||||
Self::stop();
|
||||
}
|
||||
HANDLER.update_start_time();
|
||||
let wait_timeout = HANDLER.wait_timeout(); // configured by pump_events
|
||||
let app_timeout = match HANDLER.control_flow() {
|
||||
ControlFlow::Wait => None,
|
||||
ControlFlow::Poll => Some(Instant::now()),
|
||||
ControlFlow::WaitUntil(instant) => Some(instant),
|
||||
};
|
||||
HANDLER
|
||||
.waker()
|
||||
.start_at(min_timeout(wait_timeout, app_timeout));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the minimum `Option<Instant>`, taking into account that `None`
|
||||
/// equates to an infinite timeout, not a zero timeout (so can't just use
|
||||
/// `Option::min`)
|
||||
fn min_timeout(a: Option<Instant>, b: Option<Instant>) -> Option<Instant> {
|
||||
a.map_or(b, |a_timeout| {
|
||||
b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))
|
||||
})
|
||||
}
|
||||
|
||||
/// A hack to make activation of multiple windows work when creating them before
|
||||
/// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`.
|
||||
///
|
||||
/// Alternative to this would be the user calling `window.set_visible(true)` in
|
||||
/// `StartCause::Init`.
|
||||
///
|
||||
/// If this becomes too bothersome to maintain, it can probably be removed
|
||||
/// without too much damage.
|
||||
fn window_activation_hack(app: &NSApplication) {
|
||||
// TODO: Proper ordering of the windows
|
||||
app.windows().into_iter().for_each(|window| {
|
||||
// Call `makeKeyAndOrderFront` if it was called on the window in `WinitWindow::new`
|
||||
// This way we preserve the user's desired initial visiblity status
|
||||
// TODO: Also filter on the type/"level" of the window, and maybe other things?
|
||||
if window.isVisible() {
|
||||
trace!("Activating visible window");
|
||||
window.makeKeyAndOrderFront(None);
|
||||
} else {
|
||||
trace!("Skipping activating invisible window");
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
use core_graphics::display::CGDisplay;
|
||||
use icrate::Foundation::{CGFloat, NSNotFound, NSPoint, NSRange, NSRect, NSUInteger};
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
|
||||
// Replace with `!` once stable
|
||||
#[derive(Debug)]
|
||||
pub enum Never {}
|
||||
|
||||
pub const EMPTY_RANGE: NSRange = NSRange {
|
||||
location: NSNotFound as NSUInteger,
|
||||
length: 0,
|
||||
};
|
||||
|
||||
macro_rules! trace_scope {
|
||||
($s:literal) => {
|
||||
let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s);
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) struct TraceGuard {
|
||||
module_path: &'static str,
|
||||
called_from_fn: &'static str,
|
||||
}
|
||||
|
||||
impl TraceGuard {
|
||||
#[inline]
|
||||
pub(crate) fn new(module_path: &'static str, called_from_fn: &'static str) -> Self {
|
||||
trace!(target: module_path, "Triggered `{}`", called_from_fn);
|
||||
Self {
|
||||
module_path,
|
||||
called_from_fn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TraceGuard {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
trace!(target: self.module_path, "Completed `{}`", self.called_from_fn);
|
||||
}
|
||||
}
|
||||
|
||||
// For consistency with other platforms, this will...
|
||||
// 1. translate the bottom-left window corner into the top-left window corner
|
||||
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
|
||||
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) as f64
|
||||
}
|
||||
|
||||
/// Converts from winit screen-coordinates to macOS screen-coordinates.
|
||||
/// Winit: top-left is (0, 0) and y increasing downwards
|
||||
/// macOS: bottom-left is (0, 0) and y increasing upwards
|
||||
pub fn window_position(position: LogicalPosition<f64>) -> NSPoint {
|
||||
NSPoint::new(
|
||||
position.x as CGFloat,
|
||||
CGDisplay::main().pixels_high() as CGFloat - position.y as CGFloat,
|
||||
)
|
||||
}
|
||||
@@ -1,483 +0,0 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::cell::Cell;
|
||||
use std::ptr;
|
||||
|
||||
use icrate::AppKit::{
|
||||
NSApplicationPresentationFullScreen, NSApplicationPresentationHideDock,
|
||||
NSApplicationPresentationHideMenuBar, NSApplicationPresentationOptions, NSDraggingDestination,
|
||||
NSFilenamesPboardType, NSPasteboard, NSWindowDelegate, NSWindowOcclusionStateVisible,
|
||||
};
|
||||
use icrate::Foundation::{MainThreadMarker, NSArray, NSObject, NSObjectProtocol, NSSize, NSString};
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::runtime::{AnyObject, ProtocolObject};
|
||||
use objc2::{
|
||||
class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
|
||||
};
|
||||
|
||||
use super::{
|
||||
app_state::AppState,
|
||||
util,
|
||||
window::{get_ns_theme, WinitWindow},
|
||||
Fullscreen,
|
||||
};
|
||||
use crate::{
|
||||
dpi::{LogicalPosition, LogicalSize},
|
||||
event::{Event, WindowEvent},
|
||||
window::WindowId,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct State {
|
||||
window: Id<WinitWindow>,
|
||||
|
||||
// This is set when WindowBuilder::with_fullscreen was set,
|
||||
// see comments of `window_did_fail_to_enter_fullscreen`
|
||||
initial_fullscreen: Cell<bool>,
|
||||
|
||||
// During `windowDidResize`, we use this to only send Moved if the position changed.
|
||||
previous_position: Cell<Option<(f64, f64)>>,
|
||||
|
||||
// Used to prevent redundant events.
|
||||
previous_scale_factor: Cell<f64>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
pub(crate) struct WinitWindowDelegate;
|
||||
|
||||
unsafe impl ClassType for WinitWindowDelegate {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitWindowDelegate";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitWindowDelegate {
|
||||
type Ivars = State;
|
||||
}
|
||||
|
||||
unsafe impl NSObjectProtocol for WinitWindowDelegate {}
|
||||
|
||||
unsafe impl NSWindowDelegate for WinitWindowDelegate {
|
||||
#[method(windowShouldClose:)]
|
||||
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
|
||||
trace_scope!("windowShouldClose:");
|
||||
self.queue_event(WindowEvent::CloseRequested);
|
||||
false
|
||||
}
|
||||
|
||||
#[method(windowWillClose:)]
|
||||
fn window_will_close(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillClose:");
|
||||
// `setDelegate:` retains the previous value and then autoreleases it
|
||||
autoreleasepool(|_| {
|
||||
// Since El Capitan, we need to be careful that delegate methods can't
|
||||
// be called after the window closes.
|
||||
self.ivars().window.setDelegate(None);
|
||||
});
|
||||
self.queue_event(WindowEvent::Destroyed);
|
||||
}
|
||||
|
||||
#[method(windowDidResize:)]
|
||||
fn window_did_resize(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidResize:");
|
||||
// NOTE: WindowEvent::Resized is reported in frameDidChange.
|
||||
self.emit_move_event();
|
||||
}
|
||||
|
||||
#[method(windowWillStartLiveResize:)]
|
||||
fn window_will_start_live_resize(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillStartLiveResize:");
|
||||
|
||||
let increments = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_enter_fullscreen")
|
||||
.resize_increments;
|
||||
self.ivars().window.set_resize_increments_inner(increments);
|
||||
}
|
||||
|
||||
#[method(windowDidEndLiveResize:)]
|
||||
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEndLiveResize:");
|
||||
self.ivars()
|
||||
.window
|
||||
.set_resize_increments_inner(NSSize::new(1., 1.));
|
||||
}
|
||||
|
||||
// This won't be triggered if the move was part of a resize.
|
||||
#[method(windowDidMove:)]
|
||||
fn window_did_move(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidMove:");
|
||||
self.emit_move_event();
|
||||
}
|
||||
|
||||
#[method(windowDidChangeBackingProperties:)]
|
||||
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeBackingProperties:");
|
||||
self.queue_static_scale_factor_changed_event();
|
||||
}
|
||||
|
||||
#[method(windowDidBecomeKey:)]
|
||||
fn window_did_become_key(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidBecomeKey:");
|
||||
// TODO: center the cursor if the window had mouse grab when it
|
||||
// lost focus
|
||||
self.queue_event(WindowEvent::Focused(true));
|
||||
}
|
||||
|
||||
#[method(windowDidResignKey:)]
|
||||
fn window_did_resign_key(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidResignKey:");
|
||||
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
|
||||
// NSWindowDelegate will receive a didResignKey event despite no event
|
||||
// being received when the modifiers are released. This is because
|
||||
// flagsChanged events are received by the NSView instead of the
|
||||
// NSWindowDelegate, and as a result a tracked modifiers state can quite
|
||||
// easily fall out of synchrony with reality. This requires us to emit
|
||||
// a synthetic ModifiersChanged event when we lose focus.
|
||||
self.ivars().window.view().reset_modifiers();
|
||||
|
||||
self.queue_event(WindowEvent::Focused(false));
|
||||
}
|
||||
|
||||
/// Invoked when before enter fullscreen
|
||||
#[method(windowWillEnterFullScreen:)]
|
||||
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillEnterFullScreen:");
|
||||
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_enter_fullscreen");
|
||||
shared_state.maximized = self.ivars().window.is_zoomed();
|
||||
let fullscreen = shared_state.fullscreen.as_ref();
|
||||
match fullscreen {
|
||||
// Exclusive mode sets the state in `set_fullscreen` as the user
|
||||
// can't enter exclusive mode by other means (like the
|
||||
// fullscreen button on the window decorations)
|
||||
Some(Fullscreen::Exclusive(_)) => (),
|
||||
// `window_will_enter_fullscreen` was triggered and we're already
|
||||
// in fullscreen, so we must've reached here by `set_fullscreen`
|
||||
// as it updates the state
|
||||
Some(Fullscreen::Borderless(_)) => (),
|
||||
// Otherwise, we must've reached fullscreen by the user clicking
|
||||
// on the green fullscreen button. Update state!
|
||||
None => {
|
||||
let current_monitor = self.ivars().window.current_monitor_inner();
|
||||
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
|
||||
}
|
||||
}
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
/// Invoked when before exit fullscreen
|
||||
#[method(windowWillExitFullScreen:)]
|
||||
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillExitFullScreen:");
|
||||
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
#[method(window:willUseFullScreenPresentationOptions:)]
|
||||
fn window_will_use_fullscreen_presentation_options(
|
||||
&self,
|
||||
_: Option<&AnyObject>,
|
||||
proposed_options: NSApplicationPresentationOptions,
|
||||
) -> NSApplicationPresentationOptions {
|
||||
trace_scope!("window:willUseFullScreenPresentationOptions:");
|
||||
// Generally, games will want to disable the menu bar and the dock. Ideally,
|
||||
// this would be configurable by the user. Unfortunately because of our
|
||||
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
|
||||
// placed on top of the menu bar in exclusive fullscreen mode. This looks
|
||||
// broken so we always disable the menu bar in exclusive fullscreen. We may
|
||||
// still want to make this configurable for borderless fullscreen. Right now
|
||||
// we don't, for consistency. If we do, it should be documented that the
|
||||
// user-provided options are ignored in exclusive fullscreen.
|
||||
let mut options = proposed_options;
|
||||
let shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_use_fullscreen_presentation_options");
|
||||
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
|
||||
options = NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationHideMenuBar;
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
|
||||
/// Invoked when entered fullscreen
|
||||
#[method(windowDidEnterFullScreen:)]
|
||||
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEnterFullScreen:");
|
||||
self.ivars().initial_fullscreen.set(false);
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.ivars().window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when exited fullscreen
|
||||
#[method(windowDidExitFullScreen:)]
|
||||
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidExitFullScreen:");
|
||||
|
||||
self.ivars().window.restore_state_from_fullscreen();
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.ivars().window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when fail to enter fullscreen
|
||||
///
|
||||
/// When this window launch from a fullscreen app (e.g. launch from VS Code
|
||||
/// terminal), it creates a new virtual destkop and a transition animation.
|
||||
/// This animation takes one second and cannot be disable without
|
||||
/// elevated privileges. In this animation time, all toggleFullscreen events
|
||||
/// will be failed. In this implementation, we will try again by using
|
||||
/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen.
|
||||
/// It should be fine as we only do this at initialzation (i.e with_fullscreen
|
||||
/// was set).
|
||||
///
|
||||
/// From Apple doc:
|
||||
/// In some cases, the transition to enter full-screen mode can fail,
|
||||
/// due to being in the midst of handling some other animation or user gesture.
|
||||
/// This method indicates that there was an error, and you should clean up any
|
||||
/// work you may have done to prepare to enter full-screen mode.
|
||||
#[method(windowDidFailToEnterFullScreen:)]
|
||||
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidFailToEnterFullScreen:");
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_fail_to_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
shared_state.target_fullscreen = None;
|
||||
if self.ivars().initial_fullscreen.get() {
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
&*self.ivars().window,
|
||||
performSelector: sel!(toggleFullScreen:),
|
||||
withObject: ptr::null::<AnyObject>(),
|
||||
afterDelay: 0.5,
|
||||
];
|
||||
};
|
||||
} else {
|
||||
self.ivars().window.restore_state_from_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when the occlusion state of the window changes
|
||||
#[method(windowDidChangeOcclusionState:)]
|
||||
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeOcclusionState:");
|
||||
let visible = self.ivars().window.occlusionState() & NSWindowOcclusionStateVisible
|
||||
== NSWindowOcclusionStateVisible;
|
||||
self.queue_event(WindowEvent::Occluded(!visible));
|
||||
}
|
||||
|
||||
#[method(windowDidChangeScreen:)]
|
||||
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeScreen:");
|
||||
let is_simple_fullscreen = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_change_screen")
|
||||
.is_simple_fullscreen;
|
||||
if is_simple_fullscreen {
|
||||
if let Some(screen) = self.ivars().window.screen() {
|
||||
self.ivars().window.setFrame_display(screen.frame(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSDraggingDestination for WinitWindowDelegate {
|
||||
/// Invoked when the dragged image enters destination bounds or frame
|
||||
#[method(draggingEntered:)]
|
||||
fn dragging_entered(&self, sender: &NSObject) -> bool {
|
||||
trace_scope!("draggingEntered:");
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap();
|
||||
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
let path = PathBuf::from(file.to_string());
|
||||
self.queue_event(WindowEvent::HoveredFile(path));
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked when the image is released
|
||||
#[method(prepareForDragOperation:)]
|
||||
fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool {
|
||||
trace_scope!("prepareForDragOperation:");
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked after the released image has been removed from the screen
|
||||
#[method(performDragOperation:)]
|
||||
fn perform_drag_operation(&self, sender: &NSObject) -> bool {
|
||||
trace_scope!("performDragOperation:");
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap();
|
||||
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
let path = PathBuf::from(file.to_string());
|
||||
self.queue_event(WindowEvent::DroppedFile(path));
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked when the dragging operation is complete
|
||||
#[method(concludeDragOperation:)]
|
||||
fn conclude_drag_operation(&self, _sender: Option<&NSObject>) {
|
||||
trace_scope!("concludeDragOperation:");
|
||||
}
|
||||
|
||||
/// Invoked when the dragging operation is cancelled
|
||||
#[method(draggingExited:)]
|
||||
fn dragging_exited(&self, _sender: Option<&NSObject>) {
|
||||
trace_scope!("draggingExited:");
|
||||
self.queue_event(WindowEvent::HoveredFileCancelled);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl WinitWindowDelegate {
|
||||
// Observe theme change
|
||||
#[method(effectiveAppearanceDidChange:)]
|
||||
fn effective_appearance_did_change(&self, sender: Option<&AnyObject>) {
|
||||
trace_scope!("Triggered `effectiveAppearanceDidChange:`");
|
||||
unsafe {
|
||||
msg_send![
|
||||
self,
|
||||
performSelectorOnMainThread: sel!(effectiveAppearanceDidChangedOnMainThread:),
|
||||
withObject: sender,
|
||||
waitUntilDone: false,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[method(effectiveAppearanceDidChangedOnMainThread:)]
|
||||
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let theme = get_ns_theme(mtm);
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("effective_appearance_did_change");
|
||||
let current_theme = shared_state.current_theme;
|
||||
shared_state.current_theme = Some(theme);
|
||||
drop(shared_state);
|
||||
if current_theme != Some(theme) {
|
||||
self.queue_event(WindowEvent::ThemeChanged(theme));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitWindowDelegate {
|
||||
pub fn new(window: &WinitWindow, initial_fullscreen: bool) -> Id<Self> {
|
||||
let mtm = MainThreadMarker::from(window);
|
||||
let scale_factor = window.scale_factor();
|
||||
let this = mtm.alloc().set_ivars(State {
|
||||
window: window.retain(),
|
||||
initial_fullscreen: Cell::new(initial_fullscreen),
|
||||
previous_position: Cell::new(None),
|
||||
previous_scale_factor: Cell::new(scale_factor),
|
||||
});
|
||||
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
|
||||
if scale_factor != 1.0 {
|
||||
this.queue_static_scale_factor_changed_event();
|
||||
}
|
||||
window.setDelegate(Some(ProtocolObject::from_ref(&*this)));
|
||||
|
||||
// Enable theme change event
|
||||
let notification_center: Id<AnyObject> =
|
||||
unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] };
|
||||
let notification_name = NSString::from_str("AppleInterfaceThemeChangedNotification");
|
||||
let _: () = unsafe {
|
||||
msg_send![
|
||||
¬ification_center,
|
||||
addObserver: &*this
|
||||
selector: sel!(effectiveAppearanceDidChange:)
|
||||
name: &*notification_name
|
||||
object: ptr::null::<AnyObject>()
|
||||
]
|
||||
};
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
||||
let event = Event::WindowEvent {
|
||||
window_id: WindowId(self.ivars().window.id()),
|
||||
event,
|
||||
};
|
||||
AppState::queue_event(event);
|
||||
}
|
||||
|
||||
fn queue_static_scale_factor_changed_event(&self) {
|
||||
let scale_factor = self.ivars().window.scale_factor();
|
||||
if scale_factor == self.ivars().previous_scale_factor.get() {
|
||||
return;
|
||||
};
|
||||
|
||||
self.ivars().previous_scale_factor.set(scale_factor);
|
||||
let suggested_size = self.view_size();
|
||||
AppState::queue_static_scale_factor_changed_event(
|
||||
self.ivars().window.clone(),
|
||||
suggested_size.to_physical(scale_factor),
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
|
||||
fn emit_move_event(&self) {
|
||||
let rect = self.ivars().window.frame();
|
||||
let x = rect.origin.x as f64;
|
||||
let y = util::bottom_left_to_top_left(rect);
|
||||
if self.ivars().previous_position.get() != Some((x, y)) {
|
||||
self.ivars().previous_position.set(Some((x, y)));
|
||||
let scale_factor = self.ivars().window.scale_factor();
|
||||
let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
|
||||
self.queue_event(WindowEvent::Moved(physical_pos));
|
||||
}
|
||||
}
|
||||
|
||||
fn view_size(&self) -> LogicalSize<f64> {
|
||||
let size = self.ivars().window.contentView().unwrap().frame().size;
|
||||
LogicalSize::new(size.width as f64, size.height as f64)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
mod channel;
|
||||
mod dispatcher;
|
||||
mod waker;
|
||||
mod wrapper;
|
||||
|
||||
pub use self::channel::{channel, AsyncReceiver, AsyncSender};
|
||||
pub use self::dispatcher::{DispatchRunner, Dispatcher};
|
||||
pub use self::waker::{Waker, WakerSpawner};
|
||||
use self::wrapper::Wrapper;
|
||||
@@ -1,579 +0,0 @@
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
future,
|
||||
hash::{Hash, Hasher},
|
||||
mem,
|
||||
ops::DerefMut,
|
||||
rc::{self, Rc},
|
||||
sync::{self, Arc},
|
||||
task::{Poll, Waker},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cursor::{BadImage, CursorImage},
|
||||
platform_impl::platform::r#async,
|
||||
};
|
||||
use cursor_icon::CursorIcon;
|
||||
use once_cell::sync::Lazy;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{
|
||||
Blob, Document, HtmlCanvasElement, HtmlImageElement, ImageBitmap, ImageBitmapOptions,
|
||||
ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window,
|
||||
};
|
||||
|
||||
use self::thread_safe::ThreadSafe;
|
||||
|
||||
use super::{backend::Style, r#async::AsyncSender, EventLoopWindowTarget};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum CustomCursorBuilder {
|
||||
Image(CursorImage),
|
||||
Url {
|
||||
url: String,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
},
|
||||
}
|
||||
|
||||
impl CustomCursorBuilder {
|
||||
pub fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<CustomCursorBuilder, BadImage> {
|
||||
Ok(CustomCursorBuilder::Image(CursorImage::from_rgba(
|
||||
rgba, width, height, hotspot_x, hotspot_y,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CustomCursor(Arc<Inner>);
|
||||
|
||||
impl Hash for CustomCursor {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
Arc::as_ptr(&self.0).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CustomCursor {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for CustomCursor {}
|
||||
|
||||
impl CustomCursor {
|
||||
pub(crate) fn build<T>(
|
||||
builder: CustomCursorBuilder,
|
||||
window_target: &EventLoopWindowTarget<T>,
|
||||
) -> Self {
|
||||
Lazy::force(&DROP_HANDLER);
|
||||
|
||||
Self(match builder {
|
||||
CustomCursorBuilder::Image(image) => ImageState::from_rgba(
|
||||
window_target.runner.window(),
|
||||
window_target.runner.document().clone(),
|
||||
&image,
|
||||
),
|
||||
CustomCursorBuilder::Url {
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
} => ImageState::from_url(url, hotspot_x, hotspot_y),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner(Option<ThreadSafe<RefCell<ImageState>>>);
|
||||
|
||||
static DROP_HANDLER: Lazy<AsyncSender<ThreadSafe<RefCell<ImageState>>>> = Lazy::new(|| {
|
||||
let (sender, receiver) = r#async::channel();
|
||||
wasm_bindgen_futures::spawn_local(async move { while receiver.next().await.is_ok() {} });
|
||||
|
||||
sender
|
||||
});
|
||||
|
||||
impl Inner {
|
||||
fn new() -> Arc<Self> {
|
||||
Arc::new(Inner(Some(ThreadSafe::new(RefCell::new(
|
||||
ImageState::Loading(None),
|
||||
)))))
|
||||
}
|
||||
|
||||
fn get(&self) -> &RefCell<ImageState> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.expect("value has accidently already been dropped")
|
||||
.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
let value = self
|
||||
.0
|
||||
.take()
|
||||
.expect("value has accidently already been dropped");
|
||||
|
||||
if !value.in_origin_thread() {
|
||||
DROP_HANDLER
|
||||
.send(value)
|
||||
.expect("sender dropped in main thread")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CursorState(Rc<RefCell<State>>);
|
||||
|
||||
impl CursorState {
|
||||
pub fn new(style: Style) -> Self {
|
||||
Self(Rc::new(RefCell::new(State {
|
||||
style,
|
||||
visible: true,
|
||||
cursor: SelectedCursor::default(),
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
let mut this = self.0.borrow_mut();
|
||||
|
||||
if let SelectedCursor::ImageLoading { state, .. } = &this.cursor {
|
||||
if let ImageState::Loading(state) = state.get().borrow_mut().deref_mut() {
|
||||
state.take();
|
||||
}
|
||||
}
|
||||
|
||||
this.cursor = SelectedCursor::Named(cursor);
|
||||
this.set_style();
|
||||
}
|
||||
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
let mut this = self.0.borrow_mut();
|
||||
|
||||
match cursor.0.get().borrow_mut().deref_mut() {
|
||||
ImageState::Loading(state) => {
|
||||
this.cursor = SelectedCursor::ImageLoading {
|
||||
state: cursor.0.clone(),
|
||||
previous: mem::take(&mut this.cursor).into(),
|
||||
};
|
||||
*state = Some(Rc::downgrade(&self.0));
|
||||
}
|
||||
ImageState::Failed => log::error!("tried to load invalid cursor"),
|
||||
ImageState::Ready(image) => {
|
||||
this.cursor = SelectedCursor::ImageReady(image.clone());
|
||||
this.set_style();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
if !visible && state.visible {
|
||||
state.visible = false;
|
||||
state.style.set("cursor", "none");
|
||||
} else if visible && !state.visible {
|
||||
state.visible = true;
|
||||
state.set_style();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
style: Style,
|
||||
visible: bool,
|
||||
cursor: SelectedCursor,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn set_style(&self) {
|
||||
if self.visible {
|
||||
let value = match &self.cursor {
|
||||
SelectedCursor::Named(icon) => icon.name(),
|
||||
SelectedCursor::ImageLoading { previous, .. } => previous.style(),
|
||||
SelectedCursor::ImageReady(image) => &image.style,
|
||||
};
|
||||
|
||||
self.style.set("cursor", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SelectedCursor {
|
||||
Named(CursorIcon),
|
||||
ImageLoading {
|
||||
state: Arc<Inner>,
|
||||
previous: Previous,
|
||||
},
|
||||
ImageReady(Rc<Image>),
|
||||
}
|
||||
|
||||
impl Default for SelectedCursor {
|
||||
fn default() -> Self {
|
||||
Self::Named(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Previous> for SelectedCursor {
|
||||
fn from(previous: Previous) -> Self {
|
||||
match previous {
|
||||
Previous::Named(icon) => Self::Named(icon),
|
||||
Previous::Image(image) => Self::ImageReady(image),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Previous {
|
||||
Named(CursorIcon),
|
||||
Image(Rc<Image>),
|
||||
}
|
||||
|
||||
impl Previous {
|
||||
fn style(&self) -> &str {
|
||||
match self {
|
||||
Previous::Named(icon) => icon.name(),
|
||||
Previous::Image(image) => &image.style,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SelectedCursor> for Previous {
|
||||
fn from(value: SelectedCursor) -> Self {
|
||||
match value {
|
||||
SelectedCursor::Named(icon) => Self::Named(icon),
|
||||
SelectedCursor::ImageLoading { previous, .. } => previous,
|
||||
SelectedCursor::ImageReady(image) => Self::Image(image),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ImageState {
|
||||
Loading(Option<rc::Weak<RefCell<State>>>),
|
||||
Failed,
|
||||
Ready(Rc<Image>),
|
||||
}
|
||||
|
||||
impl ImageState {
|
||||
fn from_rgba(window: &Window, document: Document, image: &CursorImage) -> Arc<Inner> {
|
||||
// 1. Create an `ImageData` from the RGBA data.
|
||||
// 2. Create an `ImageBitmap` from the `ImageData`.
|
||||
// 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
|
||||
// 4. Create a `Blob` from the `HTMLCanvasElement`.
|
||||
// 5. Create an object URL from the `Blob`.
|
||||
// 6. Decode the image on an `HTMLImageElement` from the URL.
|
||||
// 7. Change the `CursorState` if queued.
|
||||
|
||||
// 1. Create an `ImageData` from the RGBA data.
|
||||
// Adapted from https://github.com/rust-windowing/softbuffer/blob/ab7688e2ed2e2eca51b3c4e1863a5bd7fe85800e/src/web.rs#L196-L223
|
||||
#[cfg(target_feature = "atomics")]
|
||||
// Can't share `SharedArrayBuffer` with `ImageData`.
|
||||
let result = {
|
||||
use js_sys::{Uint8Array, Uint8ClampedArray};
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = ImageData)]
|
||||
type ImageDataExt;
|
||||
#[wasm_bindgen(catch, constructor, js_class = ImageData)]
|
||||
fn new(array: Uint8ClampedArray, sw: u32) -> Result<ImageDataExt, JsValue>;
|
||||
}
|
||||
|
||||
let array = Uint8Array::new_with_length(image.rgba.len() as u32);
|
||||
array.copy_from(&image.rgba);
|
||||
let array = Uint8ClampedArray::new(&array);
|
||||
ImageDataExt::new(array, image.width as u32)
|
||||
.map(JsValue::from)
|
||||
.map(ImageData::unchecked_from_js)
|
||||
};
|
||||
#[cfg(not(target_feature = "atomics"))]
|
||||
let result = ImageData::new_with_u8_clamped_array(
|
||||
wasm_bindgen::Clamped(&image.rgba),
|
||||
image.width as u32,
|
||||
);
|
||||
let image_data = result.expect("found wrong image size");
|
||||
|
||||
// 2. Create an `ImageBitmap` from the `ImageData`.
|
||||
//
|
||||
// We call `createImageBitmap()` before spawning the future,
|
||||
// to not have to clone the image buffer.
|
||||
let mut options = ImageBitmapOptions::new();
|
||||
options.premultiply_alpha(PremultiplyAlpha::None);
|
||||
let bitmap = JsFuture::from(
|
||||
window
|
||||
.create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options)
|
||||
.expect("unexpected exception in `createImageBitmap()`"),
|
||||
);
|
||||
|
||||
let this = Inner::new();
|
||||
|
||||
wasm_bindgen_futures::spawn_local({
|
||||
let weak = Arc::downgrade(&this);
|
||||
let CursorImage {
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
..
|
||||
} = *image;
|
||||
async move {
|
||||
// Keep checking if all references are dropped between every `await` call.
|
||||
if weak.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let bitmap: ImageBitmap = bitmap
|
||||
.await
|
||||
.expect("found invalid state in `ImageData`")
|
||||
.unchecked_into();
|
||||
|
||||
if weak.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let canvas: HtmlCanvasElement = document
|
||||
.create_element("canvas")
|
||||
.expect("invalid tag name")
|
||||
.unchecked_into();
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
canvas.set_width(width as u32);
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
canvas.set_height(height as u32);
|
||||
|
||||
// 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
|
||||
let context: ImageBitmapRenderingContext = canvas
|
||||
.get_context("bitmaprenderer")
|
||||
.expect("unexpected exception in `HTMLCanvasElement.getContext()`")
|
||||
.expect("`bitmaprenderer` context unsupported")
|
||||
.unchecked_into();
|
||||
context.transfer_from_image_bitmap(&bitmap);
|
||||
|
||||
// 4. Create a `Blob` from the `HTMLCanvasElement`.
|
||||
//
|
||||
// To keep the `Closure` alive until `HTMLCanvasElement.toBlob()` is done,
|
||||
// we do the whole `Waker` strategy. Commonly on `Drop` the callback is aborted,
|
||||
// but it would increase complexity and isn't possible in this case.
|
||||
// Keep in mind that `HTMLCanvasElement.toBlob()` can call the callback immediately.
|
||||
let value = Rc::new(RefCell::new(None));
|
||||
let waker = Rc::new(RefCell::<Option<Waker>>::new(None));
|
||||
let callback = Closure::once({
|
||||
let value = value.clone();
|
||||
let waker = waker.clone();
|
||||
move |blob: Option<Blob>| {
|
||||
*value.borrow_mut() = Some(blob);
|
||||
if let Some(waker) = waker.borrow_mut().take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
});
|
||||
canvas
|
||||
.to_blob(callback.as_ref().unchecked_ref())
|
||||
.expect("failed with `SecurityError` despite only source coming from memory");
|
||||
let blob = future::poll_fn(|cx| {
|
||||
if let Some(blob) = value.borrow_mut().take() {
|
||||
Poll::Ready(blob)
|
||||
} else {
|
||||
*waker.borrow_mut() = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
let url = {
|
||||
let Some(this) = weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let mut this = this.get().borrow_mut();
|
||||
|
||||
let Some(blob) = blob else {
|
||||
log::error!("creating custom cursor failed");
|
||||
let ImageState::Loading(state) = this.deref_mut() else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
let state = state.take();
|
||||
*this = ImageState::Failed;
|
||||
|
||||
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
|
||||
let mut state = state.borrow_mut();
|
||||
let SelectedCursor::ImageLoading { previous, .. } =
|
||||
mem::take(&mut state.cursor)
|
||||
else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
state.cursor = previous.into();
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
// 5. Create an object URL from the `Blob`.
|
||||
Url::create_object_url_with_blob(&blob)
|
||||
.expect("unexpected exception in `URL.createObjectURL()`")
|
||||
};
|
||||
|
||||
Self::decode(weak, url, true, hotspot_x, hotspot_y).await;
|
||||
}
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Arc<Inner> {
|
||||
let this = Inner::new();
|
||||
wasm_bindgen_futures::spawn_local(Self::decode(
|
||||
Arc::downgrade(&this),
|
||||
url,
|
||||
false,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
async fn decode(
|
||||
weak: sync::Weak<Inner>,
|
||||
url: String,
|
||||
object: bool,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) {
|
||||
if weak.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Decode the image on an `HTMLImageElement` from the URL.
|
||||
let image =
|
||||
HtmlImageElement::new().expect("unexpected exception in `new HtmlImageElement`");
|
||||
image.set_src(&url);
|
||||
let result = JsFuture::from(image.decode()).await;
|
||||
|
||||
let Some(this) = weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let mut this = this.get().borrow_mut();
|
||||
|
||||
let ImageState::Loading(state) = this.deref_mut() else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
let state = state.take();
|
||||
|
||||
if let Err(error) = result {
|
||||
log::error!("creating custom cursor failed: {error:?}");
|
||||
*this = ImageState::Failed;
|
||||
|
||||
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
|
||||
let mut state = state.borrow_mut();
|
||||
let SelectedCursor::ImageLoading { previous, .. } = mem::take(&mut state.cursor)
|
||||
else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
state.cursor = previous.into();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let image = Image::new(url, object, image, hotspot_x, hotspot_y);
|
||||
|
||||
// 7. Change the `CursorState` if queued.
|
||||
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
|
||||
let mut state = state.borrow_mut();
|
||||
state.cursor = SelectedCursor::ImageReady(image.clone());
|
||||
state.set_style();
|
||||
}
|
||||
|
||||
*this = ImageState::Ready(image);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Image {
|
||||
style: String,
|
||||
url: String,
|
||||
object: bool,
|
||||
_image: HtmlImageElement,
|
||||
}
|
||||
|
||||
impl Drop for Image {
|
||||
fn drop(&mut self) {
|
||||
if self.object {
|
||||
Url::revoke_object_url(&self.url)
|
||||
.expect("unexpected exception in `URL.revokeObjectURL()`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Image {
|
||||
fn new(
|
||||
url: String,
|
||||
object: bool,
|
||||
image: HtmlImageElement,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Rc<Self> {
|
||||
let style = format!("url({url}) {hotspot_x} {hotspot_y}, auto");
|
||||
|
||||
Rc::new(Self {
|
||||
style,
|
||||
url,
|
||||
object,
|
||||
_image: image,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
mod thread_safe {
|
||||
use std::mem;
|
||||
use std::thread::{self, ThreadId};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadSafe<T> {
|
||||
origin_thread: ThreadId,
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl<T> ThreadSafe<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
origin_thread: thread::current().id(),
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &T {
|
||||
if self.origin_thread == thread::current().id() {
|
||||
&self.value
|
||||
} else {
|
||||
panic!("value not accessible outside its origin thread")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_origin_thread(&self) -> bool {
|
||||
self.origin_thread == thread::current().id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for ThreadSafe<T> {
|
||||
fn drop(&mut self) {
|
||||
if mem::needs_drop::<T>() && self.origin_thread != thread::current().id() {
|
||||
panic!("value can't be dropped outside its origin thread")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for ThreadSafe<T> {}
|
||||
unsafe impl<T> Sync for ThreadSafe<T> {}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(pub i32);
|
||||
|
||||
impl DeviceId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
@@ -1,522 +0,0 @@
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use crate::keyboard::{Key, KeyCode, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub(crate) struct KeyEventExtra;
|
||||
|
||||
impl Key {
|
||||
pub(crate) fn from_key_attribute_value(kav: &str) -> Self {
|
||||
Key::Named(match kav {
|
||||
"Unidentified" => return Key::Unidentified(NativeKey::Web(SmolStr::new(kav))),
|
||||
"Dead" => return Key::Dead(None),
|
||||
"Alt" => NamedKey::Alt,
|
||||
"AltGraph" => NamedKey::AltGraph,
|
||||
"CapsLock" => NamedKey::CapsLock,
|
||||
"Control" => NamedKey::Control,
|
||||
"Fn" => NamedKey::Fn,
|
||||
"FnLock" => NamedKey::FnLock,
|
||||
"NumLock" => NamedKey::NumLock,
|
||||
"ScrollLock" => NamedKey::ScrollLock,
|
||||
"Shift" => NamedKey::Shift,
|
||||
"Symbol" => NamedKey::Symbol,
|
||||
"SymbolLock" => NamedKey::SymbolLock,
|
||||
"Hyper" => NamedKey::Hyper,
|
||||
"Meta" => NamedKey::Super,
|
||||
"Enter" => NamedKey::Enter,
|
||||
"Tab" => NamedKey::Tab,
|
||||
" " => NamedKey::Space,
|
||||
"ArrowDown" => NamedKey::ArrowDown,
|
||||
"ArrowLeft" => NamedKey::ArrowLeft,
|
||||
"ArrowRight" => NamedKey::ArrowRight,
|
||||
"ArrowUp" => NamedKey::ArrowUp,
|
||||
"End" => NamedKey::End,
|
||||
"Home" => NamedKey::Home,
|
||||
"PageDown" => NamedKey::PageDown,
|
||||
"PageUp" => NamedKey::PageUp,
|
||||
"Backspace" => NamedKey::Backspace,
|
||||
"Clear" => NamedKey::Clear,
|
||||
"Copy" => NamedKey::Copy,
|
||||
"CrSel" => NamedKey::CrSel,
|
||||
"Cut" => NamedKey::Cut,
|
||||
"Delete" => NamedKey::Delete,
|
||||
"EraseEof" => NamedKey::EraseEof,
|
||||
"ExSel" => NamedKey::ExSel,
|
||||
"Insert" => NamedKey::Insert,
|
||||
"Paste" => NamedKey::Paste,
|
||||
"Redo" => NamedKey::Redo,
|
||||
"Undo" => NamedKey::Undo,
|
||||
"Accept" => NamedKey::Accept,
|
||||
"Again" => NamedKey::Again,
|
||||
"Attn" => NamedKey::Attn,
|
||||
"Cancel" => NamedKey::Cancel,
|
||||
"ContextMenu" => NamedKey::ContextMenu,
|
||||
"Escape" => NamedKey::Escape,
|
||||
"Execute" => NamedKey::Execute,
|
||||
"Find" => NamedKey::Find,
|
||||
"Help" => NamedKey::Help,
|
||||
"Pause" => NamedKey::Pause,
|
||||
"Play" => NamedKey::Play,
|
||||
"Props" => NamedKey::Props,
|
||||
"Select" => NamedKey::Select,
|
||||
"ZoomIn" => NamedKey::ZoomIn,
|
||||
"ZoomOut" => NamedKey::ZoomOut,
|
||||
"BrightnessDown" => NamedKey::BrightnessDown,
|
||||
"BrightnessUp" => NamedKey::BrightnessUp,
|
||||
"Eject" => NamedKey::Eject,
|
||||
"LogOff" => NamedKey::LogOff,
|
||||
"Power" => NamedKey::Power,
|
||||
"PowerOff" => NamedKey::PowerOff,
|
||||
"PrintScreen" => NamedKey::PrintScreen,
|
||||
"Hibernate" => NamedKey::Hibernate,
|
||||
"Standby" => NamedKey::Standby,
|
||||
"WakeUp" => NamedKey::WakeUp,
|
||||
"AllCandidates" => NamedKey::AllCandidates,
|
||||
"Alphanumeric" => NamedKey::Alphanumeric,
|
||||
"CodeInput" => NamedKey::CodeInput,
|
||||
"Compose" => NamedKey::Compose,
|
||||
"Convert" => NamedKey::Convert,
|
||||
"FinalMode" => NamedKey::FinalMode,
|
||||
"GroupFirst" => NamedKey::GroupFirst,
|
||||
"GroupLast" => NamedKey::GroupLast,
|
||||
"GroupNext" => NamedKey::GroupNext,
|
||||
"GroupPrevious" => NamedKey::GroupPrevious,
|
||||
"ModeChange" => NamedKey::ModeChange,
|
||||
"NextCandidate" => NamedKey::NextCandidate,
|
||||
"NonConvert" => NamedKey::NonConvert,
|
||||
"PreviousCandidate" => NamedKey::PreviousCandidate,
|
||||
"Process" => NamedKey::Process,
|
||||
"SingleCandidate" => NamedKey::SingleCandidate,
|
||||
"HangulMode" => NamedKey::HangulMode,
|
||||
"HanjaMode" => NamedKey::HanjaMode,
|
||||
"JunjaMode" => NamedKey::JunjaMode,
|
||||
"Eisu" => NamedKey::Eisu,
|
||||
"Hankaku" => NamedKey::Hankaku,
|
||||
"Hiragana" => NamedKey::Hiragana,
|
||||
"HiraganaKatakana" => NamedKey::HiraganaKatakana,
|
||||
"KanaMode" => NamedKey::KanaMode,
|
||||
"KanjiMode" => NamedKey::KanjiMode,
|
||||
"Katakana" => NamedKey::Katakana,
|
||||
"Romaji" => NamedKey::Romaji,
|
||||
"Zenkaku" => NamedKey::Zenkaku,
|
||||
"ZenkakuHankaku" => NamedKey::ZenkakuHankaku,
|
||||
"Soft1" => NamedKey::Soft1,
|
||||
"Soft2" => NamedKey::Soft2,
|
||||
"Soft3" => NamedKey::Soft3,
|
||||
"Soft4" => NamedKey::Soft4,
|
||||
"ChannelDown" => NamedKey::ChannelDown,
|
||||
"ChannelUp" => NamedKey::ChannelUp,
|
||||
"Close" => NamedKey::Close,
|
||||
"MailForward" => NamedKey::MailForward,
|
||||
"MailReply" => NamedKey::MailReply,
|
||||
"MailSend" => NamedKey::MailSend,
|
||||
"MediaClose" => NamedKey::MediaClose,
|
||||
"MediaFastForward" => NamedKey::MediaFastForward,
|
||||
"MediaPause" => NamedKey::MediaPause,
|
||||
"MediaPlay" => NamedKey::MediaPlay,
|
||||
"MediaPlayPause" => NamedKey::MediaPlayPause,
|
||||
"MediaRecord" => NamedKey::MediaRecord,
|
||||
"MediaRewind" => NamedKey::MediaRewind,
|
||||
"MediaStop" => NamedKey::MediaStop,
|
||||
"MediaTrackNext" => NamedKey::MediaTrackNext,
|
||||
"MediaTrackPrevious" => NamedKey::MediaTrackPrevious,
|
||||
"New" => NamedKey::New,
|
||||
"Open" => NamedKey::Open,
|
||||
"Print" => NamedKey::Print,
|
||||
"Save" => NamedKey::Save,
|
||||
"SpellCheck" => NamedKey::SpellCheck,
|
||||
"Key11" => NamedKey::Key11,
|
||||
"Key12" => NamedKey::Key12,
|
||||
"AudioBalanceLeft" => NamedKey::AudioBalanceLeft,
|
||||
"AudioBalanceRight" => NamedKey::AudioBalanceRight,
|
||||
"AudioBassBoostDown" => NamedKey::AudioBassBoostDown,
|
||||
"AudioBassBoostToggle" => NamedKey::AudioBassBoostToggle,
|
||||
"AudioBassBoostUp" => NamedKey::AudioBassBoostUp,
|
||||
"AudioFaderFront" => NamedKey::AudioFaderFront,
|
||||
"AudioFaderRear" => NamedKey::AudioFaderRear,
|
||||
"AudioSurroundModeNext" => NamedKey::AudioSurroundModeNext,
|
||||
"AudioTrebleDown" => NamedKey::AudioTrebleDown,
|
||||
"AudioTrebleUp" => NamedKey::AudioTrebleUp,
|
||||
"AudioVolumeDown" => NamedKey::AudioVolumeDown,
|
||||
"AudioVolumeUp" => NamedKey::AudioVolumeUp,
|
||||
"AudioVolumeMute" => NamedKey::AudioVolumeMute,
|
||||
"MicrophoneToggle" => NamedKey::MicrophoneToggle,
|
||||
"MicrophoneVolumeDown" => NamedKey::MicrophoneVolumeDown,
|
||||
"MicrophoneVolumeUp" => NamedKey::MicrophoneVolumeUp,
|
||||
"MicrophoneVolumeMute" => NamedKey::MicrophoneVolumeMute,
|
||||
"SpeechCorrectionList" => NamedKey::SpeechCorrectionList,
|
||||
"SpeechInputToggle" => NamedKey::SpeechInputToggle,
|
||||
"LaunchApplication1" => NamedKey::LaunchApplication1,
|
||||
"LaunchApplication2" => NamedKey::LaunchApplication2,
|
||||
"LaunchCalendar" => NamedKey::LaunchCalendar,
|
||||
"LaunchContacts" => NamedKey::LaunchContacts,
|
||||
"LaunchMail" => NamedKey::LaunchMail,
|
||||
"LaunchMediaPlayer" => NamedKey::LaunchMediaPlayer,
|
||||
"LaunchMusicPlayer" => NamedKey::LaunchMusicPlayer,
|
||||
"LaunchPhone" => NamedKey::LaunchPhone,
|
||||
"LaunchScreenSaver" => NamedKey::LaunchScreenSaver,
|
||||
"LaunchSpreadsheet" => NamedKey::LaunchSpreadsheet,
|
||||
"LaunchWebBrowser" => NamedKey::LaunchWebBrowser,
|
||||
"LaunchWebCam" => NamedKey::LaunchWebCam,
|
||||
"LaunchWordProcessor" => NamedKey::LaunchWordProcessor,
|
||||
"BrowserBack" => NamedKey::BrowserBack,
|
||||
"BrowserFavorites" => NamedKey::BrowserFavorites,
|
||||
"BrowserForward" => NamedKey::BrowserForward,
|
||||
"BrowserHome" => NamedKey::BrowserHome,
|
||||
"BrowserRefresh" => NamedKey::BrowserRefresh,
|
||||
"BrowserSearch" => NamedKey::BrowserSearch,
|
||||
"BrowserStop" => NamedKey::BrowserStop,
|
||||
"AppSwitch" => NamedKey::AppSwitch,
|
||||
"Call" => NamedKey::Call,
|
||||
"Camera" => NamedKey::Camera,
|
||||
"CameraFocus" => NamedKey::CameraFocus,
|
||||
"EndCall" => NamedKey::EndCall,
|
||||
"GoBack" => NamedKey::GoBack,
|
||||
"GoHome" => NamedKey::GoHome,
|
||||
"HeadsetHook" => NamedKey::HeadsetHook,
|
||||
"LastNumberRedial" => NamedKey::LastNumberRedial,
|
||||
"Notification" => NamedKey::Notification,
|
||||
"MannerMode" => NamedKey::MannerMode,
|
||||
"VoiceDial" => NamedKey::VoiceDial,
|
||||
"TV" => NamedKey::TV,
|
||||
"TV3DMode" => NamedKey::TV3DMode,
|
||||
"TVAntennaCable" => NamedKey::TVAntennaCable,
|
||||
"TVAudioDescription" => NamedKey::TVAudioDescription,
|
||||
"TVAudioDescriptionMixDown" => NamedKey::TVAudioDescriptionMixDown,
|
||||
"TVAudioDescriptionMixUp" => NamedKey::TVAudioDescriptionMixUp,
|
||||
"TVContentsMenu" => NamedKey::TVContentsMenu,
|
||||
"TVDataService" => NamedKey::TVDataService,
|
||||
"TVInput" => NamedKey::TVInput,
|
||||
"TVInputComponent1" => NamedKey::TVInputComponent1,
|
||||
"TVInputComponent2" => NamedKey::TVInputComponent2,
|
||||
"TVInputComposite1" => NamedKey::TVInputComposite1,
|
||||
"TVInputComposite2" => NamedKey::TVInputComposite2,
|
||||
"TVInputHDMI1" => NamedKey::TVInputHDMI1,
|
||||
"TVInputHDMI2" => NamedKey::TVInputHDMI2,
|
||||
"TVInputHDMI3" => NamedKey::TVInputHDMI3,
|
||||
"TVInputHDMI4" => NamedKey::TVInputHDMI4,
|
||||
"TVInputVGA1" => NamedKey::TVInputVGA1,
|
||||
"TVMediaContext" => NamedKey::TVMediaContext,
|
||||
"TVNetwork" => NamedKey::TVNetwork,
|
||||
"TVNumberEntry" => NamedKey::TVNumberEntry,
|
||||
"TVPower" => NamedKey::TVPower,
|
||||
"TVRadioService" => NamedKey::TVRadioService,
|
||||
"TVSatellite" => NamedKey::TVSatellite,
|
||||
"TVSatelliteBS" => NamedKey::TVSatelliteBS,
|
||||
"TVSatelliteCS" => NamedKey::TVSatelliteCS,
|
||||
"TVSatelliteToggle" => NamedKey::TVSatelliteToggle,
|
||||
"TVTerrestrialAnalog" => NamedKey::TVTerrestrialAnalog,
|
||||
"TVTerrestrialDigital" => NamedKey::TVTerrestrialDigital,
|
||||
"TVTimer" => NamedKey::TVTimer,
|
||||
"AVRInput" => NamedKey::AVRInput,
|
||||
"AVRPower" => NamedKey::AVRPower,
|
||||
"ColorF0Red" => NamedKey::ColorF0Red,
|
||||
"ColorF1Green" => NamedKey::ColorF1Green,
|
||||
"ColorF2Yellow" => NamedKey::ColorF2Yellow,
|
||||
"ColorF3Blue" => NamedKey::ColorF3Blue,
|
||||
"ColorF4Grey" => NamedKey::ColorF4Grey,
|
||||
"ColorF5Brown" => NamedKey::ColorF5Brown,
|
||||
"ClosedCaptionToggle" => NamedKey::ClosedCaptionToggle,
|
||||
"Dimmer" => NamedKey::Dimmer,
|
||||
"DisplaySwap" => NamedKey::DisplaySwap,
|
||||
"DVR" => NamedKey::DVR,
|
||||
"Exit" => NamedKey::Exit,
|
||||
"FavoriteClear0" => NamedKey::FavoriteClear0,
|
||||
"FavoriteClear1" => NamedKey::FavoriteClear1,
|
||||
"FavoriteClear2" => NamedKey::FavoriteClear2,
|
||||
"FavoriteClear3" => NamedKey::FavoriteClear3,
|
||||
"FavoriteRecall0" => NamedKey::FavoriteRecall0,
|
||||
"FavoriteRecall1" => NamedKey::FavoriteRecall1,
|
||||
"FavoriteRecall2" => NamedKey::FavoriteRecall2,
|
||||
"FavoriteRecall3" => NamedKey::FavoriteRecall3,
|
||||
"FavoriteStore0" => NamedKey::FavoriteStore0,
|
||||
"FavoriteStore1" => NamedKey::FavoriteStore1,
|
||||
"FavoriteStore2" => NamedKey::FavoriteStore2,
|
||||
"FavoriteStore3" => NamedKey::FavoriteStore3,
|
||||
"Guide" => NamedKey::Guide,
|
||||
"GuideNextDay" => NamedKey::GuideNextDay,
|
||||
"GuidePreviousDay" => NamedKey::GuidePreviousDay,
|
||||
"Info" => NamedKey::Info,
|
||||
"InstantReplay" => NamedKey::InstantReplay,
|
||||
"Link" => NamedKey::Link,
|
||||
"ListProgram" => NamedKey::ListProgram,
|
||||
"LiveContent" => NamedKey::LiveContent,
|
||||
"Lock" => NamedKey::Lock,
|
||||
"MediaApps" => NamedKey::MediaApps,
|
||||
"MediaAudioTrack" => NamedKey::MediaAudioTrack,
|
||||
"MediaLast" => NamedKey::MediaLast,
|
||||
"MediaSkipBackward" => NamedKey::MediaSkipBackward,
|
||||
"MediaSkipForward" => NamedKey::MediaSkipForward,
|
||||
"MediaStepBackward" => NamedKey::MediaStepBackward,
|
||||
"MediaStepForward" => NamedKey::MediaStepForward,
|
||||
"MediaTopMenu" => NamedKey::MediaTopMenu,
|
||||
"NavigateIn" => NamedKey::NavigateIn,
|
||||
"NavigateNext" => NamedKey::NavigateNext,
|
||||
"NavigateOut" => NamedKey::NavigateOut,
|
||||
"NavigatePrevious" => NamedKey::NavigatePrevious,
|
||||
"NextFavoriteChannel" => NamedKey::NextFavoriteChannel,
|
||||
"NextUserProfile" => NamedKey::NextUserProfile,
|
||||
"OnDemand" => NamedKey::OnDemand,
|
||||
"Pairing" => NamedKey::Pairing,
|
||||
"PinPDown" => NamedKey::PinPDown,
|
||||
"PinPMove" => NamedKey::PinPMove,
|
||||
"PinPToggle" => NamedKey::PinPToggle,
|
||||
"PinPUp" => NamedKey::PinPUp,
|
||||
"PlaySpeedDown" => NamedKey::PlaySpeedDown,
|
||||
"PlaySpeedReset" => NamedKey::PlaySpeedReset,
|
||||
"PlaySpeedUp" => NamedKey::PlaySpeedUp,
|
||||
"RandomToggle" => NamedKey::RandomToggle,
|
||||
"RcLowBattery" => NamedKey::RcLowBattery,
|
||||
"RecordSpeedNext" => NamedKey::RecordSpeedNext,
|
||||
"RfBypass" => NamedKey::RfBypass,
|
||||
"ScanChannelsToggle" => NamedKey::ScanChannelsToggle,
|
||||
"ScreenModeNext" => NamedKey::ScreenModeNext,
|
||||
"Settings" => NamedKey::Settings,
|
||||
"SplitScreenToggle" => NamedKey::SplitScreenToggle,
|
||||
"STBInput" => NamedKey::STBInput,
|
||||
"STBPower" => NamedKey::STBPower,
|
||||
"Subtitle" => NamedKey::Subtitle,
|
||||
"Teletext" => NamedKey::Teletext,
|
||||
"VideoModeNext" => NamedKey::VideoModeNext,
|
||||
"Wink" => NamedKey::Wink,
|
||||
"ZoomToggle" => NamedKey::ZoomToggle,
|
||||
"F1" => NamedKey::F1,
|
||||
"F2" => NamedKey::F2,
|
||||
"F3" => NamedKey::F3,
|
||||
"F4" => NamedKey::F4,
|
||||
"F5" => NamedKey::F5,
|
||||
"F6" => NamedKey::F6,
|
||||
"F7" => NamedKey::F7,
|
||||
"F8" => NamedKey::F8,
|
||||
"F9" => NamedKey::F9,
|
||||
"F10" => NamedKey::F10,
|
||||
"F11" => NamedKey::F11,
|
||||
"F12" => NamedKey::F12,
|
||||
"F13" => NamedKey::F13,
|
||||
"F14" => NamedKey::F14,
|
||||
"F15" => NamedKey::F15,
|
||||
"F16" => NamedKey::F16,
|
||||
"F17" => NamedKey::F17,
|
||||
"F18" => NamedKey::F18,
|
||||
"F19" => NamedKey::F19,
|
||||
"F20" => NamedKey::F20,
|
||||
"F21" => NamedKey::F21,
|
||||
"F22" => NamedKey::F22,
|
||||
"F23" => NamedKey::F23,
|
||||
"F24" => NamedKey::F24,
|
||||
"F25" => NamedKey::F25,
|
||||
"F26" => NamedKey::F26,
|
||||
"F27" => NamedKey::F27,
|
||||
"F28" => NamedKey::F28,
|
||||
"F29" => NamedKey::F29,
|
||||
"F30" => NamedKey::F30,
|
||||
"F31" => NamedKey::F31,
|
||||
"F32" => NamedKey::F32,
|
||||
"F33" => NamedKey::F33,
|
||||
"F34" => NamedKey::F34,
|
||||
"F35" => NamedKey::F35,
|
||||
string => return Key::Character(SmolStr::new(string)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PhysicalKey {
|
||||
pub fn from_key_code_attribute_value(kcav: &str) -> Self {
|
||||
PhysicalKey::Code(match kcav {
|
||||
"Backquote" => KeyCode::Backquote,
|
||||
"Backslash" => KeyCode::Backslash,
|
||||
"BracketLeft" => KeyCode::BracketLeft,
|
||||
"BracketRight" => KeyCode::BracketRight,
|
||||
"Comma" => KeyCode::Comma,
|
||||
"Digit0" => KeyCode::Digit0,
|
||||
"Digit1" => KeyCode::Digit1,
|
||||
"Digit2" => KeyCode::Digit2,
|
||||
"Digit3" => KeyCode::Digit3,
|
||||
"Digit4" => KeyCode::Digit4,
|
||||
"Digit5" => KeyCode::Digit5,
|
||||
"Digit6" => KeyCode::Digit6,
|
||||
"Digit7" => KeyCode::Digit7,
|
||||
"Digit8" => KeyCode::Digit8,
|
||||
"Digit9" => KeyCode::Digit9,
|
||||
"Equal" => KeyCode::Equal,
|
||||
"IntlBackslash" => KeyCode::IntlBackslash,
|
||||
"IntlRo" => KeyCode::IntlRo,
|
||||
"IntlYen" => KeyCode::IntlYen,
|
||||
"KeyA" => KeyCode::KeyA,
|
||||
"KeyB" => KeyCode::KeyB,
|
||||
"KeyC" => KeyCode::KeyC,
|
||||
"KeyD" => KeyCode::KeyD,
|
||||
"KeyE" => KeyCode::KeyE,
|
||||
"KeyF" => KeyCode::KeyF,
|
||||
"KeyG" => KeyCode::KeyG,
|
||||
"KeyH" => KeyCode::KeyH,
|
||||
"KeyI" => KeyCode::KeyI,
|
||||
"KeyJ" => KeyCode::KeyJ,
|
||||
"KeyK" => KeyCode::KeyK,
|
||||
"KeyL" => KeyCode::KeyL,
|
||||
"KeyM" => KeyCode::KeyM,
|
||||
"KeyN" => KeyCode::KeyN,
|
||||
"KeyO" => KeyCode::KeyO,
|
||||
"KeyP" => KeyCode::KeyP,
|
||||
"KeyQ" => KeyCode::KeyQ,
|
||||
"KeyR" => KeyCode::KeyR,
|
||||
"KeyS" => KeyCode::KeyS,
|
||||
"KeyT" => KeyCode::KeyT,
|
||||
"KeyU" => KeyCode::KeyU,
|
||||
"KeyV" => KeyCode::KeyV,
|
||||
"KeyW" => KeyCode::KeyW,
|
||||
"KeyX" => KeyCode::KeyX,
|
||||
"KeyY" => KeyCode::KeyY,
|
||||
"KeyZ" => KeyCode::KeyZ,
|
||||
"Minus" => KeyCode::Minus,
|
||||
"Period" => KeyCode::Period,
|
||||
"Quote" => KeyCode::Quote,
|
||||
"Semicolon" => KeyCode::Semicolon,
|
||||
"Slash" => KeyCode::Slash,
|
||||
"AltLeft" => KeyCode::AltLeft,
|
||||
"AltRight" => KeyCode::AltRight,
|
||||
"Backspace" => KeyCode::Backspace,
|
||||
"CapsLock" => KeyCode::CapsLock,
|
||||
"ContextMenu" => KeyCode::ContextMenu,
|
||||
"ControlLeft" => KeyCode::ControlLeft,
|
||||
"ControlRight" => KeyCode::ControlRight,
|
||||
"Enter" => KeyCode::Enter,
|
||||
"MetaLeft" => KeyCode::SuperLeft,
|
||||
"MetaRight" => KeyCode::SuperRight,
|
||||
"ShiftLeft" => KeyCode::ShiftLeft,
|
||||
"ShiftRight" => KeyCode::ShiftRight,
|
||||
"Space" => KeyCode::Space,
|
||||
"Tab" => KeyCode::Tab,
|
||||
"Convert" => KeyCode::Convert,
|
||||
"KanaMode" => KeyCode::KanaMode,
|
||||
"Lang1" => KeyCode::Lang1,
|
||||
"Lang2" => KeyCode::Lang2,
|
||||
"Lang3" => KeyCode::Lang3,
|
||||
"Lang4" => KeyCode::Lang4,
|
||||
"Lang5" => KeyCode::Lang5,
|
||||
"NonConvert" => KeyCode::NonConvert,
|
||||
"Delete" => KeyCode::Delete,
|
||||
"End" => KeyCode::End,
|
||||
"Help" => KeyCode::Help,
|
||||
"Home" => KeyCode::Home,
|
||||
"Insert" => KeyCode::Insert,
|
||||
"PageDown" => KeyCode::PageDown,
|
||||
"PageUp" => KeyCode::PageUp,
|
||||
"ArrowDown" => KeyCode::ArrowDown,
|
||||
"ArrowLeft" => KeyCode::ArrowLeft,
|
||||
"ArrowRight" => KeyCode::ArrowRight,
|
||||
"ArrowUp" => KeyCode::ArrowUp,
|
||||
"NumLock" => KeyCode::NumLock,
|
||||
"Numpad0" => KeyCode::Numpad0,
|
||||
"Numpad1" => KeyCode::Numpad1,
|
||||
"Numpad2" => KeyCode::Numpad2,
|
||||
"Numpad3" => KeyCode::Numpad3,
|
||||
"Numpad4" => KeyCode::Numpad4,
|
||||
"Numpad5" => KeyCode::Numpad5,
|
||||
"Numpad6" => KeyCode::Numpad6,
|
||||
"Numpad7" => KeyCode::Numpad7,
|
||||
"Numpad8" => KeyCode::Numpad8,
|
||||
"Numpad9" => KeyCode::Numpad9,
|
||||
"NumpadAdd" => KeyCode::NumpadAdd,
|
||||
"NumpadBackspace" => KeyCode::NumpadBackspace,
|
||||
"NumpadClear" => KeyCode::NumpadClear,
|
||||
"NumpadClearEntry" => KeyCode::NumpadClearEntry,
|
||||
"NumpadComma" => KeyCode::NumpadComma,
|
||||
"NumpadDecimal" => KeyCode::NumpadDecimal,
|
||||
"NumpadDivide" => KeyCode::NumpadDivide,
|
||||
"NumpadEnter" => KeyCode::NumpadEnter,
|
||||
"NumpadEqual" => KeyCode::NumpadEqual,
|
||||
"NumpadHash" => KeyCode::NumpadHash,
|
||||
"NumpadMemoryAdd" => KeyCode::NumpadMemoryAdd,
|
||||
"NumpadMemoryClear" => KeyCode::NumpadMemoryClear,
|
||||
"NumpadMemoryRecall" => KeyCode::NumpadMemoryRecall,
|
||||
"NumpadMemoryStore" => KeyCode::NumpadMemoryStore,
|
||||
"NumpadMemorySubtract" => KeyCode::NumpadMemorySubtract,
|
||||
"NumpadMultiply" => KeyCode::NumpadMultiply,
|
||||
"NumpadParenLeft" => KeyCode::NumpadParenLeft,
|
||||
"NumpadParenRight" => KeyCode::NumpadParenRight,
|
||||
"NumpadStar" => KeyCode::NumpadStar,
|
||||
"NumpadSubtract" => KeyCode::NumpadSubtract,
|
||||
"Escape" => KeyCode::Escape,
|
||||
"Fn" => KeyCode::Fn,
|
||||
"FnLock" => KeyCode::FnLock,
|
||||
"PrintScreen" => KeyCode::PrintScreen,
|
||||
"ScrollLock" => KeyCode::ScrollLock,
|
||||
"Pause" => KeyCode::Pause,
|
||||
"BrowserBack" => KeyCode::BrowserBack,
|
||||
"BrowserFavorites" => KeyCode::BrowserFavorites,
|
||||
"BrowserForward" => KeyCode::BrowserForward,
|
||||
"BrowserHome" => KeyCode::BrowserHome,
|
||||
"BrowserRefresh" => KeyCode::BrowserRefresh,
|
||||
"BrowserSearch" => KeyCode::BrowserSearch,
|
||||
"BrowserStop" => KeyCode::BrowserStop,
|
||||
"Eject" => KeyCode::Eject,
|
||||
"LaunchApp1" => KeyCode::LaunchApp1,
|
||||
"LaunchApp2" => KeyCode::LaunchApp2,
|
||||
"LaunchMail" => KeyCode::LaunchMail,
|
||||
"MediaPlayPause" => KeyCode::MediaPlayPause,
|
||||
"MediaSelect" => KeyCode::MediaSelect,
|
||||
"MediaStop" => KeyCode::MediaStop,
|
||||
"MediaTrackNext" => KeyCode::MediaTrackNext,
|
||||
"MediaTrackPrevious" => KeyCode::MediaTrackPrevious,
|
||||
"Power" => KeyCode::Power,
|
||||
"Sleep" => KeyCode::Sleep,
|
||||
"AudioVolumeDown" => KeyCode::AudioVolumeDown,
|
||||
"AudioVolumeMute" => KeyCode::AudioVolumeMute,
|
||||
"AudioVolumeUp" => KeyCode::AudioVolumeUp,
|
||||
"WakeUp" => KeyCode::WakeUp,
|
||||
"Hyper" => KeyCode::Hyper,
|
||||
"Turbo" => KeyCode::Turbo,
|
||||
"Abort" => KeyCode::Abort,
|
||||
"Resume" => KeyCode::Resume,
|
||||
"Suspend" => KeyCode::Suspend,
|
||||
"Again" => KeyCode::Again,
|
||||
"Copy" => KeyCode::Copy,
|
||||
"Cut" => KeyCode::Cut,
|
||||
"Find" => KeyCode::Find,
|
||||
"Open" => KeyCode::Open,
|
||||
"Paste" => KeyCode::Paste,
|
||||
"Props" => KeyCode::Props,
|
||||
"Select" => KeyCode::Select,
|
||||
"Undo" => KeyCode::Undo,
|
||||
"Hiragana" => KeyCode::Hiragana,
|
||||
"Katakana" => KeyCode::Katakana,
|
||||
"F1" => KeyCode::F1,
|
||||
"F2" => KeyCode::F2,
|
||||
"F3" => KeyCode::F3,
|
||||
"F4" => KeyCode::F4,
|
||||
"F5" => KeyCode::F5,
|
||||
"F6" => KeyCode::F6,
|
||||
"F7" => KeyCode::F7,
|
||||
"F8" => KeyCode::F8,
|
||||
"F9" => KeyCode::F9,
|
||||
"F10" => KeyCode::F10,
|
||||
"F11" => KeyCode::F11,
|
||||
"F12" => KeyCode::F12,
|
||||
"F13" => KeyCode::F13,
|
||||
"F14" => KeyCode::F14,
|
||||
"F15" => KeyCode::F15,
|
||||
"F16" => KeyCode::F16,
|
||||
"F17" => KeyCode::F17,
|
||||
"F18" => KeyCode::F18,
|
||||
"F19" => KeyCode::F19,
|
||||
"F20" => KeyCode::F20,
|
||||
"F21" => KeyCode::F21,
|
||||
"F22" => KeyCode::F22,
|
||||
"F23" => KeyCode::F23,
|
||||
"F24" => KeyCode::F24,
|
||||
"F25" => KeyCode::F25,
|
||||
"F26" => KeyCode::F26,
|
||||
"F27" => KeyCode::F27,
|
||||
"F28" => KeyCode::F28,
|
||||
"F29" => KeyCode::F29,
|
||||
"F30" => KeyCode::F30,
|
||||
"F31" => KeyCode::F31,
|
||||
"F32" => KeyCode::F32,
|
||||
"F33" => KeyCode::F33,
|
||||
"F34" => KeyCode::F34,
|
||||
"F35" => KeyCode::F35,
|
||||
_ => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
|
||||
})
|
||||
}
|
||||
}
|
||||
32
winit-core/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "winit-core"
|
||||
version = "0.1.0"
|
||||
authors = ["The winit contributors"]
|
||||
description = "Base types for windowing libraries"
|
||||
edition = "2021"
|
||||
keywords = ["windowing"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
rust-version = "1.70.0"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["alloc"]
|
||||
alloc = []
|
||||
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
cursor-icon.workspace = true
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
smol_str.workspace = true
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
web-time.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
cfg_aliases.workspace = true
|
||||
3
winit-core/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# winit-core - Base types for a windowing system
|
||||
|
||||
TODO
|
||||
@@ -8,7 +8,7 @@ fn main() {
|
||||
cfg_aliases! {
|
||||
// Systems.
|
||||
android_platform: { target_os = "android" },
|
||||
wasm_platform: { all(target_family = "wasm", not(target_os = "emscripten")) },
|
||||
web_platform: { all(target_family = "wasm", target_os = "unknown") },
|
||||
macos_platform: { target_os = "macos" },
|
||||
ios_platform: { target_os = "ios" },
|
||||
windows_platform: { target_os = "windows" },
|
||||
@@ -17,8 +17,8 @@ fn main() {
|
||||
redox: { target_os = "redox" },
|
||||
|
||||
// Native displays.
|
||||
x11_platform: { all(feature = "x11", free_unix, not(wasm), not(redox)) },
|
||||
wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) },
|
||||
x11_platform: { all(feature = "x11", free_unix, not(redox)) },
|
||||
wayland_platform: { all(feature = "wayland", free_unix, not(redox)) },
|
||||
orbital_platform: { redox },
|
||||
}
|
||||
}
|
||||
@@ -97,13 +97,16 @@
|
||||
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
||||
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
||||
//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged
|
||||
//! [`window.scale_factor()`]: crate::window::Window::scale_factor
|
||||
//! [`window.scale_factor()`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.scale_factor
|
||||
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
|
||||
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
|
||||
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
|
||||
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
|
||||
//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub trait Pixel: Copy + Into<f64> {
|
||||
fn from_f64(f: f64) -> Self;
|
||||
fn cast<P: Pixel>(self) -> P {
|
||||
54
winit-core/src/error.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
//! Common error types.
|
||||
|
||||
use std::{error, fmt};
|
||||
|
||||
/// The error type for when the requested operation is not supported by the backend.
|
||||
#[derive(Clone)]
|
||||
pub struct NotSupportedError {
|
||||
_marker: (),
|
||||
}
|
||||
|
||||
impl Default for NotSupportedError {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl NotSupportedError {
|
||||
/// Create a new [`NotSupportedError`].
|
||||
#[inline]
|
||||
pub fn new() -> NotSupportedError {
|
||||
NotSupportedError { _marker: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NotSupportedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("NotSupportedError").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NotSupportedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.pad("the requested operation is not supported by Winit")
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for NotSupportedError {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::redundant_clone)]
|
||||
|
||||
use super::*;
|
||||
|
||||
// Eat attributes for testing
|
||||
#[test]
|
||||
fn ensure_fmt_does_not_panic() {
|
||||
let _ = format!(
|
||||
"{:?}, {}",
|
||||
NotSupportedError::new(),
|
||||
NotSupportedError::new().clone()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,64 @@
|
||||
//! The [`Event`] enum and assorted supporting types.
|
||||
//!
|
||||
//! These are sent to the closure given to [`EventLoop::run(...)`], where they get
|
||||
//! processed and used to modify the program state. For more details, see the root-level documentation.
|
||||
//!
|
||||
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
|
||||
//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! let mut start_cause = StartCause::Init;
|
||||
//!
|
||||
//! while !elwt.exiting() {
|
||||
//! event_handler(NewEvents(start_cause), elwt);
|
||||
//!
|
||||
//! for e in (window events, user events, device events) {
|
||||
//! event_handler(e, elwt);
|
||||
//! }
|
||||
//!
|
||||
//! for w in (redraw windows) {
|
||||
//! event_handler(RedrawRequested(w), elwt);
|
||||
//! }
|
||||
//!
|
||||
//! event_handler(AboutToWait, elwt);
|
||||
//! start_cause = wait_if_necessary();
|
||||
//! }
|
||||
//!
|
||||
//! event_handler(LoopExiting, elwt);
|
||||
//! ```
|
||||
//!
|
||||
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
|
||||
//! describes what happens in what order.
|
||||
//!
|
||||
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
|
||||
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
//! Incoming notifications from the GUI system.
|
||||
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::event_loop::AsyncRequestSerial;
|
||||
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
|
||||
use crate::window::{ActivationToken, Theme, WindowId};
|
||||
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Mutex, Weak};
|
||||
#[cfg(not(wasm_platform))]
|
||||
use std::time::Instant;
|
||||
|
||||
use smol_str::SmolStr;
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(not(web_platform))]
|
||||
use std::time::Instant;
|
||||
#[cfg(web_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::{ActivationToken, Theme, WindowId},
|
||||
};
|
||||
#[cfg(feature = "serde")]
|
||||
pub use serde::{Deserialize, Serialize};
|
||||
|
||||
use smol_str::SmolStr;
|
||||
|
||||
/// Identifier of an input device.
|
||||
///
|
||||
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
|
||||
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
|
||||
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(u64);
|
||||
|
||||
impl DeviceId {
|
||||
/// Returns a dummy id, useful for unit testing.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The only guarantee made about the return value of this function is that
|
||||
/// it will always be equal to itself and to future values returned by this function.
|
||||
/// No other guarantees are made. This may be equal to a real `DeviceId`.
|
||||
///
|
||||
/// **Passing this into a winit function will result in undefined behavior.**
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
DeviceId(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for DeviceId {
|
||||
fn from(value: u64) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DeviceId> for u64 {
|
||||
fn from(value: DeviceId) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a generic event.
|
||||
///
|
||||
/// See the module-level docs for more information on the event loop manages each event.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Event<T: 'static> {
|
||||
pub enum Event<T: 'static, KeyExtra> {
|
||||
/// Emitted when new events arrive from the OS to be processed.
|
||||
///
|
||||
/// This event type is useful as a place to put code that should be done before you start
|
||||
@@ -68,7 +70,7 @@ pub enum Event<T: 'static> {
|
||||
/// Emitted when the OS sends an event to a winit window.
|
||||
WindowEvent {
|
||||
window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
event: WindowEvent<KeyExtra>,
|
||||
},
|
||||
|
||||
/// Emitted when the OS sends an event to a device.
|
||||
@@ -77,7 +79,7 @@ pub enum Event<T: 'static> {
|
||||
event: DeviceEvent,
|
||||
},
|
||||
|
||||
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event)
|
||||
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](https://docs.rs/winit/latest/winit/event_loop/struct.EventLoopProxy.html#method.send_event)
|
||||
UserEvent(T),
|
||||
|
||||
/// Emitted when the application has been suspended.
|
||||
@@ -253,9 +255,9 @@ pub enum Event<T: 'static> {
|
||||
MemoryWarning,
|
||||
}
|
||||
|
||||
impl<T> Event<T> {
|
||||
impl<T, Extra> Event<T, Extra> {
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
|
||||
pub fn map_nonuser_event<U>(self) -> Result<Event<U, Extra>, Event<T, Extra>> {
|
||||
use self::Event::*;
|
||||
match self {
|
||||
UserEvent(_) => Err(self),
|
||||
@@ -302,8 +304,10 @@ pub enum StartCause {
|
||||
}
|
||||
|
||||
/// Describes an event from a [`Window`].
|
||||
///
|
||||
/// [`Window`]: https://docs.rs/winit/latest/winit/window/struct.Window.html
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum WindowEvent {
|
||||
pub enum WindowEvent<KeyExtra> {
|
||||
/// The activation token was delivered back and now could be used.
|
||||
///
|
||||
#[cfg_attr(
|
||||
@@ -365,7 +369,7 @@ pub enum WindowEvent {
|
||||
/// events which are not marked as `is_synthetic`.
|
||||
KeyboardInput {
|
||||
device_id: DeviceId,
|
||||
event: KeyEvent,
|
||||
event: KeyEvent<KeyExtra>,
|
||||
|
||||
/// If `true`, the event was generated synthetically by winit
|
||||
/// in one of the following circumstances:
|
||||
@@ -389,6 +393,8 @@ pub enum WindowEvent {
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Orbital:** Unsupported.
|
||||
///
|
||||
/// [`Window::set_ime_allowed`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_allowed
|
||||
Ime(Ime),
|
||||
|
||||
/// The cursor has moved on the window.
|
||||
@@ -445,21 +451,23 @@ pub enum WindowEvent {
|
||||
button: MouseButton,
|
||||
},
|
||||
|
||||
/// Touchpad magnification event with two-finger pinch gesture.
|
||||
///
|
||||
/// Positive delta values indicate magnification (zooming in) and
|
||||
/// negative delta values indicate shrinking (zooming out).
|
||||
/// Two-finger pinch gesture, often used for magnification.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - Only available on **macOS**.
|
||||
TouchpadMagnify {
|
||||
/// - Only available on **macOS** and **iOS**.
|
||||
/// - On iOS, not recognized by default. It must be enabled when needed.
|
||||
PinchGesture {
|
||||
device_id: DeviceId,
|
||||
/// Positive values indicate magnification (zooming in) and negative
|
||||
/// values indicate shrinking (zooming out).
|
||||
///
|
||||
/// This value may be NaN.
|
||||
delta: f64,
|
||||
phase: TouchPhase,
|
||||
},
|
||||
|
||||
/// Smart magnification event.
|
||||
/// Double tap gesture.
|
||||
///
|
||||
/// On a Mac, smart magnification is triggered by a double tap with two fingers
|
||||
/// on the trackpad and is commonly used to zoom on a certain object
|
||||
@@ -475,18 +483,20 @@ pub enum WindowEvent {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - Only available on **macOS 10.8** and later.
|
||||
SmartMagnify { device_id: DeviceId },
|
||||
/// - Only available on **macOS 10.8** and later, and **iOS**.
|
||||
/// - On iOS, not recognized by default. It must be enabled when needed.
|
||||
DoubleTapGesture { device_id: DeviceId },
|
||||
|
||||
/// Touchpad rotation event with two-finger rotation gesture.
|
||||
/// Two-finger rotation gesture.
|
||||
///
|
||||
/// Positive delta values indicate rotation counterclockwise and
|
||||
/// negative delta values indicate rotation clockwise.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - Only available on **macOS**.
|
||||
TouchpadRotate {
|
||||
/// - Only available on **macOS** and **iOS**.
|
||||
/// - On iOS, not recognized by default. It must be enabled when needed.
|
||||
RotationGesture {
|
||||
device_id: DeviceId,
|
||||
delta: f32,
|
||||
phase: TouchPhase,
|
||||
@@ -574,7 +584,7 @@ pub enum WindowEvent {
|
||||
/// ### Others
|
||||
///
|
||||
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
||||
/// - **Android / Windows / Orbital:** Unsupported.
|
||||
/// - **Android / Wayland / Windows / Orbital:** Unsupported.
|
||||
///
|
||||
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
@@ -590,33 +600,11 @@ pub enum WindowEvent {
|
||||
///
|
||||
/// Winit will aggregate duplicate redraw requests into a single event, to
|
||||
/// help avoid duplicating rendering work.
|
||||
///
|
||||
/// [`Window::request_redraw`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.request_redraw
|
||||
RedrawRequested,
|
||||
}
|
||||
|
||||
/// Identifier of an input device.
|
||||
///
|
||||
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
|
||||
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
|
||||
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
|
||||
|
||||
impl DeviceId {
|
||||
/// Returns a dummy id, useful for unit testing.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The only guarantee made about the return value of this function is that
|
||||
/// it will always be equal to itself and to future values returned by this function.
|
||||
/// No other guarantees are made. This may be equal to a real `DeviceId`.
|
||||
///
|
||||
/// **Passing this into a winit function will result in undefined behavior.**
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
#[allow(unused_unsafe)]
|
||||
DeviceId(unsafe { platform_impl::DeviceId::dummy() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents raw hardware events that are not associated with any particular window.
|
||||
///
|
||||
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
|
||||
@@ -677,7 +665,7 @@ pub struct RawKeyEvent {
|
||||
|
||||
/// Describes a keyboard input targeting a window.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeyEvent {
|
||||
pub struct KeyEvent<Extra> {
|
||||
/// Represents the position of a key independent of the currently active layout.
|
||||
///
|
||||
/// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode).
|
||||
@@ -727,7 +715,7 @@ pub struct KeyEvent {
|
||||
/// - **Web:** Dead keys might be reported as the real key instead
|
||||
/// of `Dead` depending on the browser/OS.
|
||||
///
|
||||
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
|
||||
/// [`key_without_modifiers`]: https://docs.rs/winit/latest/winit/platform/modifier_supplement/trait.KeyEventExtModifierSupplement.html#tymethod.key_without_modifiers
|
||||
pub logical_key: keyboard::Key,
|
||||
|
||||
/// Contains the text produced by this keypress.
|
||||
@@ -784,7 +772,7 @@ pub struct KeyEvent {
|
||||
/// modifiers applied.
|
||||
///
|
||||
/// On Android, iOS, Redox and Web, this type is a no-op.
|
||||
pub(crate) platform_specific: platform_impl::KeyEventExtra,
|
||||
pub extra: Extra,
|
||||
}
|
||||
|
||||
/// Describes keyboard modifiers event.
|
||||
@@ -799,6 +787,15 @@ pub struct Modifiers {
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
/// Only `winit` should instantiate this!
|
||||
#[doc(hidden)]
|
||||
pub fn new(state: ModifiersState, pressed_mods: ModifiersKeys) -> Self {
|
||||
Self {
|
||||
state,
|
||||
pressed_mods,
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of the modifiers.
|
||||
pub fn state(&self) -> ModifiersState {
|
||||
self.state
|
||||
@@ -898,6 +895,8 @@ impl From<ModifiersState> for Modifiers {
|
||||
/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
|
||||
/// Ime::Commit("啊不")
|
||||
/// ```
|
||||
///
|
||||
/// [`Window::set_ime_cursor_area`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_cursor_area
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Ime {
|
||||
@@ -906,6 +905,8 @@ pub enum Ime {
|
||||
/// After getting this event you could receive [`Preedit`](Self::Preedit) and
|
||||
/// [`Commit`](Self::Commit) events. You should also start performing IME related requests
|
||||
/// like [`Window::set_ime_cursor_area`].
|
||||
///
|
||||
/// [`Window::set_ime_cursor_area`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_cursor_area
|
||||
Enabled,
|
||||
|
||||
/// Notifies when a new composing text should be set at the cursor position.
|
||||
@@ -928,6 +929,8 @@ pub enum Ime {
|
||||
/// [`Commit`](Self::Commit) events until the next [`Enabled`](Self::Enabled) event. You should
|
||||
/// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear pending
|
||||
/// preedit text.
|
||||
///
|
||||
/// [`Window::set_ime_cursor_area`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_cursor_area
|
||||
Disabled,
|
||||
}
|
||||
|
||||
@@ -1116,7 +1119,7 @@ pub struct InnerSizeWriter {
|
||||
|
||||
impl InnerSizeWriter {
|
||||
#[cfg(not(orbital_platform))]
|
||||
pub(crate) fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
|
||||
pub fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
|
||||
Self { new_inner_size }
|
||||
}
|
||||
|
||||
@@ -1124,14 +1127,19 @@ impl InnerSizeWriter {
|
||||
pub fn request_inner_size(
|
||||
&mut self,
|
||||
new_inner_size: PhysicalSize<u32>,
|
||||
) -> Result<(), ExternalError> {
|
||||
) -> Result<(), InnerSizeIgnored> {
|
||||
if let Some(inner) = self.new_inner_size.upgrade() {
|
||||
*inner.lock().unwrap() = new_inner_size;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ExternalError::Ignored)
|
||||
Err(InnerSizeIgnored { _private: () })
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the underlying size.
|
||||
pub fn get(&self) -> PhysicalSize<u32> {
|
||||
*self.new_inner_size.upgrade().unwrap().lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for InnerSizeWriter {
|
||||
@@ -1140,6 +1148,25 @@ impl PartialEq for InnerSizeWriter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Could not write the inner size.
|
||||
pub struct InnerSizeIgnored {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl fmt::Debug for InnerSizeIgnored {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("InnerSizeIgnored").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InnerSizeIgnored {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("tried to write inner size after")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InnerSizeIgnored {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::event;
|
||||
@@ -1199,13 +1226,13 @@ mod tests {
|
||||
state: event::ElementState::Pressed,
|
||||
button: event::MouseButton::Other(0),
|
||||
});
|
||||
with_window_event(TouchpadMagnify {
|
||||
with_window_event(PinchGesture {
|
||||
device_id: did,
|
||||
delta: 0.0,
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(SmartMagnify { device_id: did });
|
||||
with_window_event(TouchpadRotate {
|
||||
with_window_event(DoubleTapGesture { device_id: did });
|
||||
with_window_event(RotationGesture {
|
||||
device_id: did,
|
||||
delta: 0.0,
|
||||
phase: event::TouchPhase::Started,
|
||||
@@ -1265,7 +1292,7 @@ mod tests {
|
||||
#[allow(clippy::redundant_clone)]
|
||||
#[test]
|
||||
fn test_event_clone() {
|
||||
foreach_event!(|event: event::Event<()>| {
|
||||
foreach_event!(|event: event::Event<(), ()>| {
|
||||
let event2 = event.clone();
|
||||
assert_eq!(event, event2);
|
||||
})
|
||||
@@ -1273,7 +1300,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_map_nonuser_event() {
|
||||
foreach_event!(|event: event::Event<()>| {
|
||||
foreach_event!(|event: event::Event<(), ()>| {
|
||||
let is_user = matches!(event, event::Event::UserEvent(()));
|
||||
let event2 = event.map_nonuser_event::<()>();
|
||||
if is_user {
|
||||
@@ -1307,7 +1334,7 @@ mod tests {
|
||||
#[allow(clippy::clone_on_copy)]
|
||||
#[test]
|
||||
fn ensure_attrs_do_not_panic() {
|
||||
foreach_event!(|event: event::Event<()>| {
|
||||
foreach_event!(|event: event::Event<(), ()>| {
|
||||
let _ = format!("{:?}", event);
|
||||
});
|
||||
let _ = event::StartCause::Init.clone();
|
||||
80
winit-core/src/event_loop.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
//! Types needed to define the event loop.
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
#[cfg(not(web_platform))]
|
||||
use std::time::{Duration, Instant};
|
||||
#[cfg(web_platform)]
|
||||
use web_time::{Duration, Instant};
|
||||
|
||||
/// A unique identifier of the winit's async request.
|
||||
///
|
||||
/// This could be used to identify the async request once it's done
|
||||
/// and a specific action must be taken.
|
||||
///
|
||||
/// One of the handling scenarious could be to maintain a working list
|
||||
/// containing [`AsyncRequestSerial`] and some closure associated with it.
|
||||
/// Then once event is arriving the working list is being traversed and a job
|
||||
/// executed and removed from the list.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AsyncRequestSerial {
|
||||
serial: u64,
|
||||
}
|
||||
|
||||
impl AsyncRequestSerial {
|
||||
/// Get the next serial in the sequence.
|
||||
pub fn get() -> Self {
|
||||
static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0);
|
||||
// NOTE: we rely on wrap around here, while the user may just request
|
||||
// in the loop u64::MAX times that's issue is considered on them.
|
||||
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
|
||||
Self { serial }
|
||||
}
|
||||
}
|
||||
|
||||
/// Set through [`EventLoopWindowTarget::set_control_flow()`].
|
||||
///
|
||||
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
|
||||
///
|
||||
/// Defaults to [`Wait`].
|
||||
///
|
||||
/// [`Wait`]: Self::Wait
|
||||
///
|
||||
/// [`EventLoopWindowTarget::set_control_flow()`]: https://docs.rs/winit/latest/winit/event_loop/struct.EventLoopWindowTarget.html#method.set_control_flow
|
||||
/// [`Event::AboutToWait`]: crate::event::Event::AboutToWait
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum ControlFlow {
|
||||
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
||||
/// whether or not new events are available to process.
|
||||
Poll,
|
||||
|
||||
/// When the current loop iteration finishes, suspend the thread until another event arrives.
|
||||
#[default]
|
||||
Wait,
|
||||
|
||||
/// When the current loop iteration finishes, suspend the thread until either another event
|
||||
/// arrives or the given time is reached.
|
||||
///
|
||||
/// Useful for implementing efficient timers. Applications which want to render at the display's
|
||||
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API
|
||||
/// to reduce odds of missed frames.
|
||||
///
|
||||
/// [`Poll`]: Self::Poll
|
||||
WaitUntil(Instant),
|
||||
}
|
||||
|
||||
impl ControlFlow {
|
||||
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
|
||||
///
|
||||
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
|
||||
/// instead set to [`Wait`].
|
||||
///
|
||||
/// [`WaitUntil`]: Self::WaitUntil
|
||||
/// [`Wait`]: Self::Wait
|
||||
pub fn wait_duration(timeout: Duration) -> Self {
|
||||
match Instant::now().checked_add(timeout) {
|
||||
Some(instant) => Self::WaitUntil(instant),
|
||||
None => Self::Wait,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,9 @@
|
||||
//
|
||||
// --------- END OF W3C SHORT NOTICE ---------------------------------------------------------------
|
||||
|
||||
use bitflags::bitflags;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use smol_str::SmolStr;
|
||||
|
||||
/// Contains the platform-native physical key identifier
|
||||
@@ -1451,6 +1454,29 @@ pub enum NamedKey {
|
||||
F35,
|
||||
}
|
||||
|
||||
// NOTE: the exact modifier key is not used to represent modifiers state in the
|
||||
// first place due to a fact that modifiers state could be changed without any
|
||||
// key being pressed and on some platforms like Wayland/X11 which key resulted
|
||||
// in modifiers change is hidden, also, not that it really matters.
|
||||
//
|
||||
// The reason this API is even exposed is mostly to provide a way for users
|
||||
// to treat modifiers differently based on their position, which is required
|
||||
// on macOS due to their AltGr/Option situation.
|
||||
bitflags! {
|
||||
#[doc(hidden)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ModifiersKeys: u8 {
|
||||
const LSHIFT = 0b0000_0001;
|
||||
const RSHIFT = 0b0000_0010;
|
||||
const LCONTROL = 0b0000_0100;
|
||||
const RCONTROL = 0b0000_1000;
|
||||
const LALT = 0b0001_0000;
|
||||
const RALT = 0b0010_0000;
|
||||
const LSUPER = 0b0100_0000;
|
||||
const RSUPER = 0b1000_0000;
|
||||
}
|
||||
}
|
||||
|
||||
/// Key represents the meaning of a keypress.
|
||||
///
|
||||
/// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
|
||||
@@ -1562,7 +1588,7 @@ impl NamedKey {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use winit::keyboard::NamedKey;
|
||||
/// use winit_core::keyboard::NamedKey;
|
||||
///
|
||||
/// assert_eq!(NamedKey::Enter.to_text(), Some("\r"));
|
||||
/// assert_eq!(NamedKey::F20.to_text(), None);
|
||||
@@ -1585,7 +1611,7 @@ impl Key {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use winit::keyboard::{NamedKey, Key};
|
||||
/// use winit_core::keyboard::{NamedKey, Key};
|
||||
///
|
||||
/// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
|
||||
/// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
|
||||
@@ -1724,28 +1750,6 @@ pub enum ModifiersKeyState {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
// NOTE: the exact modifier key is not used to represent modifiers state in the
|
||||
// first place due to a fact that modifiers state could be changed without any
|
||||
// key being pressed and on some platforms like Wayland/X11 which key resulted
|
||||
// in modifiers change is hidden, also, not that it really matters.
|
||||
//
|
||||
// The reason this API is even exposed is mostly to provide a way for users
|
||||
// to treat modifiers differently based on their position, which is required
|
||||
// on macOS due to their AltGr/Option situation.
|
||||
bitflags! {
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct ModifiersKeys: u8 {
|
||||
const LSHIFT = 0b0000_0001;
|
||||
const RSHIFT = 0b0000_0010;
|
||||
const LCONTROL = 0b0000_0100;
|
||||
const RCONTROL = 0b0000_1000;
|
||||
const LALT = 0b0001_0000;
|
||||
const RALT = 0b0010_0000;
|
||||
const LSUPER = 0b0100_0000;
|
||||
const RSUPER = 0b1000_0000;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
mod modifiers_serde {
|
||||
use super::ModifiersState;
|
||||
18
winit-core/src/lib.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
//! Base types for a windowing library.
|
||||
//!
|
||||
//! This crate contains types, traits and basic functions from [`winit`] that are platform
|
||||
//! independent. It is intended to allow for other crates to build abstractions around [`winit`]
|
||||
//! without needing to pull in all of [`winit`]'s dependencies, as well as to provide an
|
||||
//! interface for alternative backends for [`winit`] to be constructed.
|
||||
//!
|
||||
//! [`winit`]: https://docs.rs/winit
|
||||
|
||||
#[cfg(any(not(feature = "std"), not(feature = "alloc")))]
|
||||
compile_error! { "no-std and no-alloc usage are not yet supported" }
|
||||
|
||||
pub mod dpi;
|
||||
pub mod error;
|
||||
pub mod event;
|
||||
pub mod event_loop;
|
||||
pub mod keyboard;
|
||||
pub mod window;
|
||||
229
winit-core/src/window.rs
Normal file
@@ -0,0 +1,229 @@
|
||||
//! Types used in window construction.
|
||||
|
||||
#[doc(inline)]
|
||||
pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
pub use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Identifier of a window. Unique for each window.
|
||||
///
|
||||
/// Can be obtained with [`window.id()`](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.id).
|
||||
///
|
||||
/// Whenever you receive an event specific to a window, this event contains a `WindowId` which you
|
||||
/// can then compare to the ids of your windows.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(u64);
|
||||
|
||||
impl WindowId {
|
||||
/// Returns a dummy id, useful for unit testing.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The only guarantee made about the return value of this function is that
|
||||
/// it will always be equal to itself and to future values returned by this function.
|
||||
/// No other guarantees are made. This may be equal to a real [`WindowId`].
|
||||
///
|
||||
/// **Passing this into a winit function will result in undefined behavior.**
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
WindowId(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WindowId> for u64 {
|
||||
fn from(window_id: WindowId) -> Self {
|
||||
window_id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for WindowId {
|
||||
fn from(raw_id: u64) -> Self {
|
||||
Self(raw_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// The behavior of cursor grabbing.
|
||||
///
|
||||
/// Use this enum with [`Window::set_cursor_grab`] to grab the cursor.
|
||||
///
|
||||
/// [`Window::set_cursor_grab`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_cursor_grab
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum CursorGrabMode {
|
||||
/// No grabbing of the cursor is performed.
|
||||
None,
|
||||
|
||||
/// The cursor is confined to the window area.
|
||||
///
|
||||
/// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you
|
||||
/// want to do so.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
|
||||
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
|
||||
///
|
||||
/// [`ExternalError::NotSupported`]: https://docs.rs/winit/latest/winit/error/enum.ExternalError.html#variant.NotSupported
|
||||
Confined,
|
||||
|
||||
/// The cursor is locked inside the window area to the certain position.
|
||||
///
|
||||
/// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you
|
||||
/// want to do so.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
|
||||
/// - **iOS / Android / Orbital:** Always returns an [`ExternalError::NotSupported`].
|
||||
///
|
||||
/// [`ExternalError::NotSupported`]: https://docs.rs/winit/latest/winit/error/enum.ExternalError.html#variant.NotSupported
|
||||
Locked,
|
||||
}
|
||||
|
||||
/// Defines the orientation that a window resize will be performed.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ResizeDirection {
|
||||
East,
|
||||
North,
|
||||
NorthEast,
|
||||
NorthWest,
|
||||
South,
|
||||
SouthEast,
|
||||
SouthWest,
|
||||
West,
|
||||
}
|
||||
|
||||
impl From<ResizeDirection> for CursorIcon {
|
||||
fn from(direction: ResizeDirection) -> Self {
|
||||
use ResizeDirection::*;
|
||||
match direction {
|
||||
East => CursorIcon::EResize,
|
||||
North => CursorIcon::NResize,
|
||||
NorthEast => CursorIcon::NeResize,
|
||||
NorthWest => CursorIcon::NwResize,
|
||||
South => CursorIcon::SResize,
|
||||
SouthEast => CursorIcon::SeResize,
|
||||
SouthWest => CursorIcon::SwResize,
|
||||
West => CursorIcon::WResize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The theme variant to use.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Theme {
|
||||
/// Use the light variant.
|
||||
Light,
|
||||
|
||||
/// Use the dark variant.
|
||||
Dark,
|
||||
}
|
||||
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`].
|
||||
///
|
||||
/// [`Critical`]: Self::Critical
|
||||
/// [`Informational`]: Self::Informational
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub enum UserAttentionType {
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** Bounces the dock icon until the application is in focus.
|
||||
/// - **Windows:** Flashes both the window and the taskbar button until the application is in focus.
|
||||
Critical,
|
||||
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** Bounces the dock icon once.
|
||||
/// - **Windows:** Flashes the taskbar button until the application is in focus.
|
||||
#[default]
|
||||
Informational,
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct WindowButtons: u32 {
|
||||
const CLOSE = 1 << 0;
|
||||
const MINIMIZE = 1 << 1;
|
||||
const MAXIMIZE = 1 << 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// A window level groups windows with respect to their z-position.
|
||||
///
|
||||
/// The relative ordering between windows in different window levels is fixed.
|
||||
/// The z-order of a window within the same window level may change dynamically on user interaction.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Wayland:** Unsupported.
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum WindowLevel {
|
||||
/// The window will always be below normal windows.
|
||||
///
|
||||
/// This is useful for a widget-based app.
|
||||
AlwaysOnBottom,
|
||||
|
||||
/// The default.
|
||||
#[default]
|
||||
Normal,
|
||||
|
||||
/// The window will always be on top of normal windows.
|
||||
AlwaysOnTop,
|
||||
}
|
||||
|
||||
/// Generic IME purposes for use in [`Window::set_ime_purpose`].
|
||||
///
|
||||
/// The purpose may improve UX by optimizing the IME for the specific use case,
|
||||
/// if winit can express the purpose to the platform and the platform reacts accordingly.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported.
|
||||
///
|
||||
/// [`Window::set_ime_purpose`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_purpose
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
pub enum ImePurpose {
|
||||
/// No special hints for the IME (default).
|
||||
Normal,
|
||||
/// The IME is used for password input.
|
||||
Password,
|
||||
/// The IME is used to input into a terminal.
|
||||
///
|
||||
/// For example, that could alter OSK on Wayland to show extra buttons.
|
||||
Terminal,
|
||||
}
|
||||
|
||||
impl Default for ImePurpose {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
}
|
||||
}
|
||||
|
||||
/// An stringly-typed token used to activate the [`Window`].
|
||||
///
|
||||
/// [`Window`]: https://docs.rs/winit/latest/winit/window/struct.Window.html
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct ActivationToken {
|
||||
pub(crate) token: String,
|
||||
}
|
||||
|
||||
impl ActivationToken {
|
||||
/// Create a new [`ActivationToken`].
|
||||
pub fn new(token: String) -> Self {
|
||||
Self { token }
|
||||
}
|
||||
|
||||
/// Get the underlying token.
|
||||
pub fn token(&self) -> &str {
|
||||
&self.token
|
||||
}
|
||||
|
||||
/// Convert into the underlying token.
|
||||
pub fn into_token(self) -> String {
|
||||
self.token
|
||||
}
|
||||
}
|
||||
261
winit/Cargo.toml
Normal file
@@ -0,0 +1,261 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.29.10"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2021"
|
||||
keywords = ["windowing"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
rust-version = "1.70.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [
|
||||
"rwh_04",
|
||||
"rwh_05",
|
||||
"rwh_06",
|
||||
"serde",
|
||||
"mint",
|
||||
# Enabled to get docs to compile
|
||||
"android-native-activity",
|
||||
]
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
# These are all tested in CI
|
||||
targets = [
|
||||
# Windows
|
||||
"i686-pc-windows-msvc",
|
||||
"x86_64-pc-windows-msvc",
|
||||
# macOS
|
||||
"x86_64-apple-darwin",
|
||||
# Unix (X11 & Wayland)
|
||||
"i686-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
# iOS
|
||||
"x86_64-apple-ios",
|
||||
# Android
|
||||
"aarch64-linux-android",
|
||||
# Web
|
||||
"wasm32-unknown-unknown",
|
||||
]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
|
||||
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
|
||||
wayland-dlopen = ["wayland-backend/dlopen"]
|
||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
|
||||
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
|
||||
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
|
||||
android-native-activity = ["android-activity/native-activity"]
|
||||
android-game-activity = ["android-activity/game-activity"]
|
||||
mint = ["winit-core/mint"]
|
||||
serde = ["dep:serde", "winit-core/serde", "cursor-icon/serde", "smol_str/serde"]
|
||||
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
|
||||
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
|
||||
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
|
||||
|
||||
[build-dependencies]
|
||||
cfg_aliases.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
cursor-icon.workspace = true
|
||||
log = "0.4"
|
||||
once_cell = "1.12"
|
||||
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
|
||||
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true }
|
||||
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
smol_str.workspace = true
|
||||
winit-core.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
image = { version = "0.24.0", default-features = false, features = ["png"] }
|
||||
simple_logger = { version = "4.2.0", default_features = false }
|
||||
winit = { path = ".", features = ["rwh_05"] }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
|
||||
softbuffer = { version = "0.3.0", default-features = false, features = ["x11", "x11-dlopen", "wayland", "wayland-dlopen"] }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android-activity = "0.5.0"
|
||||
ndk = { version = "0.8.0", default-features = false }
|
||||
ndk-sys = "0.5.0"
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
core-foundation = "0.9.3"
|
||||
objc2 = "0.5.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.23.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.icrate]
|
||||
version = "0.1.0"
|
||||
features = [
|
||||
"dispatch",
|
||||
"Foundation",
|
||||
"Foundation_NSArray",
|
||||
"Foundation_NSAttributedString",
|
||||
"Foundation_NSMutableAttributedString",
|
||||
"Foundation_NSData",
|
||||
"Foundation_NSDictionary",
|
||||
"Foundation_NSString",
|
||||
"Foundation_NSProcessInfo",
|
||||
"Foundation_NSThread",
|
||||
"Foundation_NSNumber",
|
||||
"AppKit",
|
||||
"AppKit_NSAppearance",
|
||||
"AppKit_NSApplication",
|
||||
"AppKit_NSBitmapImageRep",
|
||||
"AppKit_NSButton",
|
||||
"AppKit_NSColor",
|
||||
"AppKit_NSControl",
|
||||
"AppKit_NSCursor",
|
||||
"AppKit_NSEvent",
|
||||
"AppKit_NSGraphicsContext",
|
||||
"AppKit_NSImage",
|
||||
"AppKit_NSImageRep",
|
||||
"AppKit_NSMenu",
|
||||
"AppKit_NSMenuItem",
|
||||
"AppKit_NSPasteboard",
|
||||
"AppKit_NSResponder",
|
||||
"AppKit_NSScreen",
|
||||
"AppKit_NSTextInputContext",
|
||||
"AppKit_NSView",
|
||||
"AppKit_NSWindow",
|
||||
"AppKit_NSWindowTabGroup",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies.icrate]
|
||||
version = "0.1.0"
|
||||
features = [
|
||||
"dispatch",
|
||||
"Foundation",
|
||||
"Foundation_NSArray",
|
||||
"Foundation_NSString",
|
||||
"Foundation_NSProcessInfo",
|
||||
"Foundation_NSThread",
|
||||
"Foundation_NSSet",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
unicode-segmentation = "1.7.1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||
version = "0.48"
|
||||
features = [
|
||||
"Win32_Devices_HumanInterfaceDevice",
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Media",
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Ole",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_WindowsProgramming",
|
||||
"Win32_UI_Accessibility",
|
||||
"Win32_UI_Controls",
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_Input_Ime",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Input_Pointer",
|
||||
"Win32_UI_Input_Touch",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_TextServices",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
]
|
||||
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
|
||||
ahash = { version = "0.8.3", features = ["no-rng"], optional = true }
|
||||
bytemuck = { version = "1.13.1", default-features = false, optional = true }
|
||||
calloop = "0.12.3"
|
||||
libc = "0.2.64"
|
||||
memmap2 = { version = "0.9.0", optional = true }
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
|
||||
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
|
||||
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
|
||||
wayland-client = { version = "0.31.1", optional = true }
|
||||
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
|
||||
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
||||
xkbcommon-dl = "0.4.0"
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
orbclient = { version = "0.3.47", default-features = false }
|
||||
redox_syscall = "0.4.1"
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
|
||||
package = "web-sys"
|
||||
version = "0.3.64"
|
||||
features = [
|
||||
'AbortController',
|
||||
'AbortSignal',
|
||||
'Blob',
|
||||
'console',
|
||||
'CssStyleDeclaration',
|
||||
'Document',
|
||||
'DomException',
|
||||
'DomRect',
|
||||
'DomRectReadOnly',
|
||||
'Element',
|
||||
'Event',
|
||||
'EventTarget',
|
||||
'FocusEvent',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'HtmlImageElement',
|
||||
'ImageBitmap',
|
||||
'ImageBitmapOptions',
|
||||
'ImageBitmapRenderingContext',
|
||||
'ImageData',
|
||||
'IntersectionObserver',
|
||||
'IntersectionObserverEntry',
|
||||
'KeyboardEvent',
|
||||
'MediaQueryList',
|
||||
'MessageChannel',
|
||||
'MessagePort',
|
||||
'Node',
|
||||
'PageTransitionEvent',
|
||||
'PointerEvent',
|
||||
'PremultiplyAlpha',
|
||||
'ResizeObserver',
|
||||
'ResizeObserverBoxOptions',
|
||||
'ResizeObserverEntry',
|
||||
'ResizeObserverOptions',
|
||||
'ResizeObserverSize',
|
||||
'VisibilityState',
|
||||
'Window',
|
||||
'WheelEvent',
|
||||
'Url',
|
||||
]
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
js-sys = "0.3.64"
|
||||
pin-project = "1"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-time.workspace = true
|
||||
|
||||
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
|
||||
atomic-waker = "1"
|
||||
concurrent-queue = { version = "2", default-features = false }
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dev-dependencies]
|
||||
console_log = "1"
|
||||
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
|
||||
|
||||
[[example]]
|
||||
doc-scrape-examples = true
|
||||
name = "window"
|
||||
1
winit/README.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../README.md
|
||||
24
winit/build.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use cfg_aliases::cfg_aliases;
|
||||
|
||||
fn main() {
|
||||
// The script doesn't depend on our code
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// Setup cfg aliases
|
||||
cfg_aliases! {
|
||||
// Systems.
|
||||
android_platform: { target_os = "android" },
|
||||
web_platform: { all(target_family = "wasm", target_os = "unknown") },
|
||||
macos_platform: { target_os = "macos" },
|
||||
ios_platform: { target_os = "ios" },
|
||||
windows_platform: { target_os = "windows" },
|
||||
apple: { any(target_os = "ios", target_os = "macos") },
|
||||
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
|
||||
redox: { target_os = "redox" },
|
||||
|
||||
// Native displays.
|
||||
x11_platform: { all(feature = "x11", free_unix, not(redox)) },
|
||||
wayland_platform: { all(feature = "wayland", free_unix, not(redox)) },
|
||||
orbital_platform: { redox },
|
||||
}
|
||||
}
|
||||
@@ -17,22 +17,23 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
dpi::{LogicalPosition, LogicalSize, Position},
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
raw_window_handle::HasWindowHandle,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
raw_window_handle::HasRawWindowHandle,
|
||||
window::{Window, WindowId},
|
||||
};
|
||||
|
||||
fn spawn_child_window(
|
||||
parent: &Window,
|
||||
event_loop: &EventLoopWindowTarget<()>,
|
||||
event_loop: &EventLoopWindowTarget,
|
||||
windows: &mut HashMap<WindowId, Window>,
|
||||
) {
|
||||
let parent = parent.window_handle().unwrap();
|
||||
let mut builder = WindowBuilder::new()
|
||||
let parent = parent.raw_window_handle().unwrap();
|
||||
let mut builder = Window::builder()
|
||||
.with_title("child window")
|
||||
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
|
||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
||||
.with_visible(true);
|
||||
builder = builder.with_parent_window(Some(parent));
|
||||
// `with_parent_window` is unsafe. Parent window must be a valid window.
|
||||
builder = unsafe { builder.with_parent_window(Some(parent)) };
|
||||
let child_window = builder.build(event_loop).unwrap();
|
||||
|
||||
let id = child_window.id();
|
||||
@@ -43,7 +44,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
let mut windows = HashMap::new();
|
||||
|
||||
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
|
||||
let parent_window = WindowBuilder::new()
|
||||
let parent_window = Window::builder()
|
||||
.with_title("parent window")
|
||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
||||
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32))
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use std::thread;
|
||||
#[cfg(not(wasm_platform))]
|
||||
#[cfg(not(web_platform))]
|
||||
use std::time;
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(web_platform)]
|
||||
use web_time as time;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
@@ -11,7 +11,7 @@ use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::{Key, NamedKey},
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -37,7 +37,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
println!("Press 'Esc' to close the window.");
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::{CursorIcon, WindowBuilder},
|
||||
window::{CursorIcon, Window},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let window = Window::builder().build(&event_loop).unwrap();
|
||||
window.set_title("A fantastic window!");
|
||||
|
||||
let mut cursor_idx = 0;
|
||||
@@ -31,7 +31,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
..
|
||||
} => {
|
||||
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
|
||||
window.set_cursor_icon(CURSORS[cursor_idx]);
|
||||
window.set_cursor(CURSORS[cursor_idx]);
|
||||
if cursor_idx < CURSORS.len() - 1 {
|
||||
cursor_idx += 1;
|
||||
} else {
|
||||
@@ -5,7 +5,7 @@ use winit::{
|
||||
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::{Key, ModifiersState, NamedKey},
|
||||
window::{CursorGrabMode, WindowBuilder},
|
||||
window::{CursorGrabMode, Window},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -15,7 +15,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
143
winit/examples/custom_cursors.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
#![allow(clippy::single_match, clippy::disallowed_methods)]
|
||||
|
||||
#[cfg(not(web_platform))]
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
keyboard::Key,
|
||||
window::{CursorIcon, CustomCursor, Window},
|
||||
};
|
||||
#[cfg(web_platform)]
|
||||
use {
|
||||
std::sync::atomic::{AtomicU64, Ordering},
|
||||
std::time::Duration,
|
||||
winit::platform::web::CustomCursorExtWebSys,
|
||||
};
|
||||
|
||||
#[cfg(web_platform)]
|
||||
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
fn decode_cursor(bytes: &[u8], window_target: &EventLoopWindowTarget) -> CustomCursor {
|
||||
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
|
||||
let samples = img.into_flat_samples();
|
||||
let (_, w, h) = samples.extents();
|
||||
let (w, h) = (w as u16, h as u16);
|
||||
let builder = CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap();
|
||||
|
||||
builder.build(window_target)
|
||||
}
|
||||
|
||||
#[cfg(not(web_platform))]
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
#[cfg(not(web_platform))]
|
||||
SimpleLogger::new()
|
||||
.with_level(log::LevelFilter::Info)
|
||||
.init()
|
||||
.unwrap();
|
||||
#[cfg(web_platform)]
|
||||
console_log::init_with_level(log::Level::Debug).unwrap();
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let builder = Window::builder().with_title("A fantastic window!");
|
||||
#[cfg(web_platform)]
|
||||
let builder = {
|
||||
use winit::platform::web::WindowBuilderExtWebSys;
|
||||
builder.with_append(true)
|
||||
};
|
||||
let window = builder.build(&event_loop).unwrap();
|
||||
|
||||
let mut cursor_idx = 0;
|
||||
let mut cursor_visible = true;
|
||||
|
||||
let custom_cursors = [
|
||||
decode_cursor(include_bytes!("data/cross.png"), &event_loop),
|
||||
decode_cursor(include_bytes!("data/cross2.png"), &event_loop),
|
||||
decode_cursor(include_bytes!("data/gradient.png"), &event_loop),
|
||||
];
|
||||
|
||||
event_loop.run(move |event, _elwt| match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
state: ElementState::Pressed,
|
||||
logical_key: key,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key.as_ref() {
|
||||
Key::Character("1") => {
|
||||
log::debug!("Setting cursor to {:?}", cursor_idx);
|
||||
window.set_cursor(custom_cursors[cursor_idx].clone());
|
||||
cursor_idx = (cursor_idx + 1) % 3;
|
||||
}
|
||||
Key::Character("2") => {
|
||||
log::debug!("Setting cursor icon to default");
|
||||
window.set_cursor(CursorIcon::default());
|
||||
}
|
||||
Key::Character("3") => {
|
||||
cursor_visible = !cursor_visible;
|
||||
log::debug!("Setting cursor visibility to {:?}", cursor_visible);
|
||||
window.set_cursor_visible(cursor_visible);
|
||||
}
|
||||
#[cfg(web_platform)]
|
||||
Key::Character("4") => {
|
||||
log::debug!("Setting cursor to a random image from an URL");
|
||||
window.set_cursor(
|
||||
CustomCursor::from_url(
|
||||
format!(
|
||||
"https://picsum.photos/128?random={}",
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
),
|
||||
64,
|
||||
64,
|
||||
)
|
||||
.build(_elwt),
|
||||
);
|
||||
}
|
||||
#[cfg(web_platform)]
|
||||
Key::Character("5") => {
|
||||
log::debug!("Setting cursor to an animation");
|
||||
window.set_cursor(
|
||||
CustomCursor::from_animation(
|
||||
Duration::from_secs(3),
|
||||
vec![
|
||||
custom_cursors[0].clone(),
|
||||
custom_cursors[1].clone(),
|
||||
CustomCursor::from_url(
|
||||
format!(
|
||||
"https://picsum.photos/128?random={}",
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
),
|
||||
64,
|
||||
64,
|
||||
)
|
||||
.build(_elwt),
|
||||
],
|
||||
)
|
||||
.unwrap()
|
||||
.build(_elwt),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
#[cfg(not(web_platform))]
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
#[cfg(not(web_platform))]
|
||||
_elwt.exit();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
#[cfg(not(web_platform))]
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoopBuilder,
|
||||
window::WindowBuilder,
|
||||
event_loop::EventLoop,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -18,11 +18,9 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
}
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event()
|
||||
.build()
|
||||
.unwrap();
|
||||
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -56,7 +54,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(web_platform)]
|
||||
fn main() {
|
||||
panic!("This example is not supported on web.");
|
||||
}
|
||||
|
Before Width: | Height: | Size: 159 B After Width: | Height: | Size: 159 B |
|
Before Width: | Height: | Size: 129 B After Width: | Height: | Size: 129 B |
BIN
winit/examples/data/gradient.png
Normal file
|
After Width: | Height: | Size: 229 B |
@@ -5,7 +5,7 @@ use winit::{
|
||||
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
window::{Window, WindowId},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -15,8 +15,8 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let window_1 = Window::builder().build(&event_loop).unwrap();
|
||||
let window_2 = Window::builder().build(&event_loop).unwrap();
|
||||
|
||||
let mut switched = false;
|
||||
let mut entered_id = window_2.id();
|
||||
@@ -3,14 +3,14 @@
|
||||
//! Example for focusing a window.
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
#[cfg(not(wasm_platform))]
|
||||
#[cfg(not(web_platform))]
|
||||
use std::time;
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(web_platform)]
|
||||
use web_time as time;
|
||||
use winit::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -20,7 +20,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(&event_loop)
|
||||
@@ -1,11 +1,11 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::keyboard::{Key, NamedKey};
|
||||
use winit::window::{Fullscreen, WindowBuilder};
|
||||
use winit::window::{Fullscreen, Window};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::platform::macos::WindowExtMacOS;
|
||||
@@ -22,7 +22,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
let mut with_min_size = false;
|
||||
let mut with_max_size = false;
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("Hello world!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -126,7 +126,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
"i" => {
|
||||
with_min_size = !with_min_size;
|
||||
let min_size = if with_min_size {
|
||||
Some(PhysicalSize::new(100, 100))
|
||||
Some(LogicalSize::new(100, 100))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -139,7 +139,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
"a" => {
|
||||
with_max_size = !with_max_size;
|
||||
let max_size = if with_max_size {
|
||||
Some(PhysicalSize::new(200, 200))
|
||||
Some(LogicalSize::new(200, 200))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -5,7 +5,7 @@ use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -15,7 +15,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("Your faithful window")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -7,7 +7,7 @@ use winit::{
|
||||
event::{ElementState, Event, Ime, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::NamedKey,
|
||||
window::{ImePurpose, WindowBuilder},
|
||||
window::{ImePurpose, Window},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -26,7 +26,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -8,7 +8,7 @@ use winit::{
|
||||
keyboard::{Key, ModifiersState},
|
||||
// WARNING: This is not available on all platforms (for example on the web).
|
||||
platform::modifier_supplement::KeyEventExtModifierSupplement,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
@@ -24,7 +24,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
simple_logger::SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_inner_size(LogicalSize::new(400.0, 200.0))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -3,12 +3,12 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use winit::monitor::MonitorHandle;
|
||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
||||
use winit::{event_loop::EventLoop, window::Window};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let window = Window::builder().build(&event_loop).unwrap();
|
||||
|
||||
if let Some(mon) = window.primary_monitor() {
|
||||
print_info("Primary output", mon);
|
||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("Mouse Wheel events")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
#[cfg(not(web_platform))]
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||
|
||||
@@ -10,7 +10,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::{Key, ModifiersState, NamedKey},
|
||||
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel},
|
||||
window::{CursorGrabMode, CursorIcon, Fullscreen, Window, WindowLevel},
|
||||
};
|
||||
|
||||
const WINDOW_COUNT: usize = 3;
|
||||
@@ -20,7 +20,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
|
||||
for _ in 0..WINDOW_COUNT {
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_inner_size(WINDOW_SIZE)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -84,7 +84,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
"1" => window.set_window_level(WindowLevel::AlwaysOnTop),
|
||||
"2" => window.set_window_level(WindowLevel::AlwaysOnBottom),
|
||||
"3" => window.set_window_level(WindowLevel::Normal),
|
||||
"c" => window.set_cursor_icon(match state {
|
||||
"c" => window.set_cursor(match state {
|
||||
true => CursorIcon::Progress,
|
||||
false => CursorIcon::Default,
|
||||
}),
|
||||
@@ -96,21 +96,13 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
)),
|
||||
(false, _) => None,
|
||||
}),
|
||||
"l" if state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked)
|
||||
{
|
||||
println!("error: {err}");
|
||||
}
|
||||
}
|
||||
"g" if state => {
|
||||
if let Err(err) =
|
||||
window.set_cursor_grab(CursorGrabMode::Confined)
|
||||
{
|
||||
println!("error: {err}");
|
||||
}
|
||||
}
|
||||
"g" | "l" if !state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
|
||||
ch @ ("g" | "l") => {
|
||||
let mode = match (ch, state) {
|
||||
("l", true) => CursorGrabMode::Locked,
|
||||
("g", true) => CursorGrabMode::Confined,
|
||||
(_, _) => CursorGrabMode::None,
|
||||
};
|
||||
if let Err(err) = window.set_cursor_grab(mode) {
|
||||
println!("error: {err}");
|
||||
}
|
||||
}
|
||||
@@ -123,10 +115,6 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
println!("-> inner_size : {:?}", window.inner_size());
|
||||
println!("-> fullscreen : {:?}", window.fullscreen());
|
||||
}
|
||||
"l" => window.set_min_inner_size(match state {
|
||||
true => Some(WINDOW_SIZE),
|
||||
false => None,
|
||||
}),
|
||||
"m" => window.set_maximized(state),
|
||||
"p" => window.set_outer_position({
|
||||
let mut position = window.outer_position().unwrap();
|
||||
@@ -140,12 +128,26 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
"s" => {
|
||||
let _ = window.request_inner_size(match state {
|
||||
true => PhysicalSize::new(
|
||||
WINDOW_SIZE.width + 100,
|
||||
WINDOW_SIZE.height + 100,
|
||||
WINDOW_SIZE.width + 50,
|
||||
WINDOW_SIZE.height + 50,
|
||||
),
|
||||
false => WINDOW_SIZE,
|
||||
});
|
||||
}
|
||||
"k" => window.set_min_inner_size(match state {
|
||||
true => Some(PhysicalSize::new(
|
||||
WINDOW_SIZE.width - 100,
|
||||
WINDOW_SIZE.height - 100,
|
||||
)),
|
||||
false => None,
|
||||
}),
|
||||
"o" => window.set_max_inner_size(match state {
|
||||
true => Some(PhysicalSize::new(
|
||||
WINDOW_SIZE.width + 100,
|
||||
WINDOW_SIZE.height + 100,
|
||||
)),
|
||||
false => None,
|
||||
}),
|
||||
"w" => {
|
||||
if let Size::Physical(size) = WINDOW_SIZE.into() {
|
||||
window
|
||||
@@ -203,7 +205,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(web_platform)]
|
||||
fn main() {
|
||||
panic!("Example not supported on Wasm");
|
||||
panic!("Example not supported on Web");
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
#[cfg(not(web_platform))]
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
use std::{sync::Arc, thread, time};
|
||||
|
||||
@@ -8,7 +8,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = {
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -53,7 +53,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(web_platform)]
|
||||
fn main() {
|
||||
unimplemented!() // `Window` can't be sent between threads
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::{KeyCode, PhysicalKey},
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
let mut resizable = false;
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("Hit space to toggle resizability.")
|
||||
.with_inner_size(LogicalSize::new(600.0, 300.0))
|
||||
.with_min_inner_size(LogicalSize::new(400.0, 200.0))
|
||||
@@ -14,7 +14,7 @@ mod example {
|
||||
use winit::platform::startup_notify::{
|
||||
EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify,
|
||||
};
|
||||
use winit::window::{Window, WindowBuilder, WindowId};
|
||||
use winit::window::{Window, WindowId};
|
||||
|
||||
pub(super) fn main() -> Result<(), impl std::error::Error> {
|
||||
// Create the event loop and get the activation token.
|
||||
@@ -84,8 +84,7 @@ mod example {
|
||||
if current_token.is_some() || create_first_window {
|
||||
// Create the initial window.
|
||||
let window = {
|
||||
let mut builder =
|
||||
WindowBuilder::new().with_title(format!("Window {}", counter));
|
||||
let mut builder = Window::builder().with_title(format!("Window {}", counter));
|
||||
|
||||
if let Some(token) = current_token.take() {
|
||||
println!("Creating a window with token {token:?}");
|
||||
@@ -5,7 +5,7 @@ use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::{Theme, WindowBuilder},
|
||||
window::{Theme, Window},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -15,7 +15,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.with_theme(Some(Theme::Dark))
|
||||
.build(&event_loop)
|
||||
@@ -1,16 +1,16 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use std::time::Duration;
|
||||
#[cfg(not(wasm_platform))]
|
||||
#[cfg(not(web_platform))]
|
||||
use std::time::Instant;
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(web_platform)]
|
||||
use web_time::Instant;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -20,7 +20,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -2,7 +2,7 @@ use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -12,32 +12,44 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("Touchpad gestures")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
use winit::platform::ios::WindowExtIOS;
|
||||
window.recognize_doubletap_gesture(true);
|
||||
window.recognize_pinch_gesture(true);
|
||||
window.recognize_rotation_gesture(true);
|
||||
}
|
||||
|
||||
println!("Only supported on macOS at the moment.");
|
||||
println!("Only supported on macOS/iOS at the moment.");
|
||||
|
||||
let mut zoom = 0.0;
|
||||
let mut rotated = 0.0;
|
||||
|
||||
event_loop.run(move |event, elwt| {
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::TouchpadMagnify { delta, .. } => {
|
||||
WindowEvent::PinchGesture { delta, .. } => {
|
||||
zoom += delta;
|
||||
if delta > 0.0 {
|
||||
println!("Zoomed in {delta}");
|
||||
println!("Zoomed in {delta:.5} (now: {zoom:.5})");
|
||||
} else {
|
||||
println!("Zoomed out {delta}");
|
||||
println!("Zoomed out {delta:.5} (now: {zoom:.5})");
|
||||
}
|
||||
}
|
||||
WindowEvent::SmartMagnify { .. } => {
|
||||
WindowEvent::DoubleTapGesture { .. } => {
|
||||
println!("Smart zoom");
|
||||
}
|
||||
WindowEvent::TouchpadRotate { delta, .. } => {
|
||||
WindowEvent::RotationGesture { delta, .. } => {
|
||||
rotated += delta;
|
||||
if delta > 0.0 {
|
||||
println!("Rotated counterclockwise {delta}");
|
||||
println!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})");
|
||||
} else {
|
||||
println!("Rotated clockwise {delta}");
|
||||
println!("Rotated clockwise {delta:.5} (now: {rotated:.5})");
|
||||
}
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_decorations(false)
|
||||
.with_transparent(true)
|
||||
.build(&event_loop)
|
||||
116
winit/examples/util/fill.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
//! Fill the window buffer with a solid color.
|
||||
//!
|
||||
//! Launching a window without drawing to it has unpredictable results varying from platform to
|
||||
//! platform. In order to have well-defined examples, this module provides an easy way to
|
||||
//! fill the window buffer with a solid color.
|
||||
//!
|
||||
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
|
||||
//! also be used to fill the window buffer, but they are more complicated to use.
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::cleanup_window;
|
||||
pub use platform::fill_window;
|
||||
|
||||
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
|
||||
mod platform {
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use softbuffer::{Context, Surface};
|
||||
use winit::window::Window;
|
||||
use winit::window::WindowId;
|
||||
|
||||
thread_local! {
|
||||
// NOTE: You should never do things like that, create context and drop it before
|
||||
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
||||
// ManuallyDrop to prevent destructors from running.
|
||||
//
|
||||
// A static, thread-local map of graphics contexts to open windows.
|
||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
|
||||
}
|
||||
|
||||
/// The graphics context used to draw to a window.
|
||||
struct GraphicsContext {
|
||||
/// The global softbuffer context.
|
||||
context: Context,
|
||||
|
||||
/// The hash map of window IDs to surfaces.
|
||||
surfaces: HashMap<WindowId, Surface>,
|
||||
}
|
||||
|
||||
impl GraphicsContext {
|
||||
fn new(w: &Window) -> Self {
|
||||
Self {
|
||||
context: unsafe { Context::new(w) }.expect("Failed to create a softbuffer context"),
|
||||
surfaces: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_surface(&mut self, window: &Window) -> &mut Surface {
|
||||
self.surfaces.entry(window.id()).or_insert_with(|| {
|
||||
unsafe { Surface::new(&self.context, window) }
|
||||
.expect("Failed to create a softbuffer surface")
|
||||
})
|
||||
}
|
||||
|
||||
fn destroy_surface(&mut self, window: &Window) {
|
||||
self.surfaces.remove(&window.id());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_window(window: &Window) {
|
||||
GC.with(|gc| {
|
||||
let size = window.inner_size();
|
||||
let (Some(width), Some(height)) =
|
||||
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface = gc
|
||||
.get_or_insert_with(|| GraphicsContext::new(window))
|
||||
.create_surface(window);
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xFF181818;
|
||||
|
||||
surface
|
||||
.resize(width, height)
|
||||
.expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface
|
||||
.buffer_mut()
|
||||
.expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(DARK_GRAY);
|
||||
buffer
|
||||
.present()
|
||||
.expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(window: &Window) {
|
||||
GC.with(|gc| {
|
||||
let mut gc = gc.borrow_mut();
|
||||
if let Some(context) = gc.as_mut() {
|
||||
context.destroy_surface(window);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
|
||||
mod platform {
|
||||
pub fn fill_window(_window: &winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(_window: &winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,13 @@ use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
window::{Fullscreen, Window},
|
||||
};
|
||||
|
||||
pub fn main() -> Result<(), impl std::error::Error> {
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let builder = WindowBuilder::new().with_title("A fantastic window!");
|
||||
let builder = Window::builder().with_title("A fantastic window!");
|
||||
#[cfg(wasm_platform)]
|
||||
let builder = {
|
||||
use winit::platform::web::WindowBuilderExtWebSys;
|
||||
@@ -18,11 +18,11 @@ pub fn main() -> Result<(), impl std::error::Error> {
|
||||
};
|
||||
let window = builder.build(&event_loop).unwrap();
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(web_platform)]
|
||||
let log_list = wasm::insert_canvas_and_create_log_list(&window);
|
||||
|
||||
event_loop.run(move |event, elwt| {
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(web_platform)]
|
||||
wasm::log_event(&log_list, &event);
|
||||
|
||||
match event {
|
||||
@@ -57,7 +57,7 @@ pub fn main() -> Result<(), impl std::error::Error> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(web_platform)]
|
||||
mod wasm {
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
@@ -4,7 +4,7 @@ pub fn main() {
|
||||
println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
|
||||
}
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(web_platform)]
|
||||
mod wasm {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
@@ -14,7 +14,7 @@ mod wasm {
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
platform::web::WindowBuilderExtWebSys,
|
||||
window::{Window, WindowBuilder},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
const EXPLANATION: &str = "
|
||||
@@ -33,7 +33,7 @@ This example demonstrates the desired future functionality which will possibly b
|
||||
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.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.
|
||||
@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(&event_loop)
|
||||
@@ -8,7 +8,7 @@ use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{DeviceEvents, EventLoop},
|
||||
keyboard::Key,
|
||||
window::{WindowBuilder, WindowButtons},
|
||||
window::{Window, WindowButtons},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(LogicalSize::new(300.0, 300.0))
|
||||
.build(&event_loop)
|
||||
@@ -8,7 +8,7 @@ use winit::{
|
||||
event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent},
|
||||
event_loop::{DeviceEvents, EventLoop},
|
||||
keyboard::{Key, KeyCode, PhysicalKey},
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
window::{Fullscreen, Window},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(LogicalSize::new(100.0, 100.0))
|
||||
.build(&event_loop)
|
||||
@@ -5,7 +5,7 @@ use winit::{
|
||||
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::{CursorIcon, ResizeDirection, WindowBuilder},
|
||||
window::{CursorIcon, ResizeDirection, Window},
|
||||
};
|
||||
|
||||
const BORDER: f64 = 8.0;
|
||||
@@ -17,7 +17,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0))
|
||||
.with_min_inner_size(winit::dpi::LogicalSize::new(400.0, 200.0))
|
||||
.with_decorations(false)
|
||||
@@ -40,7 +40,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
if new_location != cursor_location {
|
||||
cursor_location = new_location;
|
||||
window.set_cursor_icon(cursor_direction_icon(cursor_location))
|
||||
window.set_cursor(cursor_direction_icon(cursor_location))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::{Icon, WindowBuilder},
|
||||
window::{Icon, Window},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -25,7 +25,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("An iconic window!")
|
||||
// At present, this only does anything on Windows and X11, so if you want to save load
|
||||
// time, you can put icon loading behind a function that returns `None` on other platforms.
|
||||
@@ -12,7 +12,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
platform::run_on_demand::EventLoopExtRunOnDemand,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
window::{Window, WindowId},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -40,6 +40,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
window_id,
|
||||
} if window.id() == window_id => {
|
||||
println!("--------------------------------------------------------- Window {idx} CloseRequested");
|
||||
fill::cleanup_window(window);
|
||||
app.window = None;
|
||||
}
|
||||
Event::AboutToWait => window.request_redraw(),
|
||||
@@ -64,7 +65,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
_ => (),
|
||||
}
|
||||
} else if let Event::Resumed = event {
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("Fantastic window number one!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(elwt)
|
||||
@@ -8,7 +8,7 @@ use winit::{
|
||||
event::ElementState,
|
||||
event::{Event, MouseButton, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -21,7 +21,7 @@ mod fill;
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(&event_loop)
|
||||
@@ -16,7 +16,7 @@ fn main() -> std::process::ExitCode {
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -25,7 +25,7 @@ fn main() -> std::process::ExitCode {
|
||||
let mut event_loop = EventLoop::new().unwrap();
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -68,7 +68,7 @@ fn main() -> std::process::ExitCode {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(ios_platform, wasm_platform, orbital_platform))]
|
||||
#[cfg(any(ios_platform, web_platform, orbital_platform))]
|
||||
fn main() {
|
||||
println!("This platform doesn't support pump_events.");
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use winit::{
|
||||
event::{ElementState, Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::NamedKey,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -15,7 +15,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(LogicalSize::new(128.0, 128.0))
|
||||
.with_resize_increments(LogicalSize::new(25.0, 25.0))
|
||||
@@ -11,7 +11,7 @@ use winit::{
|
||||
event_loop::EventLoop,
|
||||
keyboard::{Key, NamedKey},
|
||||
platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS},
|
||||
window::{Window, WindowBuilder},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -60,7 +60,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
} => match logical_key.as_ref() {
|
||||
Key::Character("t") => {
|
||||
let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier();
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_tabbing_identifier(&tabbing_id)
|
||||
.build(elwt)
|
||||
.unwrap();
|
||||
@@ -12,7 +12,7 @@ mod imple {
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
platform::x11::WindowBuilderExtX11,
|
||||
window::WindowBuilder,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -25,7 +25,7 @@ mod imple {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let window = Window::builder()
|
||||
.with_title("An embedded window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.with_embed_parent_window(parent_window_id)
|
||||
@@ -3,6 +3,8 @@ use std::hash::Hasher;
|
||||
use std::sync::Arc;
|
||||
use std::{error::Error, hash::Hash};
|
||||
|
||||
use cursor_icon::CursorIcon;
|
||||
|
||||
use crate::event_loop::EventLoopWindowTarget;
|
||||
use crate::platform_impl::{self, PlatformCustomCursor, PlatformCustomCursorBuilder};
|
||||
|
||||
@@ -11,8 +13,35 @@ pub const MAX_CURSOR_SIZE: u16 = 2048;
|
||||
|
||||
const PIXEL_SIZE: usize = 4;
|
||||
|
||||
/// See [`Window::set_cursor()`](crate::window::Window::set_cursor) for more details.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Cursor {
|
||||
Icon(CursorIcon),
|
||||
Custom(CustomCursor),
|
||||
}
|
||||
|
||||
impl Default for Cursor {
|
||||
fn default() -> Self {
|
||||
Self::Icon(CursorIcon::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CursorIcon> for Cursor {
|
||||
fn from(icon: CursorIcon) -> Self {
|
||||
Self::Icon(icon)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CustomCursor> for Cursor {
|
||||
fn from(custom: CustomCursor) -> Self {
|
||||
Self::Custom(custom)
|
||||
}
|
||||
}
|
||||
|
||||
/// Use a custom image as a cursor (mouse pointer).
|
||||
///
|
||||
/// Is guaranteed to be cheap to clone.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **Web**: Some browsers have limits on cursor sizes usually at 128x128.
|
||||
@@ -44,7 +73,7 @@ const PIXEL_SIZE: usize = 4;
|
||||
/// let custom_cursor = builder.build(&event_loop);
|
||||
///
|
||||
/// let window = Window::new(&event_loop).unwrap();
|
||||
/// window.set_custom_cursor(&custom_cursor);
|
||||
/// window.set_cursor(custom_cursor.clone());
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct CustomCursor {
|
||||
@@ -55,10 +84,7 @@ pub struct CustomCursor {
|
||||
impl CustomCursor {
|
||||
/// Creates a new cursor from an rgba buffer.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Setting cursor could be delayed due to the creation of `Blob` objects,
|
||||
/// which are async by nature.
|
||||
/// The alpha channel is assumed to be **not** premultiplied.
|
||||
pub fn from_rgba(
|
||||
rgba: impl Into<Vec<u8>>,
|
||||
width: u16,
|
||||
@@ -87,7 +113,7 @@ pub struct CustomCursorBuilder {
|
||||
}
|
||||
|
||||
impl CustomCursorBuilder {
|
||||
pub fn build<T>(self, window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
|
||||
pub fn build(self, window_target: &EventLoopWindowTarget) -> CustomCursor {
|
||||
CustomCursor {
|
||||
inner: PlatformCustomCursor::build(self.inner, &window_target.p),
|
||||
}
|
||||
@@ -189,9 +215,9 @@ impl Eq for OnlyCursorImage {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl OnlyCursorImage {
|
||||
fn build<T>(
|
||||
pub(crate) fn build(
|
||||
builder: OnlyCursorImageBuilder,
|
||||
_: &platform_impl::EventLoopWindowTarget<T>,
|
||||
_: &platform_impl::EventLoopWindowTarget,
|
||||
) -> Self {
|
||||
Self(Arc::new(builder.0))
|
||||
}
|
||||
@@ -272,7 +298,7 @@ impl NoCustomCursor {
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn build<T>(self, _: &platform_impl::EventLoopWindowTarget<T>) -> NoCustomCursor {
|
||||
fn build(self, _: &platform_impl::EventLoopWindowTarget) -> NoCustomCursor {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::platform_impl;
|
||||
use std::{error, fmt};
|
||||
|
||||
use crate::platform_impl;
|
||||
#[doc(inline)]
|
||||
pub use winit_core::error::NotSupportedError;
|
||||
|
||||
// TODO: Rename
|
||||
/// An error that may be generated when requesting Winit state
|
||||
@@ -14,10 +16,10 @@ pub enum ExternalError {
|
||||
Os(OsError),
|
||||
}
|
||||
|
||||
/// The error type for when the requested operation is not supported by the backend.
|
||||
#[derive(Clone)]
|
||||
pub struct NotSupportedError {
|
||||
_marker: (),
|
||||
impl From<winit_core::event::InnerSizeIgnored> for ExternalError {
|
||||
fn from(_: winit_core::event::InnerSizeIgnored) -> Self {
|
||||
Self::Ignored
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type for when the OS cannot perform the requested operation.
|
||||
@@ -35,8 +37,6 @@ pub enum EventLoopError {
|
||||
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.
|
||||
@@ -49,14 +49,6 @@ impl From<OsError> for EventLoopError {
|
||||
}
|
||||
}
|
||||
|
||||
impl NotSupportedError {
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn new() -> NotSupportedError {
|
||||
NotSupportedError { _marker: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl OsError {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError {
|
||||
@@ -90,22 +82,9 @@ impl fmt::Display for ExternalError {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NotSupportedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("NotSupportedError").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NotSupportedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.pad("the requested operation is not supported by Winit")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EventLoopError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
EventLoopError::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),
|
||||
@@ -116,7 +95,6 @@ impl fmt::Display for EventLoopError {
|
||||
|
||||
impl error::Error for OsError {}
|
||||
impl error::Error for ExternalError {}
|
||||
impl error::Error for NotSupportedError {}
|
||||
impl error::Error for EventLoopError {}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -128,11 +106,6 @@ mod tests {
|
||||
// Eat attributes for testing
|
||||
#[test]
|
||||
fn ensure_fmt_does_not_panic() {
|
||||
let _ = format!(
|
||||
"{:?}, {}",
|
||||
NotSupportedError::new(),
|
||||
NotSupportedError::new().clone()
|
||||
);
|
||||
let _ = format!(
|
||||
"{:?}, {}",
|
||||
ExternalError::NotSupported(NotSupportedError::new()),
|
||||
53
winit/src/event.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
//! The [`Event`] enum and assorted supporting types.
|
||||
//!
|
||||
//! These are sent to the closure given to [`EventLoop::run(...)`], where they get
|
||||
//! processed and used to modify the program state. For more details, see the root-level documentation.
|
||||
//!
|
||||
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
|
||||
//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! let mut start_cause = StartCause::Init;
|
||||
//!
|
||||
//! while !elwt.exiting() {
|
||||
//! event_handler(NewEvents(start_cause), elwt);
|
||||
//!
|
||||
//! for e in (window events, user events, device events) {
|
||||
//! event_handler(e, elwt);
|
||||
//! }
|
||||
//!
|
||||
//! for w in (redraw windows) {
|
||||
//! event_handler(RedrawRequested(w), elwt);
|
||||
//! }
|
||||
//!
|
||||
//! event_handler(AboutToWait, elwt);
|
||||
//! start_cause = wait_if_necessary();
|
||||
//! }
|
||||
//!
|
||||
//! event_handler(LoopExiting, elwt);
|
||||
//! ```
|
||||
//!
|
||||
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
|
||||
//! describes what happens in what order.
|
||||
//!
|
||||
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
|
||||
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
|
||||
use crate::platform_impl;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use winit_core::event::{
|
||||
DeviceEvent, DeviceId, ElementState, Force, Ime, InnerSizeIgnored, InnerSizeWriter, Modifiers,
|
||||
MouseButton, MouseScrollDelta, RawKeyEvent, StartCause, Touch, TouchPhase,
|
||||
};
|
||||
|
||||
pub type Event<T> = winit_core::event::Event<T, KeyExtra>;
|
||||
pub type WindowEvent = winit_core::event::WindowEvent<KeyExtra>;
|
||||
pub type KeyEvent = winit_core::event::KeyEvent<KeyExtra>;
|
||||
|
||||
/// Extra keyboard information.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KeyExtra {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) extra: platform_impl::KeyEventExtra,
|
||||
}
|
||||
@@ -11,17 +11,15 @@ use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{error, fmt};
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
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};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use winit_core::event_loop::{AsyncRequestSerial, ControlFlow};
|
||||
|
||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
||||
/// the events loop.
|
||||
///
|
||||
@@ -48,8 +46,8 @@ pub struct EventLoop<T: 'static> {
|
||||
/// your callback. [`EventLoop`] will coerce into this type (`impl<T> Deref for
|
||||
/// EventLoop<T>`), so functions that take this as a parameter can also take
|
||||
/// `&EventLoop`.
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
|
||||
pub struct EventLoopWindowTarget {
|
||||
pub(crate) p: platform_impl::EventLoopWindowTarget,
|
||||
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
|
||||
}
|
||||
|
||||
@@ -57,33 +55,26 @@ pub struct EventLoopWindowTarget<T: 'static> {
|
||||
///
|
||||
/// This is used to make specifying options that affect the whole application
|
||||
/// easier. But note that constructing multiple event loops is not supported.
|
||||
///
|
||||
/// This can be created using [`EventLoop::new`] or [`EventLoop::with_user_event`].
|
||||
#[derive(Default)]
|
||||
pub struct EventLoopBuilder<T: 'static> {
|
||||
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
|
||||
_p: PhantomData<T>,
|
||||
}
|
||||
|
||||
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
impl EventLoopBuilder<()> {
|
||||
/// Start building a new event loop.
|
||||
#[inline]
|
||||
#[deprecated = "use `EventLoop::builder` instead"]
|
||||
pub fn new() -> Self {
|
||||
Self::with_user_event()
|
||||
EventLoop::builder()
|
||||
}
|
||||
}
|
||||
|
||||
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
impl<T> EventLoopBuilder<T> {
|
||||
/// Start building a new event loop, with the given type as the user event
|
||||
/// type.
|
||||
#[inline]
|
||||
pub fn with_user_event() -> Self {
|
||||
Self {
|
||||
platform_specific: Default::default(),
|
||||
_p: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new event loop.
|
||||
///
|
||||
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
|
||||
@@ -130,7 +121,7 @@ impl<T> EventLoopBuilder<T> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
#[cfg(web_platform)]
|
||||
pub(crate) fn allow_event_loop_recreation() {
|
||||
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
|
||||
}
|
||||
@@ -142,78 +133,45 @@ impl<T> fmt::Debug for EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||
impl fmt::Debug for EventLoopWindowTarget {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("EventLoopWindowTarget { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
/// Set through [`EventLoopWindowTarget::set_control_flow()`].
|
||||
///
|
||||
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
|
||||
///
|
||||
/// Defaults to [`Wait`].
|
||||
///
|
||||
/// [`Wait`]: Self::Wait
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum ControlFlow {
|
||||
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
||||
/// whether or not new events are available to process.
|
||||
Poll,
|
||||
|
||||
/// When the current loop iteration finishes, suspend the thread until another event arrives.
|
||||
#[default]
|
||||
Wait,
|
||||
|
||||
/// When the current loop iteration finishes, suspend the thread until either another event
|
||||
/// arrives or the given time is reached.
|
||||
///
|
||||
/// Useful for implementing efficient timers. Applications which want to render at the display's
|
||||
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API
|
||||
/// to reduce odds of missed frames.
|
||||
///
|
||||
/// [`Poll`]: Self::Poll
|
||||
WaitUntil(Instant),
|
||||
}
|
||||
|
||||
impl ControlFlow {
|
||||
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
|
||||
///
|
||||
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
|
||||
/// instead set to [`Wait`].
|
||||
///
|
||||
/// [`WaitUntil`]: Self::WaitUntil
|
||||
/// [`Wait`]: Self::Wait
|
||||
pub fn wait_duration(timeout: Duration) -> Self {
|
||||
match Instant::now().checked_add(timeout) {
|
||||
Some(instant) => Self::WaitUntil(instant),
|
||||
None => Self::Wait,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop<()> {
|
||||
/// Alias for [`EventLoopBuilder::new().build()`].
|
||||
/// Create the event loop.
|
||||
///
|
||||
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
|
||||
/// This is an alias of `EventLoop::builder().build()`.
|
||||
#[inline]
|
||||
pub fn new() -> Result<EventLoop<()>, EventLoopError> {
|
||||
EventLoopBuilder::new().build()
|
||||
Self::builder().build()
|
||||
}
|
||||
|
||||
/// Start building a new event loop.
|
||||
///
|
||||
/// This returns an [`EventLoopBuilder`], to allow configuring the event loop before creation.
|
||||
///
|
||||
/// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that.
|
||||
#[inline]
|
||||
pub fn builder() -> EventLoopBuilder<()> {
|
||||
Self::with_user_event()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoop<T> {
|
||||
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
|
||||
pub fn with_user_event() -> Result<EventLoop<T>, EventLoopError> {
|
||||
EventLoopBuilder::<T>::with_user_event().build()
|
||||
/// Start building a new event loop, with the given type as the user event
|
||||
/// type.
|
||||
pub fn with_user_event() -> EventLoopBuilder<T> {
|
||||
EventLoopBuilder {
|
||||
platform_specific: Default::default(),
|
||||
_p: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 [`set_control_flow()`] docs on how to change the event loop's behavior.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
@@ -226,10 +184,10 @@ impl<T> EventLoop<T> {
|
||||
///
|
||||
/// Web applications are recommended to use
|
||||
#[cfg_attr(
|
||||
wasm_platform,
|
||||
web_platform,
|
||||
doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]"
|
||||
)]
|
||||
#[cfg_attr(not(wasm_platform), doc = "`EventLoopExtWebSys::spawn()`")]
|
||||
#[cfg_attr(not(web_platform), doc = "`EventLoopExtWebSys::spawn()`")]
|
||||
/// [^1] instead of [`run()`] to avoid the need
|
||||
/// for the Javascript exception trick, and to make it clearer that the event loop runs
|
||||
/// asynchronously (via the browser's own, internal, event loop) and doesn't block the
|
||||
@@ -239,12 +197,12 @@ impl<T> EventLoop<T> {
|
||||
///
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
|
||||
/// [`run()`]: Self::run()
|
||||
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on WASM.
|
||||
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on Web.
|
||||
#[inline]
|
||||
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
|
||||
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
|
||||
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<T>, &EventLoopWindowTarget<T>),
|
||||
F: FnMut(Event<T>, &EventLoopWindowTarget),
|
||||
{
|
||||
self.event_loop.run(event_handler)
|
||||
}
|
||||
@@ -301,13 +259,13 @@ impl<T> AsRawFd for EventLoop<T> {
|
||||
}
|
||||
|
||||
impl<T> Deref for EventLoop<T> {
|
||||
type Target = EventLoopWindowTarget<T>;
|
||||
fn deref(&self) -> &EventLoopWindowTarget<T> {
|
||||
type Target = EventLoopWindowTarget;
|
||||
fn deref(&self) -> &EventLoopWindowTarget {
|
||||
self.event_loop.window_target()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
impl EventLoopWindowTarget {
|
||||
/// Returns the list of all the monitors available on the system.
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
||||
@@ -370,10 +328,19 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
pub fn exiting(&self) -> bool {
|
||||
self.p.exiting()
|
||||
}
|
||||
|
||||
/// Gets a persistent reference to the underlying platform display.
|
||||
///
|
||||
/// See the [`OwnedDisplayHandle`] type for more information.
|
||||
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
|
||||
OwnedDisplayHandle {
|
||||
platform: self.p.owned_display_handle(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl<T> rwh_06::HasDisplayHandle for EventLoopWindowTarget<T> {
|
||||
impl rwh_06::HasDisplayHandle for EventLoopWindowTarget {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = self.p.raw_display_handle_rwh_06()?;
|
||||
// SAFETY: The display will never be deallocated while the event loop is alive.
|
||||
@@ -382,13 +349,59 @@ impl<T> rwh_06::HasDisplayHandle for EventLoopWindowTarget<T> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoopWindowTarget<T> {
|
||||
unsafe impl rwh_05::HasRawDisplayHandle for EventLoopWindowTarget {
|
||||
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
|
||||
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
|
||||
self.p.raw_display_handle_rwh_05()
|
||||
}
|
||||
}
|
||||
|
||||
/// A proxy for the underlying display handle.
|
||||
///
|
||||
/// The purpose of this type is to provide a cheaply clonable handle to the underlying
|
||||
/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
|
||||
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`EventLoopWindowTarget`]
|
||||
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as
|
||||
/// needed.
|
||||
///
|
||||
/// For all platforms, this is one of the following:
|
||||
///
|
||||
/// - A zero-sized type that is likely optimized out.
|
||||
/// - A reference-counted pointer to the underlying type.
|
||||
#[derive(Clone)]
|
||||
pub struct OwnedDisplayHandle {
|
||||
#[cfg_attr(not(any(feature = "rwh_05", feature = "rwh_06")), allow(dead_code))]
|
||||
platform: platform_impl::OwnedDisplayHandle,
|
||||
}
|
||||
|
||||
impl fmt::Debug for OwnedDisplayHandle {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
|
||||
#[inline]
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = self.platform.raw_display_handle_rwh_06()?;
|
||||
|
||||
// SAFETY: The underlying display handle should be safe.
|
||||
let handle = unsafe { rwh_06::DisplayHandle::borrow_raw(raw) };
|
||||
|
||||
Ok(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
unsafe impl rwh_05::HasRawDisplayHandle for OwnedDisplayHandle {
|
||||
#[inline]
|
||||
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
|
||||
self.platform.raw_display_handle_rwh_05()
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to send custom events to [`EventLoop`].
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
event_loop_proxy: platform_impl::EventLoopProxy<T>,
|
||||
@@ -447,29 +460,3 @@ 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 }
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
//! Once this is done, there are two ways to create a [`Window`]:
|
||||
//!
|
||||
//! - Calling [`Window::new(&event_loop)`][window_new].
|
||||
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
|
||||
//! - Calling [`let builder = Window::builder()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
|
||||
//!
|
||||
//! The first method is the simplest and will give you default values for everything. The second
|
||||
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
|
||||
@@ -63,11 +63,11 @@
|
||||
//! use winit::{
|
||||
//! event::{Event, WindowEvent},
|
||||
//! event_loop::{ControlFlow, EventLoop},
|
||||
//! window::WindowBuilder,
|
||||
//! window::Window,
|
||||
//! };
|
||||
//!
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
//! let window = Window::builder().build(&event_loop).unwrap();
|
||||
//!
|
||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
||||
//! // dispatched any events. This is ideal for games and similar applications.
|
||||
@@ -137,7 +137,7 @@
|
||||
//! [`WindowId`]: window::WindowId
|
||||
//! [`WindowBuilder`]: window::WindowBuilder
|
||||
//! [window_new]: window::Window::new
|
||||
//! [window_builder_new]: window::WindowBuilder::new
|
||||
//! [window_builder_new]: window::Window::builder
|
||||
//! [window_builder_build]: window::WindowBuilder::build
|
||||
//! [`Window::id()`]: window::Window::id
|
||||
//! [`WindowEvent`]: event::WindowEvent
|
||||
@@ -154,46 +154,27 @@
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
|
||||
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(
|
||||
docsrs,
|
||||
feature(doc_auto_cfg, doc_cfg_hide),
|
||||
doc(cfg_hide(doc, docsrs))
|
||||
)]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub use rwh_06 as raw_window_handle;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "serde")]
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[doc(inline)]
|
||||
pub use winit_core::{dpi, keyboard};
|
||||
|
||||
pub mod dpi;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
mod cursor;
|
||||
pub mod event;
|
||||
pub mod event_loop;
|
||||
mod icon;
|
||||
pub mod keyboard;
|
||||
pub mod monitor;
|
||||
mod platform_impl;
|
||||
pub mod window;
|
||||
|
||||
pub mod platform;
|
||||
|
||||
/// Wrapper for objects which winit will access on the main thread so they are effectively `Send`
|
||||
/// and `Sync`, since they always execute on a single thread.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Winit can run only one event loop at a time, and the event loop itself is tied to some thread.
|
||||
/// The objects could be sent across the threads, but once passed to winit, they execute on the
|
||||
/// main thread if the platform demands it. Thus, marking such objects as `Send + Sync` is safe.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct SendSyncWrapper<T>(pub(crate) T);
|
||||
|
||||
unsafe impl<T> Send for SendSyncWrapper<T> {}
|
||||
unsafe impl<T> Sync for SendSyncWrapper<T> {}
|
||||
@@ -10,28 +10,32 @@ use crate::{
|
||||
platform_impl,
|
||||
};
|
||||
|
||||
/// Deprecated! Use `VideoModeHandle` instead.
|
||||
#[deprecated = "Renamed to `VideoModeHandle`"]
|
||||
pub type VideoMode = VideoModeHandle;
|
||||
|
||||
/// Describes a fullscreen video mode of a monitor.
|
||||
///
|
||||
/// Can be acquired with [`MonitorHandle::video_modes`].
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) video_mode: platform_impl::VideoMode,
|
||||
pub struct VideoModeHandle {
|
||||
pub(crate) video_mode: platform_impl::VideoModeHandle,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VideoMode {
|
||||
impl std::fmt::Debug for VideoModeHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.video_mode.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for VideoMode {
|
||||
fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
|
||||
impl PartialOrd for VideoModeHandle {
|
||||
fn partial_cmp(&self, other: &VideoModeHandle) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for VideoMode {
|
||||
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
|
||||
impl Ord for VideoModeHandle {
|
||||
fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering {
|
||||
self.monitor().cmp(&other.monitor()).then(
|
||||
self.size()
|
||||
.cmp(&other.size())
|
||||
@@ -45,7 +49,7 @@ impl Ord for VideoMode {
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
impl VideoModeHandle {
|
||||
/// Returns the resolution of this video mode.
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
@@ -81,7 +85,7 @@ impl VideoMode {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VideoMode {
|
||||
impl std::fmt::Display for VideoModeHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
@@ -131,8 +135,8 @@ impl MonitorHandle {
|
||||
/// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor
|
||||
/// the window is on is removed.
|
||||
///
|
||||
/// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to
|
||||
/// enter fullscreen should be used instead.
|
||||
/// When using exclusive fullscreen, the refresh rate of the [`VideoModeHandle`] that was
|
||||
/// used to enter fullscreen should be used instead.
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
self.inner.refresh_rate_millihertz()
|
||||
@@ -161,9 +165,9 @@ impl MonitorHandle {
|
||||
///
|
||||
/// - **Web:** Always returns an empty iterator
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
self.inner
|
||||
.video_modes()
|
||||
.map(|video_mode| VideoMode { video_mode })
|
||||
.map(|video_mode| VideoModeHandle { video_mode })
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
use android_activity::{AndroidApp, ConfigurationRef, Rect};
|
||||
use self::activity::{AndroidApp, ConfigurationRef, Rect};
|
||||
|
||||
/// Additional methods on [`EventLoop`] that are specific to Android.
|
||||
pub trait EventLoopExtAndroid {}
|
||||
@@ -30,7 +30,7 @@ impl WindowExtAndroid for Window {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
|
||||
impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {}
|
||||
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to Android.
|
||||
pub trait WindowBuilderExtAndroid {}
|
||||
@@ -89,5 +89,16 @@ pub mod activity {
|
||||
// feature enabled, so we avoid inlining it so that they're forced to view
|
||||
// it on the crate's own docs.rs page.
|
||||
#[doc(no_inline)]
|
||||
#[cfg(android_platform)]
|
||||
pub use android_activity::*;
|
||||
|
||||
#[cfg(not(android_platform))]
|
||||
#[doc(hidden)]
|
||||
pub struct Rect;
|
||||
#[cfg(not(android_platform))]
|
||||
#[doc(hidden)]
|
||||
pub struct ConfigurationRef;
|
||||
#[cfg(not(android_platform))]
|
||||
#[doc(hidden)]
|
||||
pub struct AndroidApp;
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use icrate::Foundation::MainThreadMarker;
|
||||
use objc2::rc::Id;
|
||||
|
||||
use crate::{
|
||||
event_loop::EventLoop,
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
monitor::{MonitorHandle, VideoModeHandle},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -88,6 +85,21 @@ pub trait WindowExtIOS {
|
||||
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
|
||||
/// is also called for you.
|
||||
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle);
|
||||
|
||||
/// Sets whether the [`Window`] should recognize pinch gestures.
|
||||
///
|
||||
/// The default is to not recognize gestures.
|
||||
fn recognize_pinch_gesture(&self, should_recognize: bool);
|
||||
|
||||
/// Sets whether the [`Window`] should recognize double tap gestures.
|
||||
///
|
||||
/// The default is to not recognize gestures.
|
||||
fn recognize_doubletap_gesture(&self, should_recognize: bool);
|
||||
|
||||
/// Sets whether the [`Window`] should recognize rotation gestures.
|
||||
///
|
||||
/// The default is to not recognize gestures.
|
||||
fn recognize_rotation_gesture(&self, should_recognize: bool);
|
||||
}
|
||||
|
||||
impl WindowExtIOS for Window {
|
||||
@@ -127,6 +139,24 @@ impl WindowExtIOS for Window {
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn recognize_pinch_gesture(&self, should_recognize: bool) {
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn recognize_doubletap_gesture(&self, should_recognize: bool) {
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn recognize_rotation_gesture(&self, should_recognize: bool) {
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to iOS.
|
||||
@@ -187,38 +217,39 @@ pub trait WindowBuilderExtIOS {
|
||||
impl WindowBuilderExtIOS for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_scale_factor(mut self, scale_factor: f64) -> Self {
|
||||
self.platform_specific.scale_factor = Some(scale_factor);
|
||||
self.window.platform_specific.scale_factor = Some(scale_factor);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self {
|
||||
self.platform_specific.valid_orientations = valid_orientations;
|
||||
self.window.platform_specific.valid_orientations = valid_orientations;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self {
|
||||
self.platform_specific.prefers_home_indicator_hidden = hidden;
|
||||
self.window.platform_specific.prefers_home_indicator_hidden = hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
|
||||
self.platform_specific
|
||||
self.window
|
||||
.platform_specific
|
||||
.preferred_screen_edges_deferring_system_gestures = edges;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self {
|
||||
self.platform_specific.prefers_status_bar_hidden = hidden;
|
||||
self.window.platform_specific.prefers_status_bar_hidden = hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
|
||||
self.platform_specific.preferred_status_bar_style = status_bar_style;
|
||||
self.window.platform_specific.preferred_status_bar_style = status_bar_style;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -230,23 +261,23 @@ pub trait MonitorHandleExtIOS {
|
||||
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
|
||||
fn ui_screen(&self) -> *mut c_void;
|
||||
|
||||
/// Returns the preferred [`VideoMode`] for this monitor.
|
||||
/// Returns the preferred [`VideoModeHandle`] for this monitor.
|
||||
///
|
||||
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
|
||||
fn preferred_video_mode(&self) -> VideoMode;
|
||||
fn preferred_video_mode(&self) -> VideoModeHandle;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtIOS for MonitorHandle {
|
||||
#[inline]
|
||||
fn ui_screen(&self) -> *mut c_void {
|
||||
// SAFETY: The marker is only used to get the pointer of the screen
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
|
||||
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() };
|
||||
objc2::rc::Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn preferred_video_mode(&self) -> VideoMode {
|
||||
VideoMode {
|
||||
fn preferred_video_mode(&self) -> VideoModeHandle {
|
||||
VideoModeHandle {
|
||||
video_mode: self.inner.preferred_video_mode(),
|
||||
}
|
||||
}
|
||||
@@ -283,7 +314,7 @@ pub enum Idiom {
|
||||
CarPlay,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
bitflags::bitflags! {
|
||||
/// The [edges] of a screen.
|
||||
///
|
||||
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use icrate::Foundation::MainThreadMarker;
|
||||
use objc2::rc::Id;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
||||
@@ -212,61 +212,62 @@ pub trait WindowBuilderExtMacOS {
|
||||
impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
|
||||
self.platform_specific.movable_by_window_background = movable_by_window_background;
|
||||
self.window.platform_specific.movable_by_window_background = movable_by_window_background;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
|
||||
self.platform_specific.titlebar_transparent = titlebar_transparent;
|
||||
self.window.platform_specific.titlebar_transparent = titlebar_transparent;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
|
||||
self.platform_specific.titlebar_hidden = titlebar_hidden;
|
||||
self.window.platform_specific.titlebar_hidden = titlebar_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
|
||||
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
|
||||
self.window.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_title_hidden(mut self, title_hidden: bool) -> Self {
|
||||
self.platform_specific.title_hidden = title_hidden;
|
||||
self.window.platform_specific.title_hidden = title_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
|
||||
self.platform_specific.fullsize_content_view = fullsize_content_view;
|
||||
self.window.platform_specific.fullsize_content_view = fullsize_content_view;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
|
||||
self.platform_specific.disallow_hidpi = disallow_hidpi;
|
||||
self.window.platform_specific.disallow_hidpi = disallow_hidpi;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_has_shadow(mut self, has_shadow: bool) -> Self {
|
||||
self.platform_specific.has_shadow = has_shadow;
|
||||
self.window.platform_specific.has_shadow = has_shadow;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
|
||||
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
|
||||
self.window.platform_specific.accepts_first_mouse = accepts_first_mouse;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
|
||||
self.platform_specific
|
||||
self.window
|
||||
.platform_specific
|
||||
.tabbing_identifier
|
||||
.replace(tabbing_identifier.to_string());
|
||||
self
|
||||
@@ -274,7 +275,7 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
|
||||
#[inline]
|
||||
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
|
||||
self.platform_specific.option_as_alt = option_as_alt;
|
||||
self.window.platform_specific.option_as_alt = option_as_alt;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -367,8 +368,10 @@ impl MonitorHandleExtMacOS for MonitorHandle {
|
||||
|
||||
fn ns_screen(&self) -> Option<*mut c_void> {
|
||||
// SAFETY: We only use the marker to get a pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
self.inner.ns_screen(mtm).map(|s| Id::as_ptr(&s) as _)
|
||||
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() };
|
||||
self.inner
|
||||
.ns_screen(mtm)
|
||||
.map(|s| objc2::rc::Id::as_ptr(&s) as _)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +389,7 @@ pub trait EventLoopWindowTargetExtMacOS {
|
||||
fn allows_automatic_window_tabbing(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
||||
impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget {
|
||||
fn hide_application(&self) {
|
||||
self.p.hide_application()
|
||||
}
|
||||
60
winit/src/platform/mod.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
//! Contains traits with platform-specific methods in them.
|
||||
//!
|
||||
//! Only the modules corresponding to the platform you're compiling to will be available.
|
||||
|
||||
#[cfg(any(android_platform, docsrs))]
|
||||
pub mod android;
|
||||
#[cfg(any(ios_platform, docsrs))]
|
||||
pub mod ios;
|
||||
#[cfg(any(macos_platform, docsrs))]
|
||||
pub mod macos;
|
||||
#[cfg(any(orbital_platform, docsrs))]
|
||||
pub mod orbital;
|
||||
#[cfg(any(x11_platform, wayland_platform, docsrs))]
|
||||
pub mod startup_notify;
|
||||
#[cfg(any(wayland_platform, docsrs))]
|
||||
pub mod wayland;
|
||||
#[cfg(any(web_platform, docsrs))]
|
||||
pub mod web;
|
||||
#[cfg(any(windows_platform, docsrs))]
|
||||
pub mod windows;
|
||||
#[cfg(any(x11_platform, docsrs))]
|
||||
pub mod x11;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
docsrs,
|
||||
))]
|
||||
pub mod run_on_demand;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
docsrs,
|
||||
))]
|
||||
pub mod pump_events;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
docsrs
|
||||
))]
|
||||
pub mod modifier_supplement;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
docsrs
|
||||
))]
|
||||
pub mod scancode;
|
||||
@@ -1,5 +1,4 @@
|
||||
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
|
||||
|
||||
use crate::event::KeyEvent;
|
||||
use crate::keyboard::Key;
|
||||
|
||||
/// Additional methods for the `KeyEvent` which cannot be implemented on all
|
||||
@@ -22,3 +21,19 @@ pub trait KeyEventExtModifierSupplement {
|
||||
/// cannot be `Dead`.
|
||||
fn key_without_modifiers(&self) -> Key;
|
||||
}
|
||||
|
||||
impl KeyEventExtModifierSupplement for KeyEvent {
|
||||
#[inline]
|
||||
fn text_with_all_modifiers(&self) -> Option<&str> {
|
||||
self.extra
|
||||
.extra
|
||||
.text_with_all_modifiers
|
||||
.as_ref()
|
||||
.map(|s| s.as_str())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn key_without_modifiers(&self) -> Key {
|
||||
self.extra.extra.key_without_modifiers.clone()
|
||||
}
|
||||
}
|
||||
@@ -51,12 +51,12 @@ pub trait EventLoopExtPumpEvents {
|
||||
/// # event::{Event, WindowEvent},
|
||||
/// # event_loop::EventLoop,
|
||||
/// # platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
|
||||
/// # window::WindowBuilder,
|
||||
/// # window::Window,
|
||||
/// # };
|
||||
/// let mut event_loop = EventLoop::new().unwrap();
|
||||
/// #
|
||||
/// # SimpleLogger::new().init().unwrap();
|
||||
/// let window = WindowBuilder::new()
|
||||
/// let window = Window::builder()
|
||||
/// .with_title("A fantastic window!")
|
||||
/// .build(&event_loop)
|
||||
/// .unwrap();
|
||||
@@ -174,7 +174,7 @@ pub trait EventLoopExtPumpEvents {
|
||||
/// callback.
|
||||
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
|
||||
@@ -182,7 +182,7 @@ impl<T> EventLoopExtPumpEvents for EventLoop<T> {
|
||||
|
||||
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget),
|
||||
{
|
||||
self.event_loop.pump_events(timeout, event_handler)
|
||||
}
|
||||
@@ -55,10 +55,10 @@ pub trait EventLoopExtRunOnDemand {
|
||||
/// 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.
|
||||
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
|
||||
///
|
||||
#[cfg_attr(
|
||||
not(wasm_platform),
|
||||
not(web_platform),
|
||||
doc = "[^1]: `spawn()` is only available on `wasm` platforms."
|
||||
)]
|
||||
///
|
||||
@@ -66,7 +66,7 @@ pub trait EventLoopExtRunOnDemand {
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
|
||||
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
|
||||
@@ -74,8 +74,29 @@ impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
|
||||
|
||||
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget),
|
||||
{
|
||||
self.event_loop.window_target().clear_exit();
|
||||
self.event_loop.run_on_demand(event_handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWindowTarget {
|
||||
/// Clear exit status.
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.p.clear_exit()
|
||||
}
|
||||
}
|
||||
|
||||
/// ```compile_fail
|
||||
/// use winit::event_loop::EventLoop;
|
||||
/// use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
|
||||
///
|
||||
/// let mut event_loop = EventLoop::new().unwrap();
|
||||
/// event_loop.run_on_demand(|_, _| {
|
||||
/// // Attempt to run the event loop re-entrantly; this must fail.
|
||||
/// event_loop.run_on_demand(|_, _| {});
|
||||
/// });
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
fn test_run_on_demand_cannot_access_event_loop() {}
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
|
||||
|
||||
use crate::keyboard::{KeyCode, PhysicalKey};
|
||||
|
||||
// TODO: Describe what this value contains for each platform
|
||||
@@ -29,17 +27,24 @@ pub trait PhysicalKeyExtScancode {
|
||||
fn from_scancode(scancode: u32) -> PhysicalKey;
|
||||
}
|
||||
|
||||
impl PhysicalKeyExtScancode for KeyCode
|
||||
where
|
||||
PhysicalKey: PhysicalKeyExtScancode,
|
||||
{
|
||||
#[inline]
|
||||
fn from_scancode(scancode: u32) -> PhysicalKey {
|
||||
<PhysicalKey as PhysicalKeyExtScancode>::from_scancode(scancode)
|
||||
impl PhysicalKeyExtScancode for PhysicalKey {
|
||||
fn to_scancode(self) -> Option<u32> {
|
||||
crate::platform_impl::physicalkey_to_scancode(self)
|
||||
}
|
||||
|
||||
fn from_scancode(scancode: u32) -> PhysicalKey {
|
||||
crate::platform_impl::scancode_to_physicalkey(scancode)
|
||||
}
|
||||
}
|
||||
|
||||
impl PhysicalKeyExtScancode for KeyCode {
|
||||
#[inline]
|
||||
fn to_scancode(self) -> Option<u32> {
|
||||
<PhysicalKey as PhysicalKeyExtScancode>::to_scancode(PhysicalKey::Code(self))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_scancode(scancode: u32) -> PhysicalKey {
|
||||
<PhysicalKey as PhysicalKeyExtScancode>::from_scancode(scancode)
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ pub trait WindowBuilderExtStartupNotify {
|
||||
fn with_activation_token(self, token: ActivationToken) -> Self;
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtStartupNotify for EventLoopWindowTarget<T> {
|
||||
impl EventLoopExtStartupNotify for EventLoopWindowTarget {
|
||||
fn read_token_from_env(&self) -> Option<ActivationToken> {
|
||||
match self.p {
|
||||
#[cfg(wayland_platform)]
|
||||
@@ -64,7 +64,7 @@ impl<T> EventLoopExtStartupNotify for EventLoopWindowTarget<T> {
|
||||
crate::platform_impl::EventLoopWindowTarget::X(_) => env::var(X11_VAR),
|
||||
}
|
||||
.ok()
|
||||
.map(ActivationToken::_new)
|
||||
.map(ActivationToken::new)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ impl WindowExtStartupNotify for Window {
|
||||
|
||||
impl WindowBuilderExtStartupNotify for WindowBuilder {
|
||||
fn with_activation_token(mut self, token: ActivationToken) -> Self {
|
||||
self.platform_specific.activation_token = Some(token);
|
||||
self.window.platform_specific.activation_token = Some(token);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -94,6 +94,6 @@ pub fn reset_activation_token_env() {
|
||||
///
|
||||
/// This could be used before running daemon processes.
|
||||
pub fn set_activation_token_env(token: ActivationToken) {
|
||||
env::set_var(X11_VAR, &token._token);
|
||||
env::set_var(WAYLAND_VAR, token._token);
|
||||
env::set_var(X11_VAR, token.token());
|
||||
env::set_var(WAYLAND_VAR, token.token());
|
||||
}
|
||||
@@ -4,8 +4,6 @@ use crate::{
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
use crate::platform_impl::{ApplicationName, Backend};
|
||||
|
||||
pub use crate::window::Theme;
|
||||
|
||||
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Wayland.
|
||||
@@ -14,7 +12,7 @@ pub trait EventLoopWindowTargetExtWayland {
|
||||
fn is_wayland(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
|
||||
impl EventLoopWindowTargetExtWayland for EventLoopWindowTarget {
|
||||
#[inline]
|
||||
fn is_wayland(&self) -> bool {
|
||||
self.p.is_wayland()
|
||||
@@ -36,7 +34,7 @@ pub trait EventLoopBuilderExtWayland {
|
||||
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
|
||||
#[inline]
|
||||
fn with_wayland(&mut self) -> &mut Self {
|
||||
self.platform_specific.forced_backend = Some(Backend::Wayland);
|
||||
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -67,7 +65,10 @@ pub trait WindowBuilderExtWayland {
|
||||
impl WindowBuilderExtWayland for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
|
||||
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
|
||||
self.window.platform_specific.name = Some(crate::platform_impl::ApplicationName::new(
|
||||
general.into(),
|
||||
instance.into(),
|
||||
));
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -27,20 +27,47 @@
|
||||
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
|
||||
use crate::cursor::CustomCursorBuilder;
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::event_loop::EventLoopWindowTarget;
|
||||
use crate::platform_impl::PlatformCustomCursorBuilder;
|
||||
use crate::window::CustomCursor;
|
||||
use crate::window::{Window, WindowBuilder};
|
||||
use crate::SendSyncWrapper;
|
||||
use std::error::Error;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(web_platform)]
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use crate::cursor::CustomCursorBuilder;
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::{EventLoop, EventLoopWindowTarget};
|
||||
#[cfg(web_platform)]
|
||||
use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture;
|
||||
use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorBuilder};
|
||||
use crate::window::{CustomCursor, Window, WindowBuilder};
|
||||
|
||||
#[cfg(not(web_platform))]
|
||||
#[doc(hidden)]
|
||||
pub struct HtmlCanvasElement;
|
||||
|
||||
pub trait WindowExtWebSys {
|
||||
/// Only returns the canvas if called from inside the window.
|
||||
/// Only returns the canvas if called from inside the window context (the
|
||||
/// main thread).
|
||||
fn canvas(&self) -> Option<HtmlCanvasElement>;
|
||||
|
||||
/// Returns [`true`] if calling `event.preventDefault()` is enabled.
|
||||
///
|
||||
/// See [`Window::set_prevent_default()`] for more details.
|
||||
fn prevent_default(&self) -> bool;
|
||||
|
||||
/// Sets whether `event.preventDefault()` should be called on events on the
|
||||
/// canvas that have side effects.
|
||||
///
|
||||
/// For example, by default using the mouse wheel would cause the page to scroll, enabling this
|
||||
/// would prevent that.
|
||||
///
|
||||
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
|
||||
/// context menu with Shift+Rightclick.
|
||||
fn set_prevent_default(&self, prevent_default: bool);
|
||||
}
|
||||
|
||||
impl WindowExtWebSys for Window {
|
||||
@@ -48,6 +75,14 @@ impl WindowExtWebSys for Window {
|
||||
fn canvas(&self) -> Option<HtmlCanvasElement> {
|
||||
self.window.canvas()
|
||||
}
|
||||
|
||||
fn prevent_default(&self) -> bool {
|
||||
self.window.prevent_default()
|
||||
}
|
||||
|
||||
fn set_prevent_default(&self, prevent_default: bool) {
|
||||
self.window.set_prevent_default(prevent_default)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WindowBuilderExtWebSys {
|
||||
@@ -57,16 +92,17 @@ pub trait WindowBuilderExtWebSys {
|
||||
/// In any case, the canvas won't be automatically inserted into the web page.
|
||||
///
|
||||
/// [`None`] by default.
|
||||
#[cfg_attr(
|
||||
not(web_platform),
|
||||
doc = "",
|
||||
doc = "[`HtmlCanvasElement`]: #only-available-on-wasm"
|
||||
)]
|
||||
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
|
||||
|
||||
/// Whether `event.preventDefault` should be automatically called to prevent event propagation
|
||||
/// when appropriate.
|
||||
/// Sets whether `event.preventDefault()` should be called on events on the
|
||||
/// canvas that have side effects.
|
||||
///
|
||||
/// For example, mouse wheel events are only handled by the canvas by default. This avoids
|
||||
/// the default behavior of scrolling the page.
|
||||
///
|
||||
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
|
||||
/// context menu with Shift+Rightclick.
|
||||
/// See [`Window::set_prevent_default()`] for more details.
|
||||
///
|
||||
/// Enabled by default.
|
||||
fn with_prevent_default(self, prevent_default: bool) -> Self;
|
||||
@@ -85,22 +121,22 @@ pub trait WindowBuilderExtWebSys {
|
||||
|
||||
impl WindowBuilderExtWebSys for WindowBuilder {
|
||||
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
|
||||
self.platform_specific.canvas = SendSyncWrapper(canvas);
|
||||
self.window.platform_specific.set_canvas(canvas);
|
||||
self
|
||||
}
|
||||
|
||||
fn with_prevent_default(mut self, prevent_default: bool) -> Self {
|
||||
self.platform_specific.prevent_default = prevent_default;
|
||||
self.window.platform_specific.prevent_default = prevent_default;
|
||||
self
|
||||
}
|
||||
|
||||
fn with_focusable(mut self, focusable: bool) -> Self {
|
||||
self.platform_specific.focusable = focusable;
|
||||
self.window.platform_specific.focusable = focusable;
|
||||
self
|
||||
}
|
||||
|
||||
fn with_append(mut self, append: bool) -> Self {
|
||||
self.platform_specific.append = append;
|
||||
self.window.platform_specific.append = append;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -114,11 +150,11 @@ pub trait EventLoopExtWebSys {
|
||||
///
|
||||
/// Unlike
|
||||
#[cfg_attr(
|
||||
all(wasm_platform, target_feature = "exception-handling"),
|
||||
all(web_platform, target_feature = "exception-handling"),
|
||||
doc = "`run()`"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(all(wasm_platform, target_feature = "exception-handling")),
|
||||
not(all(web_platform, target_feature = "exception-handling")),
|
||||
doc = "[`run()`]"
|
||||
)]
|
||||
/// [^1], this returns immediately, and doesn't throw an exception in order to
|
||||
@@ -130,13 +166,13 @@ pub trait EventLoopExtWebSys {
|
||||
/// event loop when switching between tabs on a single page application.
|
||||
///
|
||||
#[cfg_attr(
|
||||
not(all(wasm_platform, target_feature = "exception-handling")),
|
||||
not(all(web_platform, target_feature = "exception-handling")),
|
||||
doc = "[`run()`]: EventLoop::run()"
|
||||
)]
|
||||
/// [^1]: `run()` is _not_ available on WASM when the target supports `exception-handling`.
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
|
||||
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
@@ -144,7 +180,7 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
|
||||
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget),
|
||||
{
|
||||
self.event_loop.spawn(event_handler)
|
||||
}
|
||||
@@ -166,7 +202,7 @@ pub trait EventLoopWindowTargetExtWebSys {
|
||||
fn poll_strategy(&self) -> PollStrategy;
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtWebSys for EventLoopWindowTarget<T> {
|
||||
impl EventLoopWindowTargetExtWebSys for EventLoopWindowTarget {
|
||||
#[inline]
|
||||
fn set_poll_strategy(&self, strategy: PollStrategy) {
|
||||
self.p.set_poll_strategy(strategy);
|
||||
@@ -205,15 +241,29 @@ pub enum PollStrategy {
|
||||
}
|
||||
|
||||
pub trait CustomCursorExtWebSys {
|
||||
/// Returns if this cursor is an animation.
|
||||
fn is_animation(&self) -> bool;
|
||||
|
||||
/// Creates a new cursor from a URL pointing to an image.
|
||||
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
|
||||
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
|
||||
///
|
||||
/// [PNG]: https://en.wikipedia.org/wiki/PNG
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder;
|
||||
|
||||
/// Crates a new animated cursor from multiple [`CustomCursor`]s.
|
||||
/// Supplied `cursors` can't be empty or other animations.
|
||||
fn from_animation(
|
||||
duration: Duration,
|
||||
cursors: Vec<CustomCursor>,
|
||||
) -> Result<CustomCursorBuilder, BadAnimation>;
|
||||
}
|
||||
|
||||
impl CustomCursorExtWebSys for CustomCursor {
|
||||
fn is_animation(&self) -> bool {
|
||||
self.inner.animation
|
||||
}
|
||||
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder {
|
||||
CustomCursorBuilder {
|
||||
inner: PlatformCustomCursorBuilder::Url {
|
||||
@@ -223,4 +273,92 @@ impl CustomCursorExtWebSys for CustomCursor {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_animation(
|
||||
duration: Duration,
|
||||
cursors: Vec<CustomCursor>,
|
||||
) -> Result<CustomCursorBuilder, BadAnimation> {
|
||||
if cursors.is_empty() {
|
||||
return Err(BadAnimation::Empty);
|
||||
}
|
||||
|
||||
if cursors.iter().any(CustomCursor::is_animation) {
|
||||
return Err(BadAnimation::Animation);
|
||||
}
|
||||
|
||||
Ok(CustomCursorBuilder {
|
||||
inner: PlatformCustomCursorBuilder::Animation { duration, cursors },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An error produced when using [`CustomCursor::from_animation`] with invalid arguments.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BadAnimation {
|
||||
/// Produced when no cursors were supplied.
|
||||
Empty,
|
||||
/// Produced when a supplied cursor is an animation.
|
||||
Animation,
|
||||
}
|
||||
|
||||
impl fmt::Display for BadAnimation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Empty => write!(f, "No cursors supplied"),
|
||||
Self::Animation => write!(f, "A supplied cursor is an animtion"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadAnimation {}
|
||||
|
||||
pub trait CustomCursorBuilderExtWebSys {
|
||||
/// Async version of [`CustomCursorBuilder::build()`] which waits until the
|
||||
/// cursor has completely finished loading.
|
||||
fn build_async(self, window_target: &EventLoopWindowTarget) -> CustomCursorFuture;
|
||||
}
|
||||
|
||||
impl CustomCursorBuilderExtWebSys for CustomCursorBuilder {
|
||||
fn build_async(self, window_target: &EventLoopWindowTarget) -> CustomCursorFuture {
|
||||
CustomCursorFuture(PlatformCustomCursor::build_async(
|
||||
self.inner,
|
||||
&window_target.p,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(web_platform))]
|
||||
struct PlatformCustomCursorFuture;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CustomCursorFuture(PlatformCustomCursorFuture);
|
||||
|
||||
impl Future for CustomCursorFuture {
|
||||
type Output = Result<CustomCursor, CustomCursorError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Pin::new(&mut self.0)
|
||||
.poll(cx)
|
||||
.map_ok(|cursor| CustomCursor { inner: cursor })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CustomCursorError {
|
||||
Blob,
|
||||
Decode(String),
|
||||
Animation,
|
||||
}
|
||||
|
||||
impl Display for CustomCursorError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Blob => write!(f, "failed to create `Blob`"),
|
||||
Self::Decode(error) => write!(f, "failed to decode image: {error}"),
|
||||
Self::Animation => write!(
|
||||
f,
|
||||
"found `CustomCursor` that is an animation when building an animation"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,9 @@ use std::{ffi::c_void, path::Path};
|
||||
|
||||
use crate::{
|
||||
dpi::PhysicalSize,
|
||||
event::{DeviceId, KeyEvent},
|
||||
event::DeviceId,
|
||||
event_loop::EventLoopBuilder,
|
||||
keyboard::Key,
|
||||
monitor::MonitorHandle,
|
||||
platform::modifier_supplement::KeyEventExtModifierSupplement,
|
||||
platform_impl::WinIcon,
|
||||
window::{BadIcon, Icon, Window, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -18,6 +15,92 @@ pub type HMENU = isize;
|
||||
/// Monitor Handle type used by Win32 API
|
||||
pub type HMONITOR = isize;
|
||||
|
||||
/// Describes a system-drawn backdrop material of a window.
|
||||
///
|
||||
/// For a detailed explanation, see [`DWM_SYSTEMBACKDROP_TYPE docs`].
|
||||
///
|
||||
/// [`DWM_SYSTEMBACKDROP_TYPE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum BackdropType {
|
||||
/// Corresponds to `DWMSBT_AUTO`.
|
||||
///
|
||||
/// Usually draws a default backdrop effect on the title bar.
|
||||
#[default]
|
||||
Auto = 0,
|
||||
|
||||
/// Corresponds to `DWMSBT_NONE`.
|
||||
None = 1,
|
||||
|
||||
/// Corresponds to `DWMSBT_MAINWINDOW`.
|
||||
///
|
||||
/// Draws the Mica backdrop material.
|
||||
MainWindow = 2,
|
||||
|
||||
/// Corresponds to `DWMSBT_TRANSIENTWINDOW`.
|
||||
///
|
||||
/// Draws the Background Acrylic backdrop material.
|
||||
TransientWindow = 3,
|
||||
|
||||
/// Corresponds to `DWMSBT_TABBEDWINDOW`.
|
||||
///
|
||||
/// Draws the Alt Mica backdrop material.
|
||||
TabbedWindow = 4,
|
||||
}
|
||||
|
||||
/// Describes a color used by Windows
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Color(u32);
|
||||
|
||||
impl Color {
|
||||
/// Use the system's default color
|
||||
pub const SYSTEM_DEFAULT: Color = Color(0xFFFFFFFF);
|
||||
|
||||
//Special constant only valid for the window border and therefore modeled using Option<Color> for user facing code
|
||||
const NONE: Color = Color(0xFFFFFFFE);
|
||||
|
||||
/// Create a new color from the given RGB values
|
||||
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
|
||||
Self((r as u32) | ((g as u32) << 8) | ((b as u32) << 16))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Color {
|
||||
fn default() -> Self {
|
||||
Self::SYSTEM_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes how the corners of a window should look like.
|
||||
///
|
||||
/// For a detailed explanation, see [`DWM_WINDOW_CORNER_PREFERENCE docs`].
|
||||
///
|
||||
/// [`DWM_WINDOW_CORNER_PREFERENCE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum CornerPreference {
|
||||
/// Corresponds to `DWMWCP_DEFAULT`.
|
||||
///
|
||||
/// Let the system decide when to round window corners.
|
||||
#[default]
|
||||
Default = 0,
|
||||
|
||||
/// Corresponds to `DWMWCP_DONOTROUND`.
|
||||
///
|
||||
/// Never round window corners.
|
||||
DoNotRound = 1,
|
||||
|
||||
/// Corresponds to `DWMWCP_ROUND`.
|
||||
///
|
||||
/// Round the corners, if appropriate.
|
||||
Round = 2,
|
||||
|
||||
/// Corresponds to `DWMWCP_ROUNDSMALL`.
|
||||
///
|
||||
/// Round the corners if appropriate, with a small radius.
|
||||
RoundSmall = 3,
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Windows.
|
||||
pub trait EventLoopBuilderExtWindows {
|
||||
/// Whether to allow the event loop to be created off of the main thread.
|
||||
@@ -136,6 +219,31 @@ pub trait WindowExtWindows {
|
||||
///
|
||||
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
|
||||
fn set_undecorated_shadow(&self, shadow: bool);
|
||||
|
||||
/// Sets system-drawn backdrop type.
|
||||
///
|
||||
/// Requires Windows 11 build 22523+.
|
||||
fn set_system_backdrop(&self, backdrop_type: BackdropType);
|
||||
|
||||
/// Sets the color of the window border.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn set_border_color(&self, color: Option<Color>);
|
||||
|
||||
/// Sets the background color of the title bar.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn set_title_background_color(&self, color: Option<Color>);
|
||||
|
||||
/// Sets the color of the window title.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn set_title_text_color(&self, color: Color);
|
||||
|
||||
/// Sets the preferred style of the window corners.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn set_corner_preference(&self, preference: CornerPreference);
|
||||
}
|
||||
|
||||
impl WindowExtWindows for Window {
|
||||
@@ -158,6 +266,34 @@ impl WindowExtWindows for Window {
|
||||
fn set_undecorated_shadow(&self, shadow: bool) {
|
||||
self.window.set_undecorated_shadow(shadow)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_system_backdrop(&self, backdrop_type: BackdropType) {
|
||||
self.window.set_system_backdrop(backdrop_type)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_border_color(&self, color: Option<Color>) {
|
||||
self.window.set_border_color(color.unwrap_or(Color::NONE))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_title_background_color(&self, color: Option<Color>) {
|
||||
// The windows docs don't mention NONE as a valid options but it works in practice and is useful
|
||||
// to circumvent the Windows option "Show accent color on title bars and window borders"
|
||||
self.window
|
||||
.set_title_background_color(color.unwrap_or(Color::NONE))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_title_text_color(&self, color: Color) {
|
||||
self.window.set_title_text_color(color)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_corner_preference(&self, preference: CornerPreference) {
|
||||
self.window.set_corner_preference(preference)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Windows.
|
||||
@@ -185,7 +321,14 @@ pub trait WindowBuilderExtWindows {
|
||||
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
|
||||
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
|
||||
///
|
||||
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu
|
||||
#[cfg_attr(
|
||||
platform_windows,
|
||||
doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(platform_windows),
|
||||
doc = "[`CreateMenu`]: #only-available-on-windows"
|
||||
)]
|
||||
fn with_menu(self, menu: HMENU) -> Self;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
@@ -213,54 +356,118 @@ pub trait WindowBuilderExtWindows {
|
||||
/// The shadow is hidden by default.
|
||||
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
|
||||
fn with_undecorated_shadow(self, shadow: bool) -> Self;
|
||||
|
||||
/// Sets system-drawn backdrop type.
|
||||
///
|
||||
/// Requires Windows 11 build 22523+.
|
||||
fn with_system_backdrop(self, backdrop_type: BackdropType) -> Self;
|
||||
|
||||
/// This sets or removes `WS_CLIPCHILDREN` style.
|
||||
fn with_clip_children(self, flag: bool) -> Self;
|
||||
|
||||
/// Sets the color of the window border.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn with_border_color(self, color: Option<Color>) -> Self;
|
||||
|
||||
/// Sets the background color of the title bar.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn with_title_background_color(self, color: Option<Color>) -> Self;
|
||||
|
||||
/// Sets the color of the window title.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn with_title_text_color(self, color: Color) -> Self;
|
||||
|
||||
/// Sets the preferred style of the window corners.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn with_corner_preference(self, corners: CornerPreference) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtWindows for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_owner_window(mut self, parent: HWND) -> Self {
|
||||
self.platform_specific.owner = Some(parent);
|
||||
self.window.platform_specific.owner = Some(parent);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_menu(mut self, menu: HMENU) -> Self {
|
||||
self.platform_specific.menu = Some(menu);
|
||||
self.window.platform_specific.menu = Some(menu);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self {
|
||||
self.platform_specific.taskbar_icon = taskbar_icon;
|
||||
self.window.platform_specific.taskbar_icon = taskbar_icon;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_no_redirection_bitmap(mut self, flag: bool) -> Self {
|
||||
self.platform_specific.no_redirection_bitmap = flag;
|
||||
self.window.platform_specific.no_redirection_bitmap = flag;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_drag_and_drop(mut self, flag: bool) -> Self {
|
||||
self.platform_specific.drag_and_drop = flag;
|
||||
self.window.platform_specific.drag_and_drop = flag;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_skip_taskbar(mut self, skip: bool) -> Self {
|
||||
self.platform_specific.skip_taskbar = skip;
|
||||
self.window.platform_specific.skip_taskbar = skip;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_class_name<S: Into<String>>(mut self, class_name: S) -> Self {
|
||||
self.platform_specific.class_name = class_name.into();
|
||||
self.window.platform_specific.class_name = class_name.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_undecorated_shadow(mut self, shadow: bool) -> Self {
|
||||
self.platform_specific.decoration_shadow = shadow;
|
||||
self.window.platform_specific.decoration_shadow = shadow;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_system_backdrop(mut self, backdrop_type: BackdropType) -> Self {
|
||||
self.window.platform_specific.backdrop_type = backdrop_type;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_clip_children(mut self, flag: bool) -> Self {
|
||||
self.window.platform_specific.clip_children = flag;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_border_color(mut self, color: Option<Color>) -> Self {
|
||||
self.window.platform_specific.border_color = Some(color.unwrap_or(Color::NONE));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_title_background_color(mut self, color: Option<Color>) -> Self {
|
||||
self.window.platform_specific.title_background_color = Some(color.unwrap_or(Color::NONE));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_title_text_color(mut self, color: Color) -> Self {
|
||||
self.window.platform_specific.title_text_color = Some(color);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_corner_preference(mut self, corners: CornerPreference) -> Self {
|
||||
self.window.platform_specific.corner_preference = Some(corners);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -297,7 +504,7 @@ pub trait DeviceIdExtWindows {
|
||||
impl DeviceIdExtWindows for DeviceId {
|
||||
#[inline]
|
||||
fn persistent_identifier(&self) -> Option<String> {
|
||||
self.0.persistent_identifier()
|
||||
crate::platform_impl::persistent_identifier(*self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,27 +535,12 @@ impl IconExtWindows for Icon {
|
||||
path: P,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
let win_icon = WinIcon::from_path(path, size)?;
|
||||
let win_icon = crate::platform_impl::WinIcon::from_path(path, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
|
||||
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
|
||||
let win_icon = WinIcon::from_resource(ordinal, size)?;
|
||||
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyEventExtModifierSupplement for KeyEvent {
|
||||
#[inline]
|
||||
fn text_with_all_modifiers(&self) -> Option<&str> {
|
||||
self.platform_specific
|
||||
.text_with_all_modifers
|
||||
.as_ref()
|
||||
.map(|s| s.as_str())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn key_without_modifiers(&self) -> Key {
|
||||
self.platform_specific.key_without_modifiers.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
@@ -5,9 +8,50 @@ use crate::{
|
||||
};
|
||||
|
||||
use crate::dpi::Size;
|
||||
use crate::platform_impl::{ApplicationName, Backend, XLIB_ERROR_HOOKS};
|
||||
|
||||
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
|
||||
/// X window type. Maps directly to
|
||||
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum WindowType {
|
||||
/// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the
|
||||
/// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying
|
||||
/// root window clicks.
|
||||
Desktop,
|
||||
/// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows.
|
||||
Dock,
|
||||
/// Toolbar windows. "Torn off" from the main application.
|
||||
Toolbar,
|
||||
/// Pinnable menu windows. "Torn off" from the main application.
|
||||
Menu,
|
||||
/// A small persistent utility window, such as a palette or toolbox.
|
||||
Utility,
|
||||
/// The window is a splash screen displayed as an application is starting up.
|
||||
Splash,
|
||||
/// This is a dialog window.
|
||||
Dialog,
|
||||
/// A dropdown menu that usually appears when the user clicks on an item in a menu bar.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
DropdownMenu,
|
||||
/// A popup menu that usually appears when the user right clicks on an object.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
PopupMenu,
|
||||
/// A tooltip window. Usually used to show additional information when hovering over an object with the cursor.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
Tooltip,
|
||||
/// The window is a notification.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
Notification,
|
||||
/// This should be used on the windows that are popped up by combo boxes.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
Combo,
|
||||
/// This indicates the the window is being dragged.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
Dnd,
|
||||
/// This is a normal, top-level window.
|
||||
#[default]
|
||||
Normal,
|
||||
}
|
||||
|
||||
/// The first argument in the provided hook will be the pointer to `XDisplay`
|
||||
/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
|
||||
@@ -38,7 +82,10 @@ pub type XWindow = u32;
|
||||
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
|
||||
// Append new hook.
|
||||
unsafe {
|
||||
XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
|
||||
crate::platform_impl::XLIB_ERROR_HOOKS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(hook);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +95,7 @@ pub trait EventLoopWindowTargetExtX11 {
|
||||
fn is_x11(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtX11 for EventLoopWindowTarget<T> {
|
||||
impl EventLoopWindowTargetExtX11 for EventLoopWindowTarget {
|
||||
#[inline]
|
||||
fn is_x11(&self) -> bool {
|
||||
!self.p.is_wayland()
|
||||
@@ -70,7 +117,7 @@ pub trait EventLoopBuilderExtX11 {
|
||||
impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
|
||||
#[inline]
|
||||
fn with_x11(&mut self) -> &mut Self {
|
||||
self.platform_specific.forced_backend = Some(Backend::X);
|
||||
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -96,29 +143,29 @@ pub trait WindowBuilderExtX11 {
|
||||
/// Build window with the given `general` and `instance` names.
|
||||
///
|
||||
/// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
|
||||
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
|
||||
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "instance", "general"`.
|
||||
///
|
||||
/// For details about application ID conventions, see the
|
||||
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
|
||||
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
|
||||
|
||||
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
|
||||
/// Build window with override-redirect flag; defaults to false.
|
||||
fn with_override_redirect(self, override_redirect: bool) -> Self;
|
||||
|
||||
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
|
||||
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
|
||||
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`.
|
||||
fn with_x11_window_type(self, x11_window_type: Vec<WindowType>) -> Self;
|
||||
|
||||
/// Build window with base size hint. Only implemented on X11.
|
||||
/// Build window with base size hint.
|
||||
///
|
||||
/// ```
|
||||
/// # use winit::dpi::{LogicalSize, PhysicalSize};
|
||||
/// # use winit::window::WindowBuilder;
|
||||
/// # use winit::window::Window;
|
||||
/// # use winit::platform::x11::WindowBuilderExtX11;
|
||||
/// // Specify the size in logical dimensions like this:
|
||||
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
|
||||
/// Window::builder().with_base_size(LogicalSize::new(400.0, 200.0));
|
||||
///
|
||||
/// // Or specify the size in physical dimensions like this:
|
||||
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
|
||||
/// Window::builder().with_base_size(PhysicalSize::new(400, 200));
|
||||
/// ```
|
||||
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
|
||||
|
||||
@@ -127,12 +174,12 @@ pub trait WindowBuilderExtX11 {
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use winit::window::WindowBuilder;
|
||||
/// use winit::window::Window;
|
||||
/// 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()
|
||||
/// let window = Window::builder()
|
||||
/// .with_embed_parent_window(parent_window_id)
|
||||
/// .build(&event_loop)?;
|
||||
/// # Ok(()) }
|
||||
@@ -143,43 +190,46 @@ pub trait WindowBuilderExtX11 {
|
||||
impl WindowBuilderExtX11 for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_x11_visual(mut self, visual_id: XVisualID) -> Self {
|
||||
self.platform_specific.x11.visual_id = Some(visual_id);
|
||||
self.window.platform_specific.x11.visual_id = Some(visual_id);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_x11_screen(mut self, screen_id: i32) -> Self {
|
||||
self.platform_specific.x11.screen_id = Some(screen_id);
|
||||
self.window.platform_specific.x11.screen_id = Some(screen_id);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
|
||||
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
|
||||
self.window.platform_specific.name = Some(crate::platform_impl::ApplicationName::new(
|
||||
general.into(),
|
||||
instance.into(),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
|
||||
self.platform_specific.x11.override_redirect = override_redirect;
|
||||
self.window.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.x11_window_types = x11_window_types;
|
||||
fn with_x11_window_type(mut self, x11_window_types: Vec<WindowType>) -> Self {
|
||||
self.window.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.x11.base_size = Some(base_size.into());
|
||||
self.window.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.window.platform_specific.x11.embed_window = Some(parent_window_id);
|
||||
self
|
||||
}
|
||||
}
|
||||