Compare commits

..

6 Commits

Author SHA1 Message Date
John Nunley
d5240c4650 Update to newest master
Signed-off-by: John Nunley <dev@notgull.net>
2023-12-19 16:14:17 -08:00
John Nunley
1c65f70d59 Update for 0.29.3 changes
Signed-off-by: John Nunley <dev@notgull.net>
2023-12-19 16:12:09 -08:00
John Nunley
4a30d0eadd Remove superseded TODO comment
Signed-off-by: John Nunley <dev@notgull.net>
2023-12-19 16:12:09 -08:00
John Nunley
39b5437951 Use XSetEventQueueOwner to indicate XCB
I don't know what this actually does, but it can't hurt, right?

Signed-off-by: John Nunley <dev@notgull.net>
2023-12-19 16:12:09 -08:00
John Nunley
9477514ad5 Use x11rb for event handling
Signed-off-by: John Nunley <dev@notgull.net>
2023-12-19 16:12:07 -08:00
John Nunley
5c9f1b2e9a Switch to using xim for IME
This is broken until we start using x11rb for event lookups.

Signed-off-by: John Nunley <dev@notgull.net>
2023-12-19 16:09:52 -08:00
196 changed files with 10522 additions and 12621 deletions

16
.github/CODEOWNERS vendored
View File

@@ -1,6 +1,12 @@
# Core maintainers:
# - @msiglreith
# - @kchibisov
# - @madsmtm
# - @maroider
# Android # Android
/src/platform/android.rs @msiglreith @MarijnS95 /src/platform/android.rs @msiglreith
/src/platform_impl/android @msiglreith @MarijnS95 /src/platform_impl/android @msiglreith
# iOS # iOS
/src/platform/ios.rs @madsmtm /src/platform/ios.rs @madsmtm
@@ -14,14 +20,14 @@
/src/platform_impl/linux/wayland @kchibisov /src/platform_impl/linux/wayland @kchibisov
# X11 # X11
/src/platform/x11.rs @kchibisov @notgull /src/platform/x11.rs @kchibisov
/src/platform_impl/linux/x11 @kchibisov @notgull /src/platform_impl/linux/x11 @kchibisov
# macOS # macOS
/src/platform/macos.rs @madsmtm /src/platform/macos.rs @madsmtm
/src/platform_impl/macos @madsmtm /src/platform_impl/macos @madsmtm
# Web # Web (no maintainer)
/src/platform/web.rs @daxpedda /src/platform/web.rs @daxpedda
/src/platform_impl/web @daxpedda /src/platform_impl/web @daxpedda

View File

@@ -24,7 +24,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
toolchain: [stable, nightly, '1.70.0'] toolchain: [stable, nightly, '1.65.0']
platform: platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml! # Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, } - { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
@@ -43,20 +43,11 @@ jobs:
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, } - { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude: exclude:
# Android is tested on stable-3 # Android is tested on stable-3
- toolchain: '1.70.0' - toolchain: '1.65.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
include: include:
- toolchain: '1.70.0' - toolchain: '1.69.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- toolchain: 'nightly'
platform: {
name: 'web Atomic',
target: wasm32-unknown-unknown,
os: ubuntu-latest,
options: '-Zbuild-std=panic_abort,std',
rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
components: rust-src,
}
env: env:
# Set more verbose terminal output # Set more verbose terminal output
@@ -64,7 +55,8 @@ jobs:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
# Faster compilation and error on warnings # Faster compilation and error on warnings
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}' RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings'
RUSTDOCFLAGS: '--deny=warnings'
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }} OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
CMD: ${{ matrix.platform.cmd }} CMD: ${{ matrix.platform.cmd }}
@@ -89,7 +81,7 @@ jobs:
- name: Generate lockfile - name: Generate lockfile
# Also updates the crates.io index # Also updates the crates.io index
run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 run: cargo generate-lockfile
- name: Install GCC Multilib - name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
@@ -117,12 +109,10 @@ jobs:
with: with:
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }} toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }} targets: ${{ matrix.platform.target }}
components: clippy, ${{ matrix.platform.components }} components: clippy
- name: Check documentation - name: Check documentation
run: cargo doc --no-deps $OPTIONS --document-private-items run: cargo doc --no-deps $OPTIONS --document-private-items
env:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}'
- name: Build crate - name: Build crate
run: cargo $CMD build $OPTIONS run: cargo $CMD build $OPTIONS
@@ -130,7 +120,7 @@ jobs:
- name: Build tests - name: Build tests
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.toolchain != '1.65.0'
run: cargo $CMD test --no-run $OPTIONS run: cargo $CMD test --no-run $OPTIONS
- name: Run tests - name: Run tests
@@ -139,7 +129,7 @@ jobs:
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.toolchain != '1.65.0'
run: cargo $CMD test $OPTIONS run: cargo $CMD test $OPTIONS
- name: Lint with clippy - name: Lint with clippy
@@ -149,7 +139,7 @@ jobs:
- name: Build tests with serde enabled - name: Build tests with serde enabled
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.toolchain != '1.65.0'
run: cargo $CMD test --no-run $OPTIONS --features serde run: cargo $CMD test --no-run $OPTIONS --features serde
- name: Run tests with serde enabled - name: Run tests with serde enabled
@@ -158,15 +148,9 @@ jobs:
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.toolchain != '1.65.0'
run: cargo $CMD test $OPTIONS --features serde run: cargo $CMD test $OPTIONS --features serde
- name: Check docs.rs documentation
if: matrix.toolchain == 'nightly'
run: cargo doc --no-deps $OPTIONS --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
env:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs'
# See restore step above # See restore step above
- name: Save cache of cargo folder - name: Save cache of cargo folder
uses: actions/cache/save@v3 uses: actions/cache/save@v3

View File

@@ -1,50 +0,0 @@
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

View File

@@ -11,100 +11,12 @@ Unreleased` header.
# Unreleased # Unreleased
- On X11, don't require XIM to run.
- Fix compatibility with 32-bit platforms without 64-bit atomics.
- On X11, fix swapped instance and general class names.
- **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`.
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example. - On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
- **Breaking:** Remove `Window::set_cursor_icon` - Add `Window::set_custom_cursor`
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`
- Add `CustomCursor` - Add `CustomCursor`
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data. - Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs. - Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s.
- On macOS, add services menu. - On macOS, add services menu.
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
- On Web, fix setting cursor icon overriding cursor visibility.
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
- On Web, add the ability to toggle calling `Event.preventDefault()` on `Window`.
- **Breaking:** Remove `WindowAttributes::fullscreen()` and expose as field directly.
- **Breaking:** Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold static data.
- **Breaking:** No longer export `platform::x11::XNotSupported`.
- **Breaking:** Renamed `platform::x11::XWindowType` to `platform::x11::WindowType`.
- Add the `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases.
- **Breaking:** Rename `TouchpadMagnify` to `PinchGesture`, `SmartMagnify` to `DoubleTapGesture` and `TouchpadRotate` to `RotationGesture` to represent the action rather than the intent.
- on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`.
- on Windows: add `with_system_backdrop`, `with_border_color`, `with_title_background_color`, `with_title_text_color` and `with_corner_preference`
- On Windows, Remove `WS_CAPTION`, `WS_BORDER` and `WS_EX_WINDOWEDGE` styles for child windows without decorations.
- On Windows, fixed a race condition when sending an event through the loop proxy.
- **Breaking:** Removed `EventLoopError::AlreadyRunning`, which can't happen as it is already prevented by the type system.
- On Wayland, disable `Occluded` event handling.
- Added `EventLoop::builder`, which is intended to replace the (now deprecated) `EventLoopBuilder::new`.
- **Breaking:** Changed the signature of `EventLoop::with_user_event` to return a builder.
- **Breaking:** Removed `EventLoopBuilder::with_user_event`, the functionality is now available in `EventLoop::with_user_event`.
- Add `Window::builder`, which is intended to replace the (now deprecated) `WindowBuilder::new`.
- On X11, reload dpi on `_XSETTINGS_SETTINGS` update.
- On X11, fix deadlock when adjusting DPI and resizing at the same time.
- On Wayland, fix `Focused(false)` being send when other seats still have window focused.
- On Wayland, fix `Window::set_{min,max}_inner_size` not always applied.
- On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop.
- On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
- On Orbital, fix `logical_key` and `text` not reported in `KeyEvent`.
- On Orbital, implement `KeyEventExtModifierSupplement`.
- On Orbital, map keys to `NamedKey` when possible.
- On Orbital, implement `set_cursor_grab`.
- On Orbital, implement `set_cursor_visible`.
- On Orbital, implement `drag_window`.
- On Orbital, implement `drag_resize_window`.
- On Orbital, implement `set_transparent`.
- On Orbital, implement `set_visible`.
- On Orbital, implement `is_visible`.
- On Orbital, implement `set_resizable`.
- On Orbital, implement `is_resizable`.
- On Orbital, implement `set_maximized`.
- On Orbital, implement `is_maximized`.
- On Orbital, implement `set_decorations`.
- On Orbital, implement `is_decorated`.
- On Orbital, implement `set_window_level`.
- On Orbital, emit `DeviceEvent::MouseMotion`.
- On Wayland, fix title in CSD not updated from `AboutToWait`.
# 0.29.10
- On Web, account for canvas being focused already before event loop starts.
- On Web, increase cursor position accuracy.
# 0.29.9
- On X11, fix `NotSupported` error not propagated when creating event loop.
- On Wayland, fix resize not issued when scale changes
- On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`.
- On macOS, report correct logical key when Ctrl or Cmd is pressed.
# 0.29.8
- On X11, fix IME input lagging behind.
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
- On X11, fix keymap not updated from xmodmap.
- On X11, reduce the amount of time spent fetching screen resources.
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
- On Wayland, fix `Window::inner_size` not using the correct rounding.
# 0.29.7
- On X11, fix `Xft.dpi` reload during runtime.
- On X11, fix window minimize.
# 0.29.6
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
- On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window.
- Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation
# 0.29.5
- On macOS, remove spurious error logging when handling `Fn`. - On macOS, remove spurious error logging when handling `Fn`.
- On X11, fix an issue where floating point data from the server is - On X11, fix an issue where floating point data from the server is
misinterpreted during a drag and drop operation. misinterpreted during a drag and drop operation.
@@ -113,8 +25,8 @@ Unreleased` header.
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported. - On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
- On X11, fix `Xft.dpi` detection from Xresources. - On X11, fix `Xft.dpi` detection from Xresources.
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`. - On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
- On Wayland, fix resize being sent on focus change. - On Web, remove queuing fullscreen request in absence of transient activation.
- On Windows, fix `set_ime_cursor_area`. - On Web, fix setting cursor icon overriding cursor visibility.
# 0.29.4 # 0.29.4
@@ -126,7 +38,6 @@ Unreleased` header.
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event. - On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
- On Wayland, fix `wl_surface` being destroyed before associated objects. - On Wayland, fix `wl_surface` being destroyed before associated objects.
- On macOS, fix assertion when pressing `Fn` key. - On macOS, fix assertion when pressing `Fn` key.
- On Windows, add `WindowBuilderExtWindows::with_clip_children` to control `WS_CLIPCHILDREN` style.
# 0.29.3 # 0.29.3

View File

@@ -20,7 +20,7 @@ your description of the issue as detailed as possible:
When making a code contribution to winit, before opening your pull request, please make sure that: When making a code contribution to winit, before opening your pull request, please make sure that:
- your patch builds with Winit's minimal supported rust version - Rust 1.70. - your patch builds with Winit's minimal supported rust version - Rust 1.65.
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms - you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit - you updated any relevant documentation in winit

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "winit" name = "winit"
version = "0.29.10" version = "0.29.4"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"] authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library." description = "Cross-platform window creation library."
edition = "2021" edition = "2021"
@@ -10,7 +10,7 @@ readme = "README.md"
repository = "https://github.com/rust-windowing/winit" repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit" documentation = "https://docs.rs/winit"
categories = ["gui"] categories = ["gui"]
rust-version = "1.70.0" rust-version = "1.65.0"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = [ features = [
@@ -18,7 +18,6 @@ features = [
"rwh_05", "rwh_05",
"rwh_06", "rwh_06",
"serde", "serde",
"mint",
# Enabled to get docs to compile # Enabled to get docs to compile
"android-native-activity", "android-native-activity",
] ]
@@ -37,14 +36,14 @@ targets = [
"x86_64-apple-ios", "x86_64-apple-ios",
# Android # Android
"aarch64-linux-android", "aarch64-linux-android",
# Web # WebAssembly
"wasm32-unknown-unknown", "wasm32-unknown-unknown",
] ]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[features] [features]
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"] x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb", "xim"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"] wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
wayland-dlopen = ["wayland-backend/dlopen"] wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
@@ -58,7 +57,7 @@ rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"] rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
[build-dependencies] [build-dependencies]
cfg_aliases = "0.2.0" cfg_aliases = "0.1.1"
[dependencies] [dependencies]
bitflags = "2" bitflags = "2"
@@ -67,7 +66,7 @@ log = "0.4"
mint = { version = "0.5.6", optional = true } mint = { version = "0.5.6", optional = true }
once_cell = "1.12" once_cell = "1.12"
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true } 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_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 } rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
serde = { version = "1", optional = true, features = ["serde_derive"] } serde = { version = "1", optional = true, features = ["serde_derive"] }
smol_str = "0.2.0" smol_str = "0.2.0"
@@ -78,7 +77,7 @@ simple_logger = { version = "4.2.0", default_features = false }
winit = { path = ".", features = ["rwh_05"] } winit = { path = ".", features = ["rwh_05"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies] [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"] } softbuffer = "0.3.0"
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.5.0" android-activity = "0.5.0"
@@ -87,13 +86,13 @@ ndk-sys = "0.5.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3" core-foundation = "0.9.3"
objc2 = "0.5.0" objc2 = "0.4.1"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1" core-graphics = "0.23.1"
[target.'cfg(target_os = "macos")'.dependencies.icrate] [target.'cfg(target_os = "macos")'.dependencies.icrate]
version = "0.1.0" version = "0.0.4"
features = [ features = [
"dispatch", "dispatch",
"Foundation", "Foundation",
@@ -106,31 +105,10 @@ features = [
"Foundation_NSProcessInfo", "Foundation_NSProcessInfo",
"Foundation_NSThread", "Foundation_NSThread",
"Foundation_NSNumber", "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] [target.'cfg(target_os = "ios")'.dependencies.icrate]
version = "0.1.0" version = "0.0.4"
features = [ features = [
"dispatch", "dispatch",
"Foundation", "Foundation",
@@ -174,7 +152,7 @@ features = [
] ]
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies] [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.7", features = ["no-rng"], optional = true } ahash = { version = "0.8.3", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true } bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.12.3" calloop = "0.12.3"
libc = "0.2.64" libc = "0.2.64"
@@ -189,11 +167,12 @@ wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = tr
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true } wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
x11-dl = { version = "2.18.5", 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 } x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
xim = { version = "0.3.0", features = ["x11rb-client", "client"], optional = true }
xkbcommon-dl = "0.4.0" xkbcommon-dl = "0.4.0"
[target.'cfg(target_os = "redox")'.dependencies] [target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false } orbclient = { version = "0.3.42", default-features = false }
redox_syscall = "0.4.1" redox_syscall = "0.3"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys] [target.'cfg(target_family = "wasm")'.dependencies.web_sys]
package = "web-sys" package = "web-sys"
@@ -205,7 +184,6 @@ features = [
'console', 'console',
'CssStyleDeclaration', 'CssStyleDeclaration',
'Document', 'Document',
'DomException',
'DomRect', 'DomRect',
'DomRectReadOnly', 'DomRectReadOnly',
'Element', 'Element',
@@ -214,7 +192,6 @@ features = [
'FocusEvent', 'FocusEvent',
'HtmlCanvasElement', 'HtmlCanvasElement',
'HtmlElement', 'HtmlElement',
'HtmlImageElement',
'ImageBitmap', 'ImageBitmap',
'ImageBitmapOptions', 'ImageBitmapOptions',
'ImageBitmapRenderingContext', 'ImageBitmapRenderingContext',
@@ -241,15 +218,11 @@ features = [
] ]
[target.'cfg(target_family = "wasm")'.dependencies] [target.'cfg(target_family = "wasm")'.dependencies]
atomic-waker = "1"
js-sys = "0.3.64" js-sys = "0.3.64"
pin-project = "1"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-time = "1" web-time = "0.2"
[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] [target.'cfg(target_family = "wasm")'.dev-dependencies]
console_log = "1" console_log = "1"
@@ -260,6 +233,5 @@ members = [
"run-wasm", "run-wasm",
] ]
[[example]] [patch.crates-io]
doc-scrape-examples = true xim = { git = "https://github.com/forkgull/xim-rs", branch = "x11rb-13" }
name = "window"

View File

@@ -126,11 +126,6 @@ If your PR makes notable changes to Winit's features, please update this section
* Setting a menu bar * Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support * `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme * Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
* Changing a system-drawn backdrop
* Setting the window border color
* Setting the title bar background color
* Setting the title color
* Setting the corner rounding preference
### macOS ### macOS
* Window activation policy * Window activation policy

View File

@@ -2,13 +2,11 @@
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit) [![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit) [![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs
)](https://rust-windowing.github.io/winit/winit/index.html)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions) [![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
```toml ```toml
[dependencies] [dependencies]
winit = "0.29.10" winit = "0.29.4"
``` ```
## [Documentation](https://docs.rs/winit) ## [Documentation](https://docs.rs/winit)
@@ -19,9 +17,10 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
## Contact Us ## Contact Us
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit). Join us in any of these:
The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings). [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org)
[![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit)
## Usage ## Usage
@@ -43,7 +42,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
## MSRV Policy ## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to This crate's Minimum Supported Rust Version (MSRV) is **1.65**. Changes to
the MSRV will be accompanied by a minor version bump. the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following As a **tentative** policy, the upper bound of the MSRV is given by the following
@@ -75,7 +74,7 @@ same MSRV policy.
Note that windows don't appear on Wayland until you draw/present to them. Note that windows don't appear on Wayland until you draw/present to them.
#### Web #### WebAssembly
To run the web example: `cargo run-wasm --example web` To run the web example: `cargo run-wasm --example web`
@@ -86,7 +85,7 @@ either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
create a `<canvas>` element which you can then retrieve][web canvas getter] and create a `<canvas>` element which you can then retrieve][web canvas getter] and
insert it into the DOM yourself. insert it into the DOM yourself.
For the example code using Winit on Web, check out the [web example]. For For the example code using Winit with WebAssembly, check out the [web example]. For
information on using Rust on WebAssembly, check out the [Rust and WebAssembly information on using Rust on WebAssembly, check out the [Rust and WebAssembly
book]. book].
@@ -151,13 +150,13 @@ class. Your application _must_ specify the base class it needs via a feature fla
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries [agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
[Gradle]: https://developer.android.com/studio/build [Gradle]: https://developer.android.com/studio/build
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples). For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity` ##### Converting from `ndk-glue` to `android-activity`
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be: If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
1. Remove `ndk-glue` from your `Cargo.toml` 1. Remove `ndk-glue` from your `Cargo.toml`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.10", features = [ "android-native-activity" ] }` 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.4", features = [ "android-native-activity" ] }`
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above). 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above). 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).

View File

@@ -8,7 +8,7 @@ fn main() {
cfg_aliases! { cfg_aliases! {
// Systems. // Systems.
android_platform: { target_os = "android" }, android_platform: { target_os = "android" },
web_platform: { all(target_family = "wasm", target_os = "unknown") }, wasm_platform: { all(target_family = "wasm", not(target_os = "emscripten")) },
macos_platform: { target_os = "macos" }, macos_platform: { target_os = "macos" },
ios_platform: { target_os = "ios" }, ios_platform: { target_os = "ios" },
windows_platform: { target_os = "windows" }, windows_platform: { target_os = "windows" },
@@ -17,8 +17,8 @@ fn main() {
redox: { target_os = "redox" }, redox: { target_os = "redox" },
// Native displays. // Native displays.
x11_platform: { all(feature = "x11", free_unix, not(redox)) }, x11_platform: { all(feature = "x11", free_unix, not(wasm), not(redox)) },
wayland_platform: { all(feature = "wayland", free_unix, not(redox)) }, wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) },
orbital_platform: { redox }, orbital_platform: { redox },
} }
} }

View File

@@ -11,5 +11,4 @@ disallowed-methods = [
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." }, { path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ path = "icrate::AppKit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
] ]

View File

@@ -34,6 +34,7 @@ skip = [
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this { name = "raw-window-handle" }, # we intentionally have multiple versions of this
{ name = "bitflags" }, # the ecosystem is in the process of migrating. { name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "libloading" }, # x11rb uses a different version until the next update { name = "libloading" }, # x11rb uses a different version until the next update
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
] ]
skip-tree = [] skip-tree = []

View File

@@ -18,16 +18,16 @@ fn main() -> Result<(), impl std::error::Error> {
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{EventLoop, EventLoopWindowTarget}, event_loop::{EventLoop, EventLoopWindowTarget},
raw_window_handle::HasRawWindowHandle, raw_window_handle::HasRawWindowHandle,
window::{Window, WindowId}, window::{Window, WindowBuilder, WindowId},
}; };
fn spawn_child_window( fn spawn_child_window(
parent: &Window, parent: &Window,
event_loop: &EventLoopWindowTarget, event_loop: &EventLoopWindowTarget<()>,
windows: &mut HashMap<WindowId, Window>, windows: &mut HashMap<WindowId, Window>,
) { ) {
let parent = parent.raw_window_handle().unwrap(); let parent = parent.raw_window_handle().unwrap();
let mut builder = Window::builder() let mut builder = WindowBuilder::new()
.with_title("child window") .with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32)) .with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
@@ -44,7 +44,7 @@ fn main() -> Result<(), impl std::error::Error> {
let mut windows = HashMap::new(); let mut windows = HashMap::new();
let event_loop: EventLoop<()> = EventLoop::new().unwrap(); let event_loop: EventLoop<()> = EventLoop::new().unwrap();
let parent_window = Window::builder() let parent_window = WindowBuilder::new()
.with_title("parent window") .with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32)) .with_inner_size(LogicalSize::new(640.0f32, 480.0f32))

View File

@@ -1,9 +1,9 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
use std::thread; use std::thread;
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
use std::time; use std::time;
#[cfg(web_platform)] #[cfg(wasm_platform)]
use web_time as time; use web_time as time;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
@@ -11,7 +11,7 @@ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
keyboard::{Key, NamedKey}, keyboard::{Key, NamedKey},
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -37,7 +37,7 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Press 'Esc' to close the window."); println!("Press 'Esc' to close the window.");
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.") .with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();

View File

@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::{CursorIcon, Window}, window::{CursorIcon, WindowBuilder},
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
window.set_title("A fantastic window!"); window.set_title("A fantastic window!");
let mut cursor_idx = 0; let mut cursor_idx = 0;
@@ -31,7 +31,7 @@ fn main() -> Result<(), impl std::error::Error> {
.. ..
} => { } => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]); println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor(CURSORS[cursor_idx]); window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 { if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1; cursor_idx += 1;
} else { } else {

View File

@@ -5,7 +5,7 @@ use winit::{
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent}, event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
keyboard::{Key, ModifiersState, NamedKey}, keyboard::{Key, ModifiersState, NamedKey},
window::{CursorGrabMode, Window}, window::{CursorGrabMode, WindowBuilder},
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -15,7 +15,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("Super Cursor Grab'n'Hide Simulator 9000") .with_title("Super Cursor Grab'n'Hide Simulator 9000")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();

View File

@@ -1,49 +1,38 @@
#![allow(clippy::single_match, clippy::disallowed_methods)] #![allow(clippy::single_match, clippy::disallowed_methods)]
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{EventLoop, EventLoopWindowTarget}, event_loop::EventLoop,
keyboard::Key, keyboard::Key,
window::{CursorIcon, CustomCursor, Window}, window::{CustomCursor, WindowBuilder},
};
#[cfg(web_platform)]
use {
std::sync::atomic::{AtomicU64, Ordering},
std::time::Duration,
winit::platform::web::CustomCursorExtWebSys,
}; };
#[cfg(web_platform)] fn decode_cursor(bytes: &[u8]) -> CustomCursor {
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 img = image::load_from_memory(bytes).unwrap().to_rgba8();
let samples = img.into_flat_samples(); let samples = img.into_flat_samples();
let (_, w, h) = samples.extents(); let (_, w, h) = samples.extents();
let (w, h) = (w as u16, h as u16); let (w, h) = (w as u16, h as u16);
let builder = CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap(); CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
builder.build(window_target)
} }
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
fn main() -> Result<(), impl std::error::Error> { fn main() -> Result<(), impl std::error::Error> {
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
SimpleLogger::new() SimpleLogger::new()
.with_level(log::LevelFilter::Info) .with_level(log::LevelFilter::Info)
.init() .init()
.unwrap(); .unwrap();
#[cfg(web_platform)] #[cfg(wasm_platform)]
console_log::init_with_level(log::Level::Debug).unwrap(); console_log::init_with_level(log::Level::Debug).unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let builder = Window::builder().with_title("A fantastic window!"); let builder = WindowBuilder::new().with_title("A fantastic window!");
#[cfg(web_platform)] #[cfg(wasm_platform)]
let builder = { let builder = {
use winit::platform::web::WindowBuilderExtWebSys; use winit::platform::web::WindowBuilderExtWebSys;
builder.with_append(true) builder.with_append(true)
@@ -54,9 +43,8 @@ fn main() -> Result<(), impl std::error::Error> {
let mut cursor_visible = true; let mut cursor_visible = true;
let custom_cursors = [ let custom_cursors = [
decode_cursor(include_bytes!("data/cross.png"), &event_loop), decode_cursor(include_bytes!("data/cross.png")),
decode_cursor(include_bytes!("data/cross2.png"), &event_loop), decode_cursor(include_bytes!("data/cross2.png")),
decode_cursor(include_bytes!("data/gradient.png"), &event_loop),
]; ];
event_loop.run(move |event, _elwt| match event { event_loop.run(move |event, _elwt| match event {
@@ -72,65 +60,26 @@ fn main() -> Result<(), impl std::error::Error> {
} => match key.as_ref() { } => match key.as_ref() {
Key::Character("1") => { Key::Character("1") => {
log::debug!("Setting cursor to {:?}", cursor_idx); log::debug!("Setting cursor to {:?}", cursor_idx);
window.set_cursor(custom_cursors[cursor_idx].clone()); window.set_custom_cursor(&custom_cursors[cursor_idx]);
cursor_idx = (cursor_idx + 1) % 3; cursor_idx = (cursor_idx + 1) % 2;
} }
Key::Character("2") => { Key::Character("2") => {
log::debug!("Setting cursor icon to default"); log::debug!("Setting cursor icon to default");
window.set_cursor(CursorIcon::default()); window.set_cursor_icon(Default::default());
} }
Key::Character("3") => { Key::Character("3") => {
cursor_visible = !cursor_visible; cursor_visible = !cursor_visible;
log::debug!("Setting cursor visibility to {:?}", cursor_visible); log::debug!("Setting cursor visibility to {:?}", cursor_visible);
window.set_cursor_visible(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 => { WindowEvent::RedrawRequested => {
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
fill::fill_window(&window); fill::fill_window(&window);
} }
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
_elwt.exit(); _elwt.exit();
} }
_ => (), _ => (),

View File

@@ -1,12 +1,12 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
fn main() -> Result<(), impl std::error::Error> { fn main() -> Result<(), impl std::error::Error> {
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoopBuilder,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -18,9 +18,11 @@ fn main() -> Result<(), impl std::error::Error> {
} }
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap(); let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event()
.build()
.unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
@@ -54,7 +56,7 @@ fn main() -> Result<(), impl std::error::Error> {
}) })
} }
#[cfg(web_platform)] #[cfg(wasm_platform)]
fn main() { fn main() {
panic!("This example is not supported on web."); panic!("This example is not supported on web.");
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

View File

@@ -5,7 +5,7 @@ use winit::{
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent}, event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
keyboard::Key, keyboard::Key,
window::{Window, WindowId}, window::{Window, WindowBuilder, WindowId},
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -15,8 +15,8 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window_1 = Window::builder().build(&event_loop).unwrap(); let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
let window_2 = Window::builder().build(&event_loop).unwrap(); let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
let mut switched = false; let mut switched = false;
let mut entered_id = window_2.id(); let mut entered_id = window_2.id();

View File

@@ -3,14 +3,14 @@
//! Example for focusing a window. //! Example for focusing a window.
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
use std::time; use std::time;
#[cfg(web_platform)] #[cfg(wasm_platform)]
use web_time as time; use web_time as time;
use winit::{ use winit::{
event::{Event, StartCause, WindowEvent}, event::{Event, StartCause, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -20,7 +20,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop) .build(&event_loop)

View File

@@ -1,11 +1,11 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::dpi::LogicalSize; use winit::dpi::PhysicalSize;
use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::EventLoop; use winit::event_loop::EventLoop;
use winit::keyboard::{Key, NamedKey}; use winit::keyboard::{Key, NamedKey};
use winit::window::{Fullscreen, Window}; use winit::window::{Fullscreen, WindowBuilder};
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use winit::platform::macos::WindowExtMacOS; use winit::platform::macos::WindowExtMacOS;
@@ -22,7 +22,7 @@ fn main() -> Result<(), impl std::error::Error> {
let mut with_min_size = false; let mut with_min_size = false;
let mut with_max_size = false; let mut with_max_size = false;
let window = Window::builder() let window = WindowBuilder::new()
.with_title("Hello world!") .with_title("Hello world!")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
@@ -126,7 +126,7 @@ fn main() -> Result<(), impl std::error::Error> {
"i" => { "i" => {
with_min_size = !with_min_size; with_min_size = !with_min_size;
let min_size = if with_min_size { let min_size = if with_min_size {
Some(LogicalSize::new(100, 100)) Some(PhysicalSize::new(100, 100))
} else { } else {
None None
}; };
@@ -139,7 +139,7 @@ fn main() -> Result<(), impl std::error::Error> {
"a" => { "a" => {
with_max_size = !with_max_size; with_max_size = !with_max_size;
let max_size = if with_max_size { let max_size = if with_max_size {
Some(LogicalSize::new(200, 200)) Some(PhysicalSize::new(200, 200))
} else { } else {
None None
}; };

View File

@@ -5,7 +5,7 @@ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
keyboard::Key, keyboard::Key,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -15,7 +15,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("Your faithful window") .with_title("Your faithful window")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();

View File

@@ -7,7 +7,7 @@ use winit::{
event::{ElementState, Event, Ime, WindowEvent}, event::{ElementState, Event, Ime, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
keyboard::NamedKey, keyboard::NamedKey,
window::{ImePurpose, Window}, window::{ImePurpose, WindowBuilder},
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -26,7 +26,7 @@ fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64)) .with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();

View File

@@ -8,7 +8,7 @@ use winit::{
keyboard::{Key, ModifiersState}, keyboard::{Key, ModifiersState},
// WARNING: This is not available on all platforms (for example on the web). // WARNING: This is not available on all platforms (for example on the web).
platform::modifier_supplement::KeyEventExtModifierSupplement, platform::modifier_supplement::KeyEventExtModifierSupplement,
window::Window, window::WindowBuilder,
}; };
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
@@ -24,7 +24,7 @@ fn main() -> Result<(), impl std::error::Error> {
simple_logger::SimpleLogger::new().init().unwrap(); simple_logger::SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_inner_size(LogicalSize::new(400.0, 200.0)) .with_inner_size(LogicalSize::new(400.0, 200.0))
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();

View File

@@ -3,12 +3,12 @@
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::dpi::{PhysicalPosition, PhysicalSize}; use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::monitor::MonitorHandle; use winit::monitor::MonitorHandle;
use winit::{event_loop::EventLoop, window::Window}; use winit::{event_loop::EventLoop, window::WindowBuilder};
fn main() { fn main() {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
if let Some(mon) = window.primary_monitor() { if let Some(mon) = window.primary_monitor() {
print_info("Primary output", mon); print_info("Primary output", mon);

View File

@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("Mouse Wheel events") .with_title("Mouse Wheel events")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();

View File

@@ -1,6 +1,6 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
fn main() -> Result<(), impl std::error::Error> { fn main() -> Result<(), impl std::error::Error> {
use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
@@ -10,7 +10,7 @@ fn main() -> Result<(), impl std::error::Error> {
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
keyboard::{Key, ModifiersState, NamedKey}, keyboard::{Key, ModifiersState, NamedKey},
window::{CursorGrabMode, CursorIcon, Fullscreen, Window, WindowLevel}, window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel},
}; };
const WINDOW_COUNT: usize = 3; const WINDOW_COUNT: usize = 3;
@@ -20,7 +20,7 @@ fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT); let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
for _ in 0..WINDOW_COUNT { for _ in 0..WINDOW_COUNT {
let window = Window::builder() let window = WindowBuilder::new()
.with_inner_size(WINDOW_SIZE) .with_inner_size(WINDOW_SIZE)
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
@@ -84,7 +84,7 @@ fn main() -> Result<(), impl std::error::Error> {
"1" => window.set_window_level(WindowLevel::AlwaysOnTop), "1" => window.set_window_level(WindowLevel::AlwaysOnTop),
"2" => window.set_window_level(WindowLevel::AlwaysOnBottom), "2" => window.set_window_level(WindowLevel::AlwaysOnBottom),
"3" => window.set_window_level(WindowLevel::Normal), "3" => window.set_window_level(WindowLevel::Normal),
"c" => window.set_cursor(match state { "c" => window.set_cursor_icon(match state {
true => CursorIcon::Progress, true => CursorIcon::Progress,
false => CursorIcon::Default, false => CursorIcon::Default,
}), }),
@@ -96,13 +96,21 @@ fn main() -> Result<(), impl std::error::Error> {
)), )),
(false, _) => None, (false, _) => None,
}), }),
ch @ ("g" | "l") => { "l" if state => {
let mode = match (ch, state) { if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked)
("l", true) => CursorGrabMode::Locked, {
("g", true) => CursorGrabMode::Confined, println!("error: {err}");
(_, _) => CursorGrabMode::None, }
}; }
if let Err(err) = window.set_cursor_grab(mode) { "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) {
println!("error: {err}"); println!("error: {err}");
} }
} }
@@ -115,6 +123,10 @@ fn main() -> Result<(), impl std::error::Error> {
println!("-> inner_size : {:?}", window.inner_size()); println!("-> inner_size : {:?}", window.inner_size());
println!("-> fullscreen : {:?}", window.fullscreen()); println!("-> fullscreen : {:?}", window.fullscreen());
} }
"l" => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE),
false => None,
}),
"m" => window.set_maximized(state), "m" => window.set_maximized(state),
"p" => window.set_outer_position({ "p" => window.set_outer_position({
let mut position = window.outer_position().unwrap(); let mut position = window.outer_position().unwrap();
@@ -128,26 +140,12 @@ fn main() -> Result<(), impl std::error::Error> {
"s" => { "s" => {
let _ = window.request_inner_size(match state { let _ = window.request_inner_size(match state {
true => PhysicalSize::new( true => PhysicalSize::new(
WINDOW_SIZE.width + 50, WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 50, WINDOW_SIZE.height + 100,
), ),
false => WINDOW_SIZE, false => WINDOW_SIZE,
}); });
} }
"k" => window.set_min_inner_size(match state {
true => Some(PhysicalSize::new(
WINDOW_SIZE.width - 100,
WINDOW_SIZE.height - 100,
)),
false => None,
}),
"o" => window.set_max_inner_size(match state {
true => Some(PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
)),
false => None,
}),
"w" => { "w" => {
if let Size::Physical(size) = WINDOW_SIZE.into() { if let Size::Physical(size) = WINDOW_SIZE.into() {
window window
@@ -205,7 +203,7 @@ fn main() -> Result<(), impl std::error::Error> {
}) })
} }
#[cfg(web_platform)] #[cfg(wasm_platform)]
fn main() { fn main() {
panic!("Example not supported on Web"); panic!("Example not supported on Wasm");
} }

View File

@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{ElementState, Event, WindowEvent}, event::{ElementState, Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();

View File

@@ -1,6 +1,6 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
fn main() -> Result<(), impl std::error::Error> { fn main() -> Result<(), impl std::error::Error> {
use std::{sync::Arc, thread, time}; use std::{sync::Arc, thread, time};
@@ -8,7 +8,7 @@ fn main() -> Result<(), impl std::error::Error> {
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = { let window = {
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
@@ -53,7 +53,7 @@ fn main() -> Result<(), impl std::error::Error> {
}) })
} }
#[cfg(web_platform)] #[cfg(wasm_platform)]
fn main() { fn main() {
unimplemented!() // `Window` can't be sent between threads unimplemented!() // `Window` can't be sent between threads
} }

View File

@@ -6,7 +6,7 @@ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey}, keyboard::{KeyCode, PhysicalKey},
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
let mut resizable = false; let mut resizable = false;
let window = Window::builder() let window = WindowBuilder::new()
.with_title("Hit space to toggle resizability.") .with_title("Hit space to toggle resizability.")
.with_inner_size(LogicalSize::new(600.0, 300.0)) .with_inner_size(LogicalSize::new(600.0, 300.0))
.with_min_inner_size(LogicalSize::new(400.0, 200.0)) .with_min_inner_size(LogicalSize::new(400.0, 200.0))

View File

@@ -14,7 +14,7 @@ mod example {
use winit::platform::startup_notify::{ use winit::platform::startup_notify::{
EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify, EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify,
}; };
use winit::window::{Window, WindowId}; use winit::window::{Window, WindowBuilder, WindowId};
pub(super) fn main() -> Result<(), impl std::error::Error> { pub(super) fn main() -> Result<(), impl std::error::Error> {
// Create the event loop and get the activation token. // Create the event loop and get the activation token.
@@ -84,7 +84,8 @@ mod example {
if current_token.is_some() || create_first_window { if current_token.is_some() || create_first_window {
// Create the initial window. // Create the initial window.
let window = { let window = {
let mut builder = Window::builder().with_title(format!("Window {}", counter)); let mut builder =
WindowBuilder::new().with_title(format!("Window {}", counter));
if let Some(token) = current_token.take() { if let Some(token) = current_token.take() {
println!("Creating a window with token {token:?}"); println!("Creating a window with token {token:?}");

View File

@@ -5,7 +5,7 @@ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
keyboard::Key, keyboard::Key,
window::{Theme, Window}, window::{Theme, WindowBuilder},
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -15,7 +15,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.with_theme(Some(Theme::Dark)) .with_theme(Some(Theme::Dark))
.build(&event_loop) .build(&event_loop)

View File

@@ -1,16 +1,16 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
use std::time::Duration; use std::time::Duration;
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
use std::time::Instant; use std::time::Instant;
#[cfg(web_platform)] #[cfg(wasm_platform)]
use web_time::Instant; use web_time::Instant;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, StartCause, WindowEvent}, event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -20,7 +20,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();

View File

@@ -2,7 +2,7 @@ use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -12,44 +12,32 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("Touchpad gestures") .with_title("Touchpad gestures")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
#[cfg(target_os = "ios")]
{
use winit::platform::ios::WindowExtIOS;
window.recognize_doubletap_gesture(true);
window.recognize_pinch_gesture(true);
window.recognize_rotation_gesture(true);
}
println!("Only supported on macOS/iOS at the moment."); println!("Only supported on macOS at the moment.");
let mut zoom = 0.0;
let mut rotated = 0.0;
event_loop.run(move |event, elwt| { event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event { if let Event::WindowEvent { event, .. } = event {
match event { match event {
WindowEvent::CloseRequested => elwt.exit(), WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::PinchGesture { delta, .. } => { WindowEvent::TouchpadMagnify { delta, .. } => {
zoom += delta;
if delta > 0.0 { if delta > 0.0 {
println!("Zoomed in {delta:.5} (now: {zoom:.5})"); println!("Zoomed in {delta}");
} else { } else {
println!("Zoomed out {delta:.5} (now: {zoom:.5})"); println!("Zoomed out {delta}");
} }
} }
WindowEvent::DoubleTapGesture { .. } => { WindowEvent::SmartMagnify { .. } => {
println!("Smart zoom"); println!("Smart zoom");
} }
WindowEvent::RotationGesture { delta, .. } => { WindowEvent::TouchpadRotate { delta, .. } => {
rotated += delta;
if delta > 0.0 { if delta > 0.0 {
println!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})"); println!("Rotated counterclockwise {delta}");
} else { } else {
println!("Rotated clockwise {delta:.5} (now: {rotated:.5})"); println!("Rotated clockwise {delta}");
} }
} }
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {

View File

@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_decorations(false) .with_decorations(false)
.with_transparent(true) .with_transparent(true)
.build(&event_loop) .build(&event_loop)

View File

@@ -7,30 +7,17 @@
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could //! 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. //! also be used to fill the window buffer, but they are more complicated to use.
#[allow(unused_imports)] use winit::window::Window;
pub use platform::cleanup_window;
pub use platform::fill_window;
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))] #[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
mod platform { pub(super) fn fill_window(window: &Window) {
use softbuffer::{Context, Surface};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::mem::ManuallyDrop; use std::mem::ManuallyDrop;
use std::num::NonZeroU32; use std::num::NonZeroU32;
use softbuffer::{Context, Surface};
use winit::window::Window;
use winit::window::WindowId; 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. /// The graphics context used to draw to a window.
struct GraphicsContext { struct GraphicsContext {
/// The global softbuffer context. /// The global softbuffer context.
@@ -48,69 +35,55 @@ mod platform {
} }
} }
fn create_surface(&mut self, window: &Window) -> &mut Surface { fn surface(&mut self, w: &Window) -> &mut Surface {
self.surfaces.entry(window.id()).or_insert_with(|| { self.surfaces.entry(w.id()).or_insert_with(|| {
unsafe { Surface::new(&self.context, window) } unsafe { Surface::new(&self.context, w) }
.expect("Failed to create a softbuffer surface") .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) { thread_local! {
GC.with(|gc| { // NOTE: You should never do things like that, create context and drop it before
let size = window.inner_size(); // you drop the event loop. We do this for brevity to not blow up examples. We use
let (Some(width), Some(height)) = // ManuallyDrop to prevent destructors from running.
(NonZeroU32::new(size.width), NonZeroU32::new(size.height)) //
else { // A static, thread-local map of graphics contexts to open windows.
return; static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
};
// 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)] GC.with(|gc| {
pub fn cleanup_window(window: &Window) { let size = window.inner_size();
GC.with(|gc| { let (Some(width), Some(height)) =
let mut gc = gc.borrow_mut(); (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
if let Some(context) = gc.as_mut() { else {
context.destroy_surface(window); 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")))))] #[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
mod platform { pub(super) fn fill_window(_window: &Window) {
pub fn fill_window(_window: &winit::window::Window) { // No-op on mobile platforms.
// No-op on mobile platforms.
}
#[allow(dead_code)]
pub fn cleanup_window(_window: &winit::window::Window) {
// No-op on mobile platforms.
}
} }

View File

@@ -4,13 +4,13 @@ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
keyboard::Key, keyboard::Key,
window::{Fullscreen, Window}, window::{Fullscreen, WindowBuilder},
}; };
pub fn main() -> Result<(), impl std::error::Error> { pub fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let builder = Window::builder().with_title("A fantastic window!"); let builder = WindowBuilder::new().with_title("A fantastic window!");
#[cfg(wasm_platform)] #[cfg(wasm_platform)]
let builder = { let builder = {
use winit::platform::web::WindowBuilderExtWebSys; use winit::platform::web::WindowBuilderExtWebSys;
@@ -18,11 +18,11 @@ pub fn main() -> Result<(), impl std::error::Error> {
}; };
let window = builder.build(&event_loop).unwrap(); let window = builder.build(&event_loop).unwrap();
#[cfg(web_platform)] #[cfg(wasm_platform)]
let log_list = wasm::insert_canvas_and_create_log_list(&window); let log_list = wasm::insert_canvas_and_create_log_list(&window);
event_loop.run(move |event, elwt| { event_loop.run(move |event, elwt| {
#[cfg(web_platform)] #[cfg(wasm_platform)]
wasm::log_event(&log_list, &event); wasm::log_event(&log_list, &event);
match event { match event {
@@ -57,7 +57,7 @@ pub fn main() -> Result<(), impl std::error::Error> {
}) })
} }
#[cfg(web_platform)] #[cfg(wasm_platform)]
mod wasm { mod wasm {
use std::num::NonZeroU32; use std::num::NonZeroU32;

View File

@@ -4,7 +4,7 @@ pub fn main() {
println!("This example must be run with cargo run-wasm --example web_aspect_ratio") println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
} }
#[cfg(web_platform)] #[cfg(wasm_platform)]
mod wasm { mod wasm {
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
@@ -14,7 +14,7 @@ mod wasm {
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
platform::web::WindowBuilderExtWebSys, platform::web::WindowBuilderExtWebSys,
window::Window, window::{Window, WindowBuilder},
}; };
const EXPLANATION: &str = " const EXPLANATION: &str = "
@@ -33,7 +33,7 @@ This example demonstrates the desired future functionality which will possibly b
console_log::init_with_level(log::Level::Debug).expect("error initializing logger"); console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
// When running in a non-wasm environment this would set the window size to 100x100. // When running in a non-wasm environment this would set the window size to 100x100.
// However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page. // However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page.

View File

@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -14,7 +14,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop) .build(&event_loop)

View File

@@ -8,7 +8,7 @@ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{DeviceEvents, EventLoop}, event_loop::{DeviceEvents, EventLoop},
keyboard::Key, keyboard::Key,
window::{Window, WindowButtons}, window::{WindowBuilder, WindowButtons},
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(300.0, 300.0)) .with_inner_size(LogicalSize::new(300.0, 300.0))
.build(&event_loop) .build(&event_loop)

View File

@@ -8,7 +8,7 @@ use winit::{
event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent}, event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent},
event_loop::{DeviceEvents, EventLoop}, event_loop::{DeviceEvents, EventLoop},
keyboard::{Key, KeyCode, PhysicalKey}, keyboard::{Key, KeyCode, PhysicalKey},
window::{Fullscreen, Window}, window::{Fullscreen, WindowBuilder},
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -18,7 +18,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(100.0, 100.0)) .with_inner_size(LogicalSize::new(100.0, 100.0))
.build(&event_loop) .build(&event_loop)

View File

@@ -5,7 +5,7 @@ use winit::{
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent}, event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
keyboard::Key, keyboard::Key,
window::{CursorIcon, ResizeDirection, Window}, window::{CursorIcon, ResizeDirection, WindowBuilder},
}; };
const BORDER: f64 = 8.0; const BORDER: f64 = 8.0;
@@ -17,7 +17,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0)) .with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0))
.with_min_inner_size(winit::dpi::LogicalSize::new(400.0, 200.0)) .with_min_inner_size(winit::dpi::LogicalSize::new(400.0, 200.0))
.with_decorations(false) .with_decorations(false)
@@ -40,7 +40,7 @@ fn main() -> Result<(), impl std::error::Error> {
if new_location != cursor_location { if new_location != cursor_location {
cursor_location = new_location; cursor_location = new_location;
window.set_cursor(cursor_direction_icon(cursor_location)) window.set_cursor_icon(cursor_direction_icon(cursor_location))
} }
} }
} }

View File

@@ -6,7 +6,7 @@ use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::{Icon, Window}, window::{Icon, WindowBuilder},
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -25,7 +25,7 @@ fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("An iconic window!") .with_title("An iconic window!")
// At present, this only does anything on Windows and X11, so if you want to save load // 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. // time, you can put icon loading behind a function that returns `None` on other platforms.

View File

@@ -12,7 +12,7 @@ fn main() -> Result<(), impl std::error::Error> {
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
platform::run_on_demand::EventLoopExtRunOnDemand, platform::run_on_demand::EventLoopExtRunOnDemand,
window::{Window, WindowId}, window::{Window, WindowBuilder, WindowId},
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -40,7 +40,6 @@ fn main() -> Result<(), impl std::error::Error> {
window_id, window_id,
} if window.id() == window_id => { } if window.id() == window_id => {
println!("--------------------------------------------------------- Window {idx} CloseRequested"); println!("--------------------------------------------------------- Window {idx} CloseRequested");
fill::cleanup_window(window);
app.window = None; app.window = None;
} }
Event::AboutToWait => window.request_redraw(), Event::AboutToWait => window.request_redraw(),
@@ -65,7 +64,7 @@ fn main() -> Result<(), impl std::error::Error> {
_ => (), _ => (),
} }
} else if let Event::Resumed = event { } else if let Event::Resumed = event {
let window = Window::builder() let window = WindowBuilder::new()
.with_title("Fantastic window number one!") .with_title("Fantastic window number one!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(elwt) .build(elwt)

View File

@@ -8,7 +8,7 @@ use winit::{
event::ElementState, event::ElementState,
event::{Event, MouseButton, WindowEvent}, event::{Event, MouseButton, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::Window, window::WindowBuilder,
}; };
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@@ -21,7 +21,7 @@ mod fill;
fn main() -> Result<(), impl std::error::Error> { fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop) .build(&event_loop)

View File

@@ -16,7 +16,7 @@ fn main() -> std::process::ExitCode {
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -25,7 +25,7 @@ fn main() -> std::process::ExitCode {
let mut event_loop = EventLoop::new().unwrap(); let mut event_loop = EventLoop::new().unwrap();
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
@@ -68,7 +68,7 @@ fn main() -> std::process::ExitCode {
} }
} }
#[cfg(any(ios_platform, web_platform, orbital_platform))] #[cfg(any(ios_platform, wasm_platform, orbital_platform))]
fn main() { fn main() {
println!("This platform doesn't support pump_events."); println!("This platform doesn't support pump_events.");
} }

View File

@@ -5,7 +5,7 @@ use winit::{
event::{ElementState, Event, WindowEvent}, event::{ElementState, Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
keyboard::NamedKey, keyboard::NamedKey,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -15,7 +15,7 @@ fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(128.0, 128.0)) .with_inner_size(LogicalSize::new(128.0, 128.0))
.with_resize_increments(LogicalSize::new(25.0, 25.0)) .with_resize_increments(LogicalSize::new(25.0, 25.0))

View File

@@ -11,7 +11,7 @@ use winit::{
event_loop::EventLoop, event_loop::EventLoop,
keyboard::{Key, NamedKey}, keyboard::{Key, NamedKey},
platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS}, platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS},
window::Window, window::{Window, WindowBuilder},
}; };
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@@ -60,7 +60,7 @@ fn main() -> Result<(), impl std::error::Error> {
} => match logical_key.as_ref() { } => match logical_key.as_ref() {
Key::Character("t") => { Key::Character("t") => {
let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier(); let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier();
let window = Window::builder() let window = WindowBuilder::new()
.with_tabbing_identifier(&tabbing_id) .with_tabbing_identifier(&tabbing_id)
.build(elwt) .build(elwt)
.unwrap(); .unwrap();

View File

@@ -12,7 +12,7 @@ mod imple {
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
platform::x11::WindowBuilderExtX11, platform::x11::WindowBuilderExtX11,
window::Window, window::WindowBuilder,
}; };
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> { pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
@@ -25,7 +25,7 @@ mod imple {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
let window = Window::builder() let window = WindowBuilder::new()
.with_title("An embedded window!") .with_title("An embedded window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(parent_window_id) .with_embed_parent_window(parent_window_id)

View File

@@ -1,125 +1,67 @@
use core::fmt; use core::fmt;
use std::hash::Hasher; use std::{error::Error, sync::Arc};
use std::sync::Arc;
use std::{error::Error, hash::Hash};
use cursor_icon::CursorIcon; use crate::platform_impl::PlatformCustomCursor;
use crate::event_loop::EventLoopWindowTarget;
use crate::platform_impl::{self, PlatformCustomCursor, PlatformCustomCursorBuilder};
/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`]. /// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048; pub const MAX_CURSOR_SIZE: u16 = 2048;
const PIXEL_SIZE: usize = 4; 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). /// Use a custom image as a cursor (mouse pointer).
/// ///
/// Is guaranteed to be cheap to clone.
///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// **Web**: Some browsers have limits on cursor sizes usually at 128x128. /// **Web**: Some browsers have limits on cursor sizes usually at 128x128.
/// ///
/// # Example /// # Example
/// ///
/// ```no_run /// ```
/// use winit::{ /// use winit::window::CustomCursor;
/// event::{Event, WindowEvent},
/// event_loop::{ControlFlow, EventLoop},
/// window::{CustomCursor, Window},
/// };
///
/// let mut event_loop = EventLoop::new().unwrap();
/// ///
/// let w = 10; /// let w = 10;
/// let h = 10; /// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize]; /// let rgba = vec![255; (w * h * 4) as usize];
/// /// let custom_cursor = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
/// #[cfg(not(target_family = "wasm"))]
/// let builder = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
/// ///
/// #[cfg(target_family = "wasm")] /// #[cfg(target_family = "wasm")]
/// let builder = { /// let custom_cursor_url = {
/// use winit::platform::web::CustomCursorExtWebSys; /// use winit::platform::web::CustomCursorExtWebSys;
/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0) /// CustomCursor::from_url("http://localhost:3000/cursor.png", 0, 0).unwrap()
/// }; /// };
///
/// let custom_cursor = builder.build(&event_loop);
///
/// let window = Window::new(&event_loop).unwrap();
/// window.set_cursor(custom_cursor.clone());
/// ``` /// ```
#[derive(Clone, Debug, Eq, Hash, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct CustomCursor { pub struct CustomCursor {
/// Platforms should make sure this is cheap to clone. pub(crate) inner: Arc<PlatformCustomCursor>,
pub(crate) inner: PlatformCustomCursor,
} }
impl CustomCursor { impl CustomCursor {
/// Creates a new cursor from an rgba buffer. /// Creates a new cursor from an rgba buffer.
/// ///
/// The alpha channel is assumed to be **not** premultiplied. /// ## Platform-specific
///
/// - **Web:** Setting cursor could be delayed due to the creation of `Blob` objects,
/// which are async by nature.
pub fn from_rgba( pub fn from_rgba(
rgba: impl Into<Vec<u8>>, rgba: impl Into<Vec<u8>>,
width: u16, width: u16,
height: u16, height: u16,
hotspot_x: u16, hotspot_x: u16,
hotspot_y: u16, hotspot_y: u16,
) -> Result<CustomCursorBuilder, BadImage> { ) -> Result<Self, BadImage> {
Ok(CustomCursorBuilder { Ok(Self {
inner: PlatformCustomCursorBuilder::from_rgba( inner: PlatformCustomCursor::from_rgba(
rgba.into(), rgba.into(),
width, width,
height, height,
hotspot_x, hotspot_x,
hotspot_y, hotspot_y,
)?, )?
.into(),
}) })
} }
} }
/// Builds a [`CustomCursor`].
///
/// See [`CustomCursor`] for more details.
#[derive(Debug)]
pub struct CustomCursorBuilder {
pub(crate) inner: PlatformCustomCursorBuilder,
}
impl CustomCursorBuilder {
pub fn build(self, window_target: &EventLoopWindowTarget) -> CustomCursor {
CustomCursor {
inner: PlatformCustomCursor::build(self.inner, &window_target.p),
}
}
}
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments. /// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum BadImage { pub enum BadImage {
@@ -178,54 +120,9 @@ impl fmt::Display for BadImage {
impl Error for BadImage {} impl Error for BadImage {}
/// Platforms export this directly as `PlatformCustomCursorBuilder` if they need to only work with images. /// Platforms export this directly as `PlatformCustomCursor` if they need to only work with images.
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct OnlyCursorImageBuilder(pub(crate) CursorImage); pub struct CursorImage {
#[allow(dead_code)]
impl OnlyCursorImageBuilder {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
}
}
/// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching.
#[derive(Debug, Clone)]
pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
impl Hash for OnlyCursorImage {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}
impl PartialEq for OnlyCursorImage {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for OnlyCursorImage {}
#[allow(dead_code)]
impl OnlyCursorImage {
pub(crate) fn build(
builder: OnlyCursorImageBuilder,
_: &platform_impl::EventLoopWindowTarget,
) -> Self {
Self(Arc::new(builder.0))
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub(crate) struct CursorImage {
pub(crate) rgba: Vec<u8>, pub(crate) rgba: Vec<u8>,
pub(crate) width: u16, pub(crate) width: u16,
pub(crate) height: u16, pub(crate) height: u16,
@@ -233,8 +130,9 @@ pub(crate) struct CursorImage {
pub(crate) hotspot_y: u16, pub(crate) hotspot_y: u16,
} }
#[allow(dead_code)]
impl CursorImage { impl CursorImage {
pub(crate) fn from_rgba( pub fn from_rgba(
rgba: Vec<u8>, rgba: Vec<u8>,
width: u16, width: u16,
height: u16, height: u16,
@@ -282,12 +180,12 @@ impl CursorImage {
} }
// Platforms that don't support cursors will export this as `PlatformCustomCursor`. // Platforms that don't support cursors will export this as `PlatformCustomCursor`.
#[derive(Debug, Clone, Hash, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct NoCustomCursor; pub(crate) struct NoCustomCursor;
#[allow(dead_code)] #[allow(dead_code)]
impl NoCustomCursor { impl NoCustomCursor {
pub(crate) fn from_rgba( pub fn from_rgba(
rgba: Vec<u8>, rgba: Vec<u8>,
width: u16, width: u16,
height: u16, height: u16,
@@ -297,8 +195,4 @@ impl NoCustomCursor {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?; CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
Ok(Self) Ok(Self)
} }
fn build(self, _: &platform_impl::EventLoopWindowTarget) -> NoCustomCursor {
self
}
} }

View File

@@ -104,9 +104,6 @@
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities //! [android_1]: https://developer.android.com/training/multiscreen/screendensities
//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio //! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub trait Pixel: Copy + Into<f64> { pub trait Pixel: Copy + Into<f64> {
fn from_f64(f: f64) -> Self; fn from_f64(f: f64) -> Self;
fn cast<P: Pixel>(self) -> P { fn cast<P: Pixel>(self) -> P {

View File

@@ -35,6 +35,8 @@ pub enum EventLoopError {
NotSupported(NotSupportedError), NotSupported(NotSupportedError),
/// The OS cannot perform the operation. /// The OS cannot perform the operation.
Os(OsError), Os(OsError),
/// The event loop can't be re-run while it's already running
AlreadyRunning,
/// The event loop can't be re-created. /// The event loop can't be re-created.
RecreationAttempt, RecreationAttempt,
/// Application has exit with an error status. /// Application has exit with an error status.
@@ -103,6 +105,7 @@ impl fmt::Display for NotSupportedError {
impl fmt::Display for EventLoopError { impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self { match self {
EventLoopError::AlreadyRunning => write!(f, "EventLoop is already running"),
EventLoopError::RecreationAttempt => write!(f, "EventLoop can't be recreated"), EventLoopError::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
EventLoopError::NotSupported(e) => e.fmt(f), EventLoopError::NotSupported(e) => e.fmt(f),
EventLoopError::Os(e) => e.fmt(f), EventLoopError::Os(e) => e.fmt(f),

View File

@@ -34,13 +34,11 @@
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil //! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Mutex, Weak}; use std::sync::{Mutex, Weak};
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
use std::time::Instant; use std::time::Instant;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use smol_str::SmolStr; use smol_str::SmolStr;
#[cfg(web_platform)] #[cfg(wasm_platform)]
use web_time::Instant; use web_time::Instant;
use crate::error::ExternalError; use crate::error::ExternalError;
@@ -447,23 +445,21 @@ pub enum WindowEvent {
button: MouseButton, button: MouseButton,
}, },
/// Two-finger pinch gesture, often used for magnification. /// Touchpad magnification event with two-finger pinch gesture.
///
/// Positive delta values indicate magnification (zooming in) and
/// negative delta values indicate shrinking (zooming out).
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - Only available on **macOS** and **iOS**. /// - Only available on **macOS**.
/// - On iOS, not recognized by default. It must be enabled when needed. TouchpadMagnify {
PinchGesture {
device_id: DeviceId, device_id: DeviceId,
/// Positive values indicate magnification (zooming in) and negative
/// values indicate shrinking (zooming out).
///
/// This value may be NaN.
delta: f64, delta: f64,
phase: TouchPhase, phase: TouchPhase,
}, },
/// Double tap gesture. /// Smart magnification event.
/// ///
/// On a Mac, smart magnification is triggered by a double tap with two fingers /// On a Mac, smart magnification is triggered by a double tap with two fingers
/// on the trackpad and is commonly used to zoom on a certain object /// on the trackpad and is commonly used to zoom on a certain object
@@ -479,20 +475,18 @@ pub enum WindowEvent {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - Only available on **macOS 10.8** and later, and **iOS**. /// - Only available on **macOS 10.8** and later.
/// - On iOS, not recognized by default. It must be enabled when needed. SmartMagnify { device_id: DeviceId },
DoubleTapGesture { device_id: DeviceId },
/// Two-finger rotation gesture. /// Touchpad rotation event with two-finger rotation gesture.
/// ///
/// Positive delta values indicate rotation counterclockwise and /// Positive delta values indicate rotation counterclockwise and
/// negative delta values indicate rotation clockwise. /// negative delta values indicate rotation clockwise.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - Only available on **macOS** and **iOS**. /// - Only available on **macOS**.
/// - On iOS, not recognized by default. It must be enabled when needed. TouchpadRotate {
RotationGesture {
device_id: DeviceId, device_id: DeviceId,
delta: f32, delta: f32,
phase: TouchPhase, phase: TouchPhase,
@@ -536,8 +530,9 @@ pub enum WindowEvent {
/// * Changing the display's scale factor (e.g. in Control Panel on Windows). /// * Changing the display's scale factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different scale factor. /// * Moving the window to a display with a different scale factor.
/// ///
/// To update the window size, use the provided [`InnerSizeWriter`] handle. By default, the window is /// After this event callback has been processed, the window will be resized to whatever value
/// resized to the value suggested by the OS, but it can be changed to any value. /// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested
/// by the OS, but it can be changed to any value.
/// ///
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module. /// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
ScaleFactorChanged { ScaleFactorChanged {
@@ -579,7 +574,7 @@ pub enum WindowEvent {
/// ### Others /// ### Others
/// ///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **Android / Wayland / Windows / Orbital:** Unsupported. /// - **Android / Windows / Orbital:** Unsupported.
/// ///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
@@ -1204,13 +1199,13 @@ mod tests {
state: event::ElementState::Pressed, state: event::ElementState::Pressed,
button: event::MouseButton::Other(0), button: event::MouseButton::Other(0),
}); });
with_window_event(PinchGesture { with_window_event(TouchpadMagnify {
device_id: did, device_id: did,
delta: 0.0, delta: 0.0,
phase: event::TouchPhase::Started, phase: event::TouchPhase::Started,
}); });
with_window_event(DoubleTapGesture { device_id: did }); with_window_event(SmartMagnify { device_id: did });
with_window_event(RotationGesture { with_window_event(TouchpadRotate {
device_id: did, device_id: did,
delta: 0.0, delta: 0.0,
phase: event::TouchPhase::Started, phase: event::TouchPhase::Started,

View File

@@ -11,12 +11,12 @@ use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::{error, fmt}; use std::{error, fmt};
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
#[cfg(web_platform)] #[cfg(wasm_platform)]
use web_time::{Duration, Instant}; use web_time::{Duration, Instant};
use crate::error::EventLoopError; use crate::error::EventLoopError;
@@ -48,8 +48,8 @@ pub struct EventLoop<T: 'static> {
/// your callback. [`EventLoop`] will coerce into this type (`impl<T> Deref for /// 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<T>`), so functions that take this as a parameter can also take
/// `&EventLoop`. /// `&EventLoop`.
pub struct EventLoopWindowTarget { pub struct EventLoopWindowTarget<T: 'static> {
pub(crate) p: platform_impl::EventLoopWindowTarget, pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
} }
@@ -57,26 +57,33 @@ pub struct EventLoopWindowTarget {
/// ///
/// This is used to make specifying options that affect the whole application /// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported. /// 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)] #[derive(Default)]
pub struct EventLoopBuilder<T: 'static> { pub struct EventLoopBuilder<T: 'static> {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes, pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
_p: PhantomData<T>, _p: PhantomData<T>,
} }
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl EventLoopBuilder<()> { impl EventLoopBuilder<()> {
/// Start building a new event loop. /// Start building a new event loop.
#[inline] #[inline]
#[deprecated = "use `EventLoop::builder` instead"]
pub fn new() -> Self { pub fn new() -> Self {
EventLoop::builder() Self::with_user_event()
} }
} }
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl<T> EventLoopBuilder<T> { 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. /// Builds a new event loop.
/// ///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread, /// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
@@ -123,7 +130,7 @@ impl<T> EventLoopBuilder<T> {
}) })
} }
#[cfg(web_platform)] #[cfg(wasm_platform)]
pub(crate) fn allow_event_loop_recreation() { pub(crate) fn allow_event_loop_recreation() {
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed); EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
} }
@@ -135,7 +142,7 @@ impl<T> fmt::Debug for EventLoop<T> {
} }
} }
impl fmt::Debug for EventLoopWindowTarget { impl<T> fmt::Debug for EventLoopWindowTarget<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoopWindowTarget { .. }") f.pad("EventLoopWindowTarget { .. }")
} }
@@ -186,38 +193,27 @@ impl ControlFlow {
} }
impl EventLoop<()> { impl EventLoop<()> {
/// Create the event loop. /// Alias for [`EventLoopBuilder::new().build()`].
/// ///
/// This is an alias of `EventLoop::builder().build()`. /// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
#[inline] #[inline]
pub fn new() -> Result<EventLoop<()>, EventLoopError> { pub fn new() -> Result<EventLoop<()>, EventLoopError> {
Self::builder().build() EventLoopBuilder::new().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> { impl<T> EventLoop<T> {
/// Start building a new event loop, with the given type as the user event #[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
/// type. pub fn with_user_event() -> Result<EventLoop<T>, EventLoopError> {
pub fn with_user_event() -> EventLoopBuilder<T> { EventLoopBuilder::<T>::with_user_event().build()
EventLoopBuilder {
platform_specific: Default::default(),
_p: PhantomData,
}
} }
/// Runs the event loop in the calling thread and calls the given `event_handler` closure /// Runs the event loop in the calling thread and calls the given `event_handler` closure
/// to dispatch any pending events. /// 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. /// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
/// ///
/// ## Platform-specific /// ## Platform-specific
@@ -230,10 +226,10 @@ impl<T> EventLoop<T> {
/// ///
/// Web applications are recommended to use /// Web applications are recommended to use
#[cfg_attr( #[cfg_attr(
web_platform, wasm_platform,
doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]" doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]"
)] )]
#[cfg_attr(not(web_platform), doc = "`EventLoopExtWebSys::spawn()`")] #[cfg_attr(not(wasm_platform), doc = "`EventLoopExtWebSys::spawn()`")]
/// [^1] instead of [`run()`] to avoid the need /// [^1] instead of [`run()`] to avoid the need
/// for the Javascript exception trick, and to make it clearer that the event loop runs /// 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 /// asynchronously (via the browser's own, internal, event loop) and doesn't block the
@@ -243,12 +239,12 @@ impl<T> EventLoop<T> {
/// ///
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow() /// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
/// [`run()`]: Self::run() /// [`run()`]: Self::run()
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on Web. /// [^1]: `EventLoopExtWebSys::spawn()` is only available on WASM.
#[inline] #[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))] #[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError> pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(Event<T>, &EventLoopWindowTarget), F: FnMut(Event<T>, &EventLoopWindowTarget<T>),
{ {
self.event_loop.run(event_handler) self.event_loop.run(event_handler)
} }
@@ -305,13 +301,13 @@ impl<T> AsRawFd for EventLoop<T> {
} }
impl<T> Deref for EventLoop<T> { impl<T> Deref for EventLoop<T> {
type Target = EventLoopWindowTarget; type Target = EventLoopWindowTarget<T>;
fn deref(&self) -> &EventLoopWindowTarget { fn deref(&self) -> &EventLoopWindowTarget<T> {
self.event_loop.window_target() self.event_loop.window_target()
} }
} }
impl EventLoopWindowTarget { impl<T> EventLoopWindowTarget<T> {
/// Returns the list of all the monitors available on the system. /// Returns the list of all the monitors available on the system.
#[inline] #[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> { pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
@@ -374,19 +370,10 @@ impl EventLoopWindowTarget {
pub fn exiting(&self) -> bool { pub fn exiting(&self) -> bool {
self.p.exiting() 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")] #[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for EventLoopWindowTarget { impl<T> rwh_06::HasDisplayHandle for EventLoopWindowTarget<T> {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> { fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.p.raw_display_handle_rwh_06()?; let raw = self.p.raw_display_handle_rwh_06()?;
// SAFETY: The display will never be deallocated while the event loop is alive. // SAFETY: The display will never be deallocated while the event loop is alive.
@@ -395,59 +382,13 @@ impl rwh_06::HasDisplayHandle for EventLoopWindowTarget {
} }
#[cfg(feature = "rwh_05")] #[cfg(feature = "rwh_05")]
unsafe impl rwh_05::HasRawDisplayHandle for EventLoopWindowTarget { unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoopWindowTarget<T> {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop. /// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
self.p.raw_display_handle_rwh_05() 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`]. /// Used to send custom events to [`EventLoop`].
pub struct EventLoopProxy<T: 'static> { pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>, event_loop_proxy: platform_impl::EventLoopProxy<T>,
@@ -518,16 +459,16 @@ pub enum DeviceEvents {
/// executed and removed from the list. /// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AsyncRequestSerial { pub struct AsyncRequestSerial {
serial: usize, serial: u64,
} }
impl AsyncRequestSerial { impl AsyncRequestSerial {
// TODO(kchibisov): Remove `cfg` when the clipboard will be added. // TODO(kchibisov) remove `cfg` when the clipboard will be added.
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn get() -> Self { pub(crate) fn get() -> Self {
static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0); static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0);
// NOTE: We rely on wrap around here, while the user may just request // NOTE: we rely on wrap around here, while the user may just request
// in the loop usize::MAX times that's issue is considered on them. // in the loop u64::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed); let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial } Self { serial }
} }

View File

@@ -69,9 +69,6 @@
// //
// --------- END OF W3C SHORT NOTICE --------------------------------------------------------------- // --------- END OF W3C SHORT NOTICE ---------------------------------------------------------------
use bitflags::bitflags;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub use smol_str::SmolStr; pub use smol_str::SmolStr;
/// Contains the platform-native physical key identifier /// Contains the platform-native physical key identifier

View File

@@ -13,7 +13,7 @@
//! Once this is done, there are two ways to create a [`Window`]: //! Once this is done, there are two ways to create a [`Window`]:
//! //!
//! - Calling [`Window::new(&event_loop)`][window_new]. //! - Calling [`Window::new(&event_loop)`][window_new].
//! - Calling [`let builder = Window::builder()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build]. //! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
//! //!
//! The first method is the simplest and will give you default values for everything. The second //! The first method is the simplest and will give you default values for everything. The second
//! method allows you to customize the way your [`Window`] will look and behave by modifying the //! method allows you to customize the way your [`Window`] will look and behave by modifying the
@@ -63,11 +63,11 @@
//! use winit::{ //! use winit::{
//! event::{Event, WindowEvent}, //! event::{Event, WindowEvent},
//! event_loop::{ControlFlow, EventLoop}, //! event_loop::{ControlFlow, EventLoop},
//! window::Window, //! window::WindowBuilder,
//! }; //! };
//! //!
//! let event_loop = EventLoop::new().unwrap(); //! let event_loop = EventLoop::new().unwrap();
//! let window = Window::builder().build(&event_loop).unwrap(); //! let window = WindowBuilder::new().build(&event_loop).unwrap();
//! //!
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't //! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications. //! // dispatched any events. This is ideal for games and similar applications.
@@ -137,7 +137,7 @@
//! [`WindowId`]: window::WindowId //! [`WindowId`]: window::WindowId
//! [`WindowBuilder`]: window::WindowBuilder //! [`WindowBuilder`]: window::WindowBuilder
//! [window_new]: window::Window::new //! [window_new]: window::Window::new
//! [window_builder_new]: window::Window::builder //! [window_builder_new]: window::WindowBuilder::new
//! [window_builder_build]: window::WindowBuilder::build //! [window_builder_build]: window::WindowBuilder::build
//! [`Window::id()`]: window::Window::id //! [`Window::id()`]: window::Window::id
//! [`WindowEvent`]: event::WindowEvent //! [`WindowEvent`]: event::WindowEvent
@@ -154,16 +154,21 @@
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(feature = "cargo-clippy", deny(warnings))] #![cfg_attr(feature = "cargo-clippy", deny(warnings))]
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc // Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc
#![cfg_attr( #![cfg_attr(docsrs, feature(doc_auto_cfg))]
docsrs,
feature(doc_auto_cfg, doc_cfg_hide),
doc(cfg_hide(doc, docsrs))
)]
#![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_safety_doc)]
#[cfg(feature = "rwh_06")] #[cfg(feature = "rwh_06")]
pub use rwh_06 as raw_window_handle; 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;
pub mod dpi; pub mod dpi;
#[macro_use] #[macro_use]
pub mod error; pub mod error;
@@ -177,3 +182,18 @@ mod platform_impl;
pub mod window; pub mod window;
pub mod platform; 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> {}

View File

@@ -10,32 +10,28 @@ use crate::{
platform_impl, platform_impl,
}; };
/// Deprecated! Use `VideoModeHandle` instead.
#[deprecated = "Renamed to `VideoModeHandle`"]
pub type VideoMode = VideoModeHandle;
/// Describes a fullscreen video mode of a monitor. /// Describes a fullscreen video mode of a monitor.
/// ///
/// Can be acquired with [`MonitorHandle::video_modes`]. /// Can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle { pub struct VideoMode {
pub(crate) video_mode: platform_impl::VideoModeHandle, pub(crate) video_mode: platform_impl::VideoMode,
} }
impl std::fmt::Debug for VideoModeHandle { impl std::fmt::Debug for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.video_mode.fmt(f) self.video_mode.fmt(f)
} }
} }
impl PartialOrd for VideoModeHandle { impl PartialOrd for VideoMode {
fn partial_cmp(&self, other: &VideoModeHandle) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl Ord for VideoModeHandle { impl Ord for VideoMode {
fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering { fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
self.monitor().cmp(&other.monitor()).then( self.monitor().cmp(&other.monitor()).then(
self.size() self.size()
.cmp(&other.size()) .cmp(&other.size())
@@ -49,7 +45,7 @@ impl Ord for VideoModeHandle {
} }
} }
impl VideoModeHandle { impl VideoMode {
/// Returns the resolution of this video mode. /// Returns the resolution of this video mode.
#[inline] #[inline]
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
@@ -85,7 +81,7 @@ impl VideoModeHandle {
} }
} }
impl std::fmt::Display for VideoModeHandle { impl std::fmt::Display for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
@@ -135,8 +131,8 @@ impl MonitorHandle {
/// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor /// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor
/// the window is on is removed. /// the window is on is removed.
/// ///
/// When using exclusive fullscreen, the refresh rate of the [`VideoModeHandle`] that was /// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to
/// used to enter fullscreen should be used instead. /// enter fullscreen should be used instead.
#[inline] #[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> { pub fn refresh_rate_millihertz(&self) -> Option<u32> {
self.inner.refresh_rate_millihertz() self.inner.refresh_rate_millihertz()
@@ -165,9 +161,9 @@ impl MonitorHandle {
/// ///
/// - **Web:** Always returns an empty iterator /// - **Web:** Always returns an empty iterator
#[inline] #[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> { pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.inner self.inner
.video_modes() .video_modes()
.map(|video_mode| VideoModeHandle { video_mode }) .map(|video_mode| VideoMode { video_mode })
} }
} }

View File

@@ -3,7 +3,7 @@ use crate::{
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
use self::activity::{AndroidApp, ConfigurationRef, Rect}; use android_activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android. /// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {} pub trait EventLoopExtAndroid {}
@@ -30,7 +30,7 @@ impl WindowExtAndroid for Window {
} }
} }
impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {} impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
/// Additional methods on [`WindowBuilder`] that are specific to Android. /// Additional methods on [`WindowBuilder`] that are specific to Android.
pub trait WindowBuilderExtAndroid {} pub trait WindowBuilderExtAndroid {}
@@ -89,16 +89,5 @@ pub mod activity {
// feature enabled, so we avoid inlining it so that they're forced to view // feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page. // it on the crate's own docs.rs page.
#[doc(no_inline)] #[doc(no_inline)]
#[cfg(android_platform)]
pub use android_activity::*; 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;
} }

View File

@@ -1,8 +1,11 @@
use std::os::raw::c_void; use std::os::raw::c_void;
use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id;
use crate::{ use crate::{
event_loop::EventLoop, event_loop::EventLoop,
monitor::{MonitorHandle, VideoModeHandle}, monitor::{MonitorHandle, VideoMode},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@@ -85,21 +88,6 @@ pub trait WindowExtIOS {
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc) /// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
/// is also called for you. /// is also called for you.
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle); 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 { impl WindowExtIOS for Window {
@@ -139,24 +127,6 @@ impl WindowExtIOS for Window {
self.window self.window
.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style)) .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. /// Additional methods on [`WindowBuilder`] that are specific to iOS.
@@ -217,39 +187,38 @@ pub trait WindowBuilderExtIOS {
impl WindowBuilderExtIOS for WindowBuilder { impl WindowBuilderExtIOS for WindowBuilder {
#[inline] #[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> Self { fn with_scale_factor(mut self, scale_factor: f64) -> Self {
self.window.platform_specific.scale_factor = Some(scale_factor); self.platform_specific.scale_factor = Some(scale_factor);
self self
} }
#[inline] #[inline]
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self { fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self {
self.window.platform_specific.valid_orientations = valid_orientations; self.platform_specific.valid_orientations = valid_orientations;
self self
} }
#[inline] #[inline]
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self { fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self {
self.window.platform_specific.prefers_home_indicator_hidden = hidden; self.platform_specific.prefers_home_indicator_hidden = hidden;
self self
} }
#[inline] #[inline]
fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self { fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
self.window self.platform_specific
.platform_specific
.preferred_screen_edges_deferring_system_gestures = edges; .preferred_screen_edges_deferring_system_gestures = edges;
self self
} }
#[inline] #[inline]
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self { fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self {
self.window.platform_specific.prefers_status_bar_hidden = hidden; self.platform_specific.prefers_status_bar_hidden = hidden;
self self
} }
#[inline] #[inline]
fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self { fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
self.window.platform_specific.preferred_status_bar_style = status_bar_style; self.platform_specific.preferred_status_bar_style = status_bar_style;
self self
} }
} }
@@ -261,23 +230,23 @@ pub trait MonitorHandleExtIOS {
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc /// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
fn ui_screen(&self) -> *mut c_void; fn ui_screen(&self) -> *mut c_void;
/// Returns the preferred [`VideoModeHandle`] for this monitor. /// Returns the preferred [`VideoMode`] for this monitor.
/// ///
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc). /// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
fn preferred_video_mode(&self) -> VideoModeHandle; fn preferred_video_mode(&self) -> VideoMode;
} }
impl MonitorHandleExtIOS for MonitorHandle { impl MonitorHandleExtIOS for MonitorHandle {
#[inline] #[inline]
fn ui_screen(&self) -> *mut c_void { fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen // SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() }; let mtm = unsafe { MainThreadMarker::new_unchecked() };
objc2::rc::Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
} }
#[inline] #[inline]
fn preferred_video_mode(&self) -> VideoModeHandle { fn preferred_video_mode(&self) -> VideoMode {
VideoModeHandle { VideoMode {
video_mode: self.inner.preferred_video_mode(), video_mode: self.inner.preferred_video_mode(),
} }
} }
@@ -314,7 +283,7 @@ pub enum Idiom {
CarPlay, CarPlay,
} }
bitflags::bitflags! { bitflags! {
/// The [edges] of a screen. /// The [edges] of a screen.
/// ///
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc /// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc

View File

@@ -1,7 +1,6 @@
use std::os::raw::c_void; use std::os::raw::c_void;
#[cfg(feature = "serde")] use objc2::rc::Id;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget}, event_loop::{EventLoopBuilder, EventLoopWindowTarget},
@@ -212,62 +211,61 @@ pub trait WindowBuilderExtMacOS {
impl WindowBuilderExtMacOS for WindowBuilder { impl WindowBuilderExtMacOS for WindowBuilder {
#[inline] #[inline]
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self { fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.window.platform_specific.movable_by_window_background = movable_by_window_background; self.platform_specific.movable_by_window_background = movable_by_window_background;
self self
} }
#[inline] #[inline]
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self { fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
self.window.platform_specific.titlebar_transparent = titlebar_transparent; self.platform_specific.titlebar_transparent = titlebar_transparent;
self self
} }
#[inline] #[inline]
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self { fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
self.window.platform_specific.titlebar_hidden = titlebar_hidden; self.platform_specific.titlebar_hidden = titlebar_hidden;
self self
} }
#[inline] #[inline]
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self { fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
self.window.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden; self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
self self
} }
#[inline] #[inline]
fn with_title_hidden(mut self, title_hidden: bool) -> Self { fn with_title_hidden(mut self, title_hidden: bool) -> Self {
self.window.platform_specific.title_hidden = title_hidden; self.platform_specific.title_hidden = title_hidden;
self self
} }
#[inline] #[inline]
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self { fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
self.window.platform_specific.fullsize_content_view = fullsize_content_view; self.platform_specific.fullsize_content_view = fullsize_content_view;
self self
} }
#[inline] #[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self { fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
self.window.platform_specific.disallow_hidpi = disallow_hidpi; self.platform_specific.disallow_hidpi = disallow_hidpi;
self self
} }
#[inline] #[inline]
fn with_has_shadow(mut self, has_shadow: bool) -> Self { fn with_has_shadow(mut self, has_shadow: bool) -> Self {
self.window.platform_specific.has_shadow = has_shadow; self.platform_specific.has_shadow = has_shadow;
self self
} }
#[inline] #[inline]
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self { fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
self.window.platform_specific.accepts_first_mouse = accepts_first_mouse; self.platform_specific.accepts_first_mouse = accepts_first_mouse;
self self
} }
#[inline] #[inline]
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self { fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.window self.platform_specific
.platform_specific
.tabbing_identifier .tabbing_identifier
.replace(tabbing_identifier.to_string()); .replace(tabbing_identifier.to_string());
self self
@@ -275,7 +273,7 @@ impl WindowBuilderExtMacOS for WindowBuilder {
#[inline] #[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self { fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
self.window.platform_specific.option_as_alt = option_as_alt; self.platform_specific.option_as_alt = option_as_alt;
self self
} }
} }
@@ -367,11 +365,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
} }
fn ns_screen(&self) -> Option<*mut c_void> { fn ns_screen(&self) -> Option<*mut c_void> {
// SAFETY: We only use the marker to get a pointer self.inner.ns_screen().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 _)
} }
} }
@@ -389,7 +383,7 @@ pub trait EventLoopWindowTargetExtMacOS {
fn allows_automatic_window_tabbing(&self) -> bool; fn allows_automatic_window_tabbing(&self) -> bool;
} }
impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
fn hide_application(&self) { fn hide_application(&self) {
self.p.hide_application() self.p.hide_application()
} }

View File

@@ -1,24 +1,38 @@
//! Contains traits with platform-specific methods in them. //! Contains traits with platform-specific methods in them.
//! //!
//! Only the modules corresponding to the platform you're compiling to will be available. //! 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(any(android_platform, docsrs))] #[cfg(android_platform)]
pub mod android; pub mod android;
#[cfg(any(ios_platform, docsrs))] #[cfg(ios_platform)]
pub mod ios; pub mod ios;
#[cfg(any(macos_platform, docsrs))] #[cfg(macos_platform)]
pub mod macos; pub mod macos;
#[cfg(any(orbital_platform, docsrs))] #[cfg(orbital_platform)]
pub mod orbital; pub mod orbital;
#[cfg(any(x11_platform, wayland_platform, docsrs))] #[cfg(any(x11_platform, wayland_platform))]
pub mod startup_notify; pub mod startup_notify;
#[cfg(any(wayland_platform, docsrs))] #[cfg(wayland_platform)]
pub mod wayland; pub mod wayland;
#[cfg(any(web_platform, docsrs))] #[cfg(wasm_platform)]
pub mod web; pub mod web;
#[cfg(any(windows_platform, docsrs))] #[cfg(windows_platform)]
pub mod windows; pub mod windows;
#[cfg(any(x11_platform, docsrs))] #[cfg(x11_platform)]
pub mod x11; pub mod x11;
#[cfg(any( #[cfg(any(
@@ -26,8 +40,7 @@ pub mod x11;
macos_platform, macos_platform,
android_platform, android_platform,
x11_platform, x11_platform,
wayland_platform, wayland_platform
docsrs,
))] ))]
pub mod run_on_demand; pub mod run_on_demand;
@@ -36,26 +49,9 @@ pub mod run_on_demand;
macos_platform, macos_platform,
android_platform, android_platform,
x11_platform, x11_platform,
wayland_platform, wayland_platform
docsrs,
))] ))]
pub mod pump_events; pub mod pump_events;
#[cfg(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
orbital_platform,
docsrs
))]
pub mod modifier_supplement; pub mod modifier_supplement;
#[cfg(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
docsrs
))]
pub mod scancode; pub mod scancode;

View File

@@ -1,4 +1,5 @@
use crate::event::KeyEvent; #![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
use crate::keyboard::Key; use crate::keyboard::Key;
/// Additional methods for the `KeyEvent` which cannot be implemented on all /// Additional methods for the `KeyEvent` which cannot be implemented on all
@@ -21,18 +22,3 @@ pub trait KeyEventExtModifierSupplement {
/// cannot be `Dead`. /// cannot be `Dead`.
fn key_without_modifiers(&self) -> Key; fn key_without_modifiers(&self) -> Key;
} }
impl KeyEventExtModifierSupplement for KeyEvent {
#[inline]
fn text_with_all_modifiers(&self) -> Option<&str> {
self.platform_specific
.text_with_all_modifiers
.as_ref()
.map(|s| s.as_str())
}
#[inline]
fn key_without_modifiers(&self) -> Key {
self.platform_specific.key_without_modifiers.clone()
}
}

View File

@@ -51,12 +51,12 @@ pub trait EventLoopExtPumpEvents {
/// # event::{Event, WindowEvent}, /// # event::{Event, WindowEvent},
/// # event_loop::EventLoop, /// # event_loop::EventLoop,
/// # platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, /// # platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
/// # window::Window, /// # window::WindowBuilder,
/// # }; /// # };
/// let mut event_loop = EventLoop::new().unwrap(); /// let mut event_loop = EventLoop::new().unwrap();
/// # /// #
/// # SimpleLogger::new().init().unwrap(); /// # SimpleLogger::new().init().unwrap();
/// let window = Window::builder() /// let window = WindowBuilder::new()
/// .with_title("A fantastic window!") /// .with_title("A fantastic window!")
/// .build(&event_loop) /// .build(&event_loop)
/// .unwrap(); /// .unwrap();
@@ -155,26 +155,26 @@ pub trait EventLoopExtPumpEvents {
/// - **Windows**: The implementation will use `PeekMessage` when checking for /// - **Windows**: The implementation will use `PeekMessage` when checking for
/// window messages to avoid blocking your external event loop. /// window messages to avoid blocking your external event loop.
/// ///
/// - **MacOS**: The implementation works in terms of stopping the global application /// - **MacOS**: The implementation works in terms of stopping the global `NSApp`
/// whenever the application `RunLoop` indicates that it is preparing to block /// whenever the application `RunLoop` indicates that it is preparing to block
/// and wait for new events. /// and wait for new events.
/// ///
/// This is very different to the polling APIs that are available on other /// This is very different to the polling APIs that are available on other
/// platforms (the lower level polling primitives on MacOS are private /// platforms (the lower level polling primitives on MacOS are private
/// implementation details for `NSApplication` which aren't accessible to /// implementation details for `NSApp` which aren't accessible to application
/// application developers) /// developers)
/// ///
/// It's likely this will be less efficient than polling on other OSs and /// It's likely this will be less efficient than polling on other OSs and
/// it also means the `NSApplication` is stopped while outside of the Winit /// it also means the `NSApp` is stopped while outside of the Winit
/// event loop - and that's observable (for example to crates like `rfd`) /// event loop - and that's observable (for example to crates like `rfd`)
/// because the `NSApplication` is global state. /// because the `NSApp` is global state.
/// ///
/// If you render outside of Winit you are likely to see window resizing artifacts /// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect` /// since MacOS expects applications to render synchronously during any `drawRect`
/// callback. /// callback.
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget); F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
} }
impl<T> EventLoopExtPumpEvents for EventLoop<T> { 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 fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget), F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{ {
self.event_loop.pump_events(timeout, event_handler) self.event_loop.pump_events(timeout, event_handler)
} }

View File

@@ -55,10 +55,10 @@ pub trait EventLoopExtRunOnDemand {
/// loop that would block the browser and there is nothing that can be /// 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 /// polled to ask for new events. Events are delivered via callbacks based
/// on an event loop that is internal to the browser itself. /// on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS. /// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS.
/// ///
#[cfg_attr( #[cfg_attr(
not(web_platform), not(wasm_platform),
doc = "[^1]: `spawn()` is only available on `wasm` platforms." doc = "[^1]: `spawn()` is only available on `wasm` platforms."
)] )]
/// ///
@@ -66,7 +66,7 @@ pub trait EventLoopExtRunOnDemand {
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow() /// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError> fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget); F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
} }
impl<T> EventLoopExtRunOnDemand for EventLoop<T> { impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
@@ -74,29 +74,8 @@ impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError> fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget), F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{ {
self.event_loop.window_target().clear_exit();
self.event_loop.run_on_demand(event_handler) 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() {}

View File

@@ -1,3 +1,5 @@
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
use crate::keyboard::{KeyCode, PhysicalKey}; use crate::keyboard::{KeyCode, PhysicalKey};
// TODO: Describe what this value contains for each platform // TODO: Describe what this value contains for each platform
@@ -27,24 +29,17 @@ pub trait PhysicalKeyExtScancode {
fn from_scancode(scancode: u32) -> PhysicalKey; fn from_scancode(scancode: u32) -> PhysicalKey;
} }
impl PhysicalKeyExtScancode for PhysicalKey { impl PhysicalKeyExtScancode for KeyCode
fn to_scancode(self) -> Option<u32> { where
crate::platform_impl::physicalkey_to_scancode(self) PhysicalKey: PhysicalKeyExtScancode,
} {
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] #[inline]
fn from_scancode(scancode: u32) -> PhysicalKey { fn from_scancode(scancode: u32) -> PhysicalKey {
<PhysicalKey as PhysicalKeyExtScancode>::from_scancode(scancode) <PhysicalKey as PhysicalKeyExtScancode>::from_scancode(scancode)
} }
#[inline]
fn to_scancode(self) -> Option<u32> {
<PhysicalKey as PhysicalKeyExtScancode>::to_scancode(PhysicalKey::Code(self))
}
} }

View File

@@ -55,7 +55,7 @@ pub trait WindowBuilderExtStartupNotify {
fn with_activation_token(self, token: ActivationToken) -> Self; fn with_activation_token(self, token: ActivationToken) -> Self;
} }
impl EventLoopExtStartupNotify for EventLoopWindowTarget { impl<T> EventLoopExtStartupNotify for EventLoopWindowTarget<T> {
fn read_token_from_env(&self) -> Option<ActivationToken> { fn read_token_from_env(&self) -> Option<ActivationToken> {
match self.p { match self.p {
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
@@ -76,7 +76,7 @@ impl WindowExtStartupNotify for Window {
impl WindowBuilderExtStartupNotify for WindowBuilder { impl WindowBuilderExtStartupNotify for WindowBuilder {
fn with_activation_token(mut self, token: ActivationToken) -> Self { fn with_activation_token(mut self, token: ActivationToken) -> Self {
self.window.platform_specific.activation_token = Some(token); self.platform_specific.activation_token = Some(token);
self self
} }
} }

View File

@@ -4,6 +4,8 @@ use crate::{
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
use crate::platform_impl::{ApplicationName, Backend};
pub use crate::window::Theme; pub use crate::window::Theme;
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Wayland. /// Additional methods on [`EventLoopWindowTarget`] that are specific to Wayland.
@@ -12,7 +14,7 @@ pub trait EventLoopWindowTargetExtWayland {
fn is_wayland(&self) -> bool; fn is_wayland(&self) -> bool;
} }
impl EventLoopWindowTargetExtWayland for EventLoopWindowTarget { impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
#[inline] #[inline]
fn is_wayland(&self) -> bool { fn is_wayland(&self) -> bool {
self.p.is_wayland() self.p.is_wayland()
@@ -34,7 +36,7 @@ pub trait EventLoopBuilderExtWayland {
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> { impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
#[inline] #[inline]
fn with_wayland(&mut self) -> &mut Self { fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland); self.platform_specific.forced_backend = Some(Backend::Wayland);
self self
} }
@@ -65,10 +67,7 @@ pub trait WindowBuilderExtWayland {
impl WindowBuilderExtWayland for WindowBuilder { impl WindowBuilderExtWayland for WindowBuilder {
#[inline] #[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self { fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.window.platform_specific.name = Some(crate::platform_impl::ApplicationName::new( self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
general.into(),
instance.into(),
));
self self
} }
} }

View File

@@ -27,47 +27,19 @@
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border //! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding //! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
use std::error::Error; use crate::cursor::CustomCursor;
use std::fmt::{self, Display, Formatter}; use crate::event::Event;
use std::future::Future; use crate::event_loop::EventLoop;
use std::pin::Pin; use crate::event_loop::EventLoopWindowTarget;
use std::task::{Context, Poll}; use crate::platform_impl::PlatformCustomCursor;
use std::time::Duration; use crate::window::{Window, WindowBuilder};
use crate::SendSyncWrapper;
#[cfg(web_platform)]
use web_sys::HtmlCanvasElement; 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 { pub trait WindowExtWebSys {
/// Only returns the canvas if called from inside the window context (the /// Only returns the canvas if called from inside the window.
/// main thread).
fn canvas(&self) -> Option<HtmlCanvasElement>; 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 { impl WindowExtWebSys for Window {
@@ -75,14 +47,6 @@ impl WindowExtWebSys for Window {
fn canvas(&self) -> Option<HtmlCanvasElement> { fn canvas(&self) -> Option<HtmlCanvasElement> {
self.window.canvas() 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 { pub trait WindowBuilderExtWebSys {
@@ -92,17 +56,16 @@ pub trait WindowBuilderExtWebSys {
/// In any case, the canvas won't be automatically inserted into the web page. /// In any case, the canvas won't be automatically inserted into the web page.
/// ///
/// [`None`] by default. /// [`None`] by default.
#[cfg_attr(
not(web_platform),
doc = "",
doc = "[`HtmlCanvasElement`]: #only-available-on-wasm"
)]
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self; fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
/// Sets whether `event.preventDefault()` should be called on events on the /// Whether `event.preventDefault` should be automatically called to prevent event propagation
/// canvas that have side effects. /// when appropriate.
/// ///
/// See [`Window::set_prevent_default()`] for more details. /// 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.
/// ///
/// Enabled by default. /// Enabled by default.
fn with_prevent_default(self, prevent_default: bool) -> Self; fn with_prevent_default(self, prevent_default: bool) -> Self;
@@ -121,22 +84,22 @@ pub trait WindowBuilderExtWebSys {
impl WindowBuilderExtWebSys for WindowBuilder { impl WindowBuilderExtWebSys for WindowBuilder {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self { fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.window.platform_specific.set_canvas(canvas); self.platform_specific.canvas = SendSyncWrapper(canvas);
self self
} }
fn with_prevent_default(mut self, prevent_default: bool) -> Self { fn with_prevent_default(mut self, prevent_default: bool) -> Self {
self.window.platform_specific.prevent_default = prevent_default; self.platform_specific.prevent_default = prevent_default;
self self
} }
fn with_focusable(mut self, focusable: bool) -> Self { fn with_focusable(mut self, focusable: bool) -> Self {
self.window.platform_specific.focusable = focusable; self.platform_specific.focusable = focusable;
self self
} }
fn with_append(mut self, append: bool) -> Self { fn with_append(mut self, append: bool) -> Self {
self.window.platform_specific.append = append; self.platform_specific.append = append;
self self
} }
} }
@@ -150,11 +113,11 @@ pub trait EventLoopExtWebSys {
/// ///
/// Unlike /// Unlike
#[cfg_attr( #[cfg_attr(
all(web_platform, target_feature = "exception-handling"), all(wasm_platform, target_feature = "exception-handling"),
doc = "`run()`" doc = "`run()`"
)] )]
#[cfg_attr( #[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")), not(all(wasm_platform, target_feature = "exception-handling")),
doc = "[`run()`]" doc = "[`run()`]"
)] )]
/// [^1], this returns immediately, and doesn't throw an exception in order to /// [^1], this returns immediately, and doesn't throw an exception in order to
@@ -166,13 +129,13 @@ pub trait EventLoopExtWebSys {
/// event loop when switching between tabs on a single page application. /// event loop when switching between tabs on a single page application.
/// ///
#[cfg_attr( #[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")), not(all(wasm_platform, target_feature = "exception-handling")),
doc = "[`run()`]: EventLoop::run()" doc = "[`run()`]: EventLoop::run()"
)] )]
/// [^1]: `run()` is _not_ available on WASM when the target supports `exception-handling`. /// [^1]: `run()` is _not_ available on WASM when the target supports `exception-handling`.
fn spawn<F>(self, event_handler: F) fn spawn<F>(self, event_handler: F)
where where
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget); F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
} }
impl<T> EventLoopExtWebSys for EventLoop<T> { impl<T> EventLoopExtWebSys for EventLoop<T> {
@@ -180,7 +143,7 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
fn spawn<F>(self, event_handler: F) fn spawn<F>(self, event_handler: F)
where where
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget), F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{ {
self.event_loop.spawn(event_handler) self.event_loop.spawn(event_handler)
} }
@@ -202,7 +165,7 @@ pub trait EventLoopWindowTargetExtWebSys {
fn poll_strategy(&self) -> PollStrategy; fn poll_strategy(&self) -> PollStrategy;
} }
impl EventLoopWindowTargetExtWebSys for EventLoopWindowTarget { impl<T> EventLoopWindowTargetExtWebSys for EventLoopWindowTarget<T> {
#[inline] #[inline]
fn set_poll_strategy(&self, strategy: PollStrategy) { fn set_poll_strategy(&self, strategy: PollStrategy) {
self.p.set_poll_strategy(strategy); self.p.set_poll_strategy(strategy);
@@ -241,124 +204,23 @@ pub enum PollStrategy {
} }
pub trait CustomCursorExtWebSys { 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. /// 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), /// 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. /// but browser support for image formats is inconsistent. Using [PNG] is recommended.
/// ///
/// [PNG]: https://en.wikipedia.org/wiki/PNG /// [PNG]: https://en.wikipedia.org/wiki/PNG
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder; fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self;
/// 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 { impl CustomCursorExtWebSys for CustomCursor {
fn is_animation(&self) -> bool { fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self {
self.inner.animation Self {
} inner: PlatformCustomCursor::Url {
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder {
CustomCursorBuilder {
inner: PlatformCustomCursorBuilder::Url {
url, url,
hotspot_x, hotspot_x,
hotspot_y, hotspot_y,
}, }
} .into(),
}
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"
),
} }
} }
} }

View File

@@ -2,9 +2,12 @@ use std::{ffi::c_void, path::Path};
use crate::{ use crate::{
dpi::PhysicalSize, dpi::PhysicalSize,
event::DeviceId, event::{DeviceId, KeyEvent},
event_loop::EventLoopBuilder, event_loop::EventLoopBuilder,
keyboard::Key,
monitor::MonitorHandle, monitor::MonitorHandle,
platform::modifier_supplement::KeyEventExtModifierSupplement,
platform_impl::WinIcon,
window::{BadIcon, Icon, Window, WindowBuilder}, window::{BadIcon, Icon, Window, WindowBuilder},
}; };
@@ -15,92 +18,6 @@ pub type HMENU = isize;
/// Monitor Handle type used by Win32 API /// Monitor Handle type used by Win32 API
pub type HMONITOR = isize; 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. /// Additional methods on `EventLoop` that are specific to Windows.
pub trait EventLoopBuilderExtWindows { pub trait EventLoopBuilderExtWindows {
/// Whether to allow the event loop to be created off of the main thread. /// Whether to allow the event loop to be created off of the main thread.
@@ -219,31 +136,6 @@ pub trait WindowExtWindows {
/// ///
/// Enabling the shadow causes a thin 1px line to appear on the top of the window. /// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn set_undecorated_shadow(&self, shadow: bool); 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 { impl WindowExtWindows for Window {
@@ -266,34 +158,6 @@ impl WindowExtWindows for Window {
fn set_undecorated_shadow(&self, shadow: bool) { fn set_undecorated_shadow(&self, shadow: bool) {
self.window.set_undecorated_shadow(shadow) 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. /// Additional methods on `WindowBuilder` that are specific to Windows.
@@ -321,14 +185,7 @@ pub trait WindowBuilderExtWindows {
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look. /// 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. /// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
/// ///
#[cfg_attr( /// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu
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; fn with_menu(self, menu: HMENU) -> Self;
/// This sets `ICON_BIG`. A good ceiling here is 256x256. /// This sets `ICON_BIG`. A good ceiling here is 256x256.
@@ -356,118 +213,54 @@ pub trait WindowBuilderExtWindows {
/// The shadow is hidden by default. /// The shadow is hidden by default.
/// Enabling the shadow causes a thin 1px line to appear on the top of the window. /// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn with_undecorated_shadow(self, shadow: bool) -> Self; 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 { impl WindowBuilderExtWindows for WindowBuilder {
#[inline] #[inline]
fn with_owner_window(mut self, parent: HWND) -> Self { fn with_owner_window(mut self, parent: HWND) -> Self {
self.window.platform_specific.owner = Some(parent); self.platform_specific.owner = Some(parent);
self self
} }
#[inline] #[inline]
fn with_menu(mut self, menu: HMENU) -> Self { fn with_menu(mut self, menu: HMENU) -> Self {
self.window.platform_specific.menu = Some(menu); self.platform_specific.menu = Some(menu);
self self
} }
#[inline] #[inline]
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self { fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self {
self.window.platform_specific.taskbar_icon = taskbar_icon; self.platform_specific.taskbar_icon = taskbar_icon;
self self
} }
#[inline] #[inline]
fn with_no_redirection_bitmap(mut self, flag: bool) -> Self { fn with_no_redirection_bitmap(mut self, flag: bool) -> Self {
self.window.platform_specific.no_redirection_bitmap = flag; self.platform_specific.no_redirection_bitmap = flag;
self self
} }
#[inline] #[inline]
fn with_drag_and_drop(mut self, flag: bool) -> Self { fn with_drag_and_drop(mut self, flag: bool) -> Self {
self.window.platform_specific.drag_and_drop = flag; self.platform_specific.drag_and_drop = flag;
self self
} }
#[inline] #[inline]
fn with_skip_taskbar(mut self, skip: bool) -> Self { fn with_skip_taskbar(mut self, skip: bool) -> Self {
self.window.platform_specific.skip_taskbar = skip; self.platform_specific.skip_taskbar = skip;
self self
} }
#[inline] #[inline]
fn with_class_name<S: Into<String>>(mut self, class_name: S) -> Self { fn with_class_name<S: Into<String>>(mut self, class_name: S) -> Self {
self.window.platform_specific.class_name = class_name.into(); self.platform_specific.class_name = class_name.into();
self self
} }
#[inline] #[inline]
fn with_undecorated_shadow(mut self, shadow: bool) -> Self { fn with_undecorated_shadow(mut self, shadow: bool) -> Self {
self.window.platform_specific.decoration_shadow = shadow; self.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 self
} }
} }
@@ -535,12 +328,27 @@ impl IconExtWindows for Icon {
path: P, path: P,
size: Option<PhysicalSize<u32>>, size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> { ) -> Result<Self, BadIcon> {
let win_icon = crate::platform_impl::WinIcon::from_path(path, size)?; let win_icon = WinIcon::from_path(path, size)?;
Ok(Icon { inner: win_icon }) Ok(Icon { inner: win_icon })
} }
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> { fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?; let win_icon = WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon }) 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()
}
}

View File

@@ -1,6 +1,3 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget}, event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle, monitor::MonitorHandle,
@@ -8,50 +5,9 @@ use crate::{
}; };
use crate::dpi::Size; use crate::dpi::Size;
use crate::platform_impl::{ApplicationName, Backend, XLIB_ERROR_HOOKS};
/// X window type. Maps directly to pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
/// [`_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` /// 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 /// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
@@ -82,10 +38,7 @@ pub type XWindow = u32;
pub fn register_xlib_error_hook(hook: XlibErrorHook) { pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook. // Append new hook.
unsafe { unsafe {
crate::platform_impl::XLIB_ERROR_HOOKS XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
.lock()
.unwrap()
.push(hook);
} }
} }
@@ -95,7 +48,7 @@ pub trait EventLoopWindowTargetExtX11 {
fn is_x11(&self) -> bool; fn is_x11(&self) -> bool;
} }
impl EventLoopWindowTargetExtX11 for EventLoopWindowTarget { impl<T> EventLoopWindowTargetExtX11 for EventLoopWindowTarget<T> {
#[inline] #[inline]
fn is_x11(&self) -> bool { fn is_x11(&self) -> bool {
!self.p.is_wayland() !self.p.is_wayland()
@@ -117,7 +70,7 @@ pub trait EventLoopBuilderExtX11 {
impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> { impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
#[inline] #[inline]
fn with_x11(&mut self) -> &mut Self { fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X); self.platform_specific.forced_backend = Some(Backend::X);
self self
} }
@@ -143,29 +96,29 @@ pub trait WindowBuilderExtX11 {
/// Build window with the given `general` and `instance` names. /// Build window with the given `general` and `instance` names.
/// ///
/// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the /// 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) = "instance", "general"`. /// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
/// ///
/// For details about application ID conventions, see the /// 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) /// [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; fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
/// Build window with override-redirect flag; defaults to false. /// Build window with override-redirect flag; defaults to false. Only relevant on X11.
fn with_override_redirect(self, override_redirect: bool) -> Self; fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. /// 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<WindowType>) -> Self; fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
/// Build window with base size hint. /// Build window with base size hint. Only implemented on X11.
/// ///
/// ``` /// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize}; /// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::Window; /// # use winit::window::WindowBuilder;
/// # use winit::platform::x11::WindowBuilderExtX11; /// # use winit::platform::x11::WindowBuilderExtX11;
/// // Specify the size in logical dimensions like this: /// // Specify the size in logical dimensions like this:
/// Window::builder().with_base_size(LogicalSize::new(400.0, 200.0)); /// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
/// ///
/// // Or specify the size in physical dimensions like this: /// // Or specify the size in physical dimensions like this:
/// Window::builder().with_base_size(PhysicalSize::new(400, 200)); /// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
/// ``` /// ```
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self; fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
@@ -174,12 +127,12 @@ pub trait WindowBuilderExtX11 {
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// use winit::window::Window; /// use winit::window::WindowBuilder;
/// use winit::platform::x11::{XWindow, WindowBuilderExtX11}; /// use winit::platform::x11::{XWindow, WindowBuilderExtX11};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let event_loop = winit::event_loop::EventLoop::new().unwrap(); /// let event_loop = winit::event_loop::EventLoop::new().unwrap();
/// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?; /// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?;
/// let window = Window::builder() /// let window = WindowBuilder::new()
/// .with_embed_parent_window(parent_window_id) /// .with_embed_parent_window(parent_window_id)
/// .build(&event_loop)?; /// .build(&event_loop)?;
/// # Ok(()) } /// # Ok(()) }
@@ -190,46 +143,43 @@ pub trait WindowBuilderExtX11 {
impl WindowBuilderExtX11 for WindowBuilder { impl WindowBuilderExtX11 for WindowBuilder {
#[inline] #[inline]
fn with_x11_visual(mut self, visual_id: XVisualID) -> Self { fn with_x11_visual(mut self, visual_id: XVisualID) -> Self {
self.window.platform_specific.x11.visual_id = Some(visual_id); self.platform_specific.x11.visual_id = Some(visual_id);
self self
} }
#[inline] #[inline]
fn with_x11_screen(mut self, screen_id: i32) -> Self { fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.window.platform_specific.x11.screen_id = Some(screen_id); self.platform_specific.x11.screen_id = Some(screen_id);
self self
} }
#[inline] #[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self { fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.window.platform_specific.name = Some(crate::platform_impl::ApplicationName::new( self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
general.into(),
instance.into(),
));
self self
} }
#[inline] #[inline]
fn with_override_redirect(mut self, override_redirect: bool) -> Self { fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.window.platform_specific.x11.override_redirect = override_redirect; self.platform_specific.x11.override_redirect = override_redirect;
self self
} }
#[inline] #[inline]
fn with_x11_window_type(mut self, x11_window_types: Vec<WindowType>) -> Self { fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.window.platform_specific.x11.x11_window_types = x11_window_types; self.platform_specific.x11.x11_window_types = x11_window_types;
self self
} }
#[inline] #[inline]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self { fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.window.platform_specific.x11.base_size = Some(base_size.into()); self.platform_specific.x11.base_size = Some(base_size.into());
self self
} }
#[inline] #[inline]
fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self { fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self {
self.window.platform_specific.x11.embed_window = Some(parent_window_id); self.platform_specific.x11.embed_window = Some(parent_window_id);
self self
} }
} }

View File

@@ -4,7 +4,6 @@ use std::{
cell::Cell, cell::Cell,
collections::VecDeque, collections::VecDeque,
hash::Hash, hash::Hash,
marker::PhantomData,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
mpsc, Arc, Mutex, RwLock, mpsc, Arc, Mutex, RwLock,
@@ -16,11 +15,10 @@ use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
use android_activity::{ use android_activity::{
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect, AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
}; };
use log::{debug, trace, warn};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::{ use crate::{
cursor::Cursor, cursor::CustomCursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size}, dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error, error,
event::{self, Force, InnerSizeWriter, StartCause}, event::{self, Force, InnerSizeWriter, StartCause},
@@ -141,7 +139,7 @@ pub struct KeyEventExtra {}
pub struct EventLoop<T: 'static> { pub struct EventLoop<T: 'static> {
android_app: AndroidApp, android_app: AndroidApp,
window_target: event_loop::EventLoopWindowTarget, window_target: event_loop::EventLoopWindowTarget<T>,
redraw_flag: SharedFlag, redraw_flag: SharedFlag,
user_events_sender: mpsc::Sender<T>, user_events_sender: mpsc::Sender<T>,
user_events_receiver: PeekableReceiver<T>, //must wake looper whenever something gets sent user_events_receiver: PeekableReceiver<T>, //must wake looper whenever something gets sent
@@ -188,8 +186,9 @@ impl<T: 'static> EventLoop<T> {
&redraw_flag, &redraw_flag,
android_app.create_waker(), android_app.create_waker(),
), ),
_marker: std::marker::PhantomData,
}, },
_marker: PhantomData, _marker: std::marker::PhantomData,
}, },
redraw_flag, redraw_flag,
user_events_sender, user_events_sender,
@@ -205,7 +204,7 @@ impl<T: 'static> EventLoop<T> {
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F) fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F)
where where
F: FnMut(event::Event<T>, &RootELW), F: FnMut(event::Event<T>, &RootELW<T>),
{ {
trace!("Mainloop iteration"); trace!("Mainloop iteration");
@@ -377,7 +376,7 @@ impl<T: 'static> EventLoop<T> {
callback: &mut F, callback: &mut F,
) -> InputStatus ) -> InputStatus
where where
F: FnMut(event::Event<T>, &RootELW), F: FnMut(event::Event<T>, &RootELW<T>),
{ {
let mut input_status = InputStatus::Handled; let mut input_status = InputStatus::Handled;
match event { match event {
@@ -482,15 +481,19 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError> pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget), F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>),
{ {
self.run_on_demand(event_handler) self.run_on_demand(event_handler)
} }
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError> pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget), F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>),
{ {
if self.loop_running {
return Err(EventLoopError::AlreadyRunning);
}
loop { loop {
match self.pump_events(None, &mut event_handler) { match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => { PumpStatus::Exit(0) => {
@@ -508,7 +511,7 @@ impl<T: 'static> EventLoop<T> {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where where
F: FnMut(event::Event<T>, &RootELW), F: FnMut(event::Event<T>, &RootELW<T>),
{ {
if !self.loop_running { if !self.loop_running {
self.loop_running = true; self.loop_running = true;
@@ -541,7 +544,7 @@ impl<T: 'static> EventLoop<T> {
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F) fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where where
F: FnMut(event::Event<T>, &RootELW), F: FnMut(event::Event<T>, &RootELW<T>),
{ {
let start = Instant::now(); let start = Instant::now();
@@ -617,7 +620,7 @@ impl<T: 'static> EventLoop<T> {
}); });
} }
pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget<T> {
&self.window_target &self.window_target
} }
@@ -661,14 +664,15 @@ impl<T> EventLoopProxy<T> {
} }
} }
pub struct EventLoopWindowTarget { pub struct EventLoopWindowTarget<T: 'static> {
app: AndroidApp, app: AndroidApp,
control_flow: Cell<ControlFlow>, control_flow: Cell<ControlFlow>,
exit: Cell<bool>, exit: Cell<bool>,
redraw_requester: RedrawRequester, redraw_requester: RedrawRequester,
_marker: std::marker::PhantomData<T>,
} }
impl EventLoopWindowTarget { impl<T: 'static> EventLoopWindowTarget<T> {
pub fn primary_monitor(&self) -> Option<MonitorHandle> { pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(self.app.clone())) Some(MonitorHandle::new(self.app.clone()))
} }
@@ -710,36 +714,9 @@ impl EventLoopWindowTarget {
self.exit.set(true) self.exit.set(true)
} }
pub(crate) fn clear_exit(&self) {
self.exit.set(false)
}
pub(crate) fn exiting(&self) -> bool { pub(crate) fn exiting(&self) -> bool {
self.exit.get() self.exit.get()
} }
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle
}
}
#[derive(Clone)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::AndroidDisplayHandle::empty().into()
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::AndroidDisplayHandle::new().into())
}
} }
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
@@ -781,9 +758,10 @@ pub(crate) struct Window {
} }
impl Window { impl Window {
pub(crate) fn new( pub(crate) fn new<T: 'static>(
el: &EventLoopWindowTarget, el: &EventLoopWindowTarget<T>,
_window_attrs: window::WindowAttributes, _window_attrs: window::WindowAttributes,
_: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, error::OsError> { ) -> Result<Self, error::OsError> {
// FIXME this ignores requested window attributes // FIXME this ignores requested window attributes
@@ -927,7 +905,9 @@ impl Window {
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {} pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
pub fn set_cursor(&self, _: Cursor) {} pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub fn set_custom_cursor(&self, _: CustomCursor) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported( Err(error::ExternalError::NotSupported(
@@ -1055,7 +1035,6 @@ impl Display for OsError {
} }
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor; pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
@@ -1107,11 +1086,11 @@ impl MonitorHandle {
None None
} }
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> { pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
let size = self.size().into(); let size = self.size().into();
// FIXME this is not the real refresh rate // FIXME this is not the real refresh rate
// (it is guaranteed to support 32 bit color though) // (it is guaranteed to support 32 bit color though)
std::iter::once(VideoModeHandle { std::iter::once(VideoMode {
size, size,
bit_depth: 32, bit_depth: 32,
refresh_rate_millihertz: 60000, refresh_rate_millihertz: 60000,
@@ -1121,14 +1100,14 @@ impl MonitorHandle {
} }
#[derive(Clone, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct VideoModeHandle { pub struct VideoMode {
size: (u32, u32), size: (u32, u32),
bit_depth: u16, bit_depth: u16,
refresh_rate_millihertz: u32, refresh_rate_millihertz: u32,
monitor: MonitorHandle, monitor: MonitorHandle,
} }
impl VideoModeHandle { impl VideoMode {
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
self.size.into() self.size.into()
} }

View File

@@ -3,7 +3,7 @@
use std::{ use std::{
cell::{RefCell, RefMut}, cell::{RefCell, RefMut},
collections::HashSet, collections::HashSet,
fmt, mem, mem,
os::raw::c_void, os::raw::c_void,
ptr, ptr,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
@@ -24,12 +24,13 @@ use objc2::runtime::AnyObject;
use objc2::{msg_send, sel}; use objc2::{msg_send, sel};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use super::event_loop::{EventHandler, Never};
use super::uikit::UIView; use super::uikit::UIView;
use super::view::WinitUIWindow; use super::view::WinitUIWindow;
use crate::{ use crate::{
dpi::PhysicalSize, dpi::PhysicalSize,
event::{Event, InnerSizeWriter, StartCause, WindowEvent}, event::{Event, InnerSizeWriter, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget}, event_loop::ControlFlow,
window::WindowId as RootWindowId, window::WindowId as RootWindowId,
}; };
@@ -46,32 +47,8 @@ macro_rules! bug_assert {
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct HandlePendingUserEvents; pub enum EventWrapper {
StaticEvent(Event<Never>),
pub(crate) struct EventLoopHandler {
#[allow(clippy::type_complexity)]
pub(crate) handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootEventLoopWindowTarget)>,
pub(crate) event_loop: RootEventLoopWindowTarget,
}
impl fmt::Debug for EventLoopHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopHandler")
.field("handler", &"...")
.field("event_loop", &self.event_loop)
.finish()
}
}
impl EventLoopHandler {
fn handle_event(&mut self, event: Event<HandlePendingUserEvents>) {
(self.handler)(event, &self.event_loop)
}
}
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event<HandlePendingUserEvents>),
ScaleFactorChanged(ScaleFactorChanged), ScaleFactorChanged(ScaleFactorChanged),
} }
@@ -84,7 +61,7 @@ pub struct ScaleFactorChanged {
enum UserCallbackTransitionResult<'a> { enum UserCallbackTransitionResult<'a> {
Success { Success {
handler: EventLoopHandler, event_handler: Box<dyn EventHandler>,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
processing_redraws: bool, processing_redraws: bool,
}, },
@@ -93,7 +70,7 @@ enum UserCallbackTransitionResult<'a> {
}, },
} }
impl Event<HandlePendingUserEvents> { impl Event<Never> {
fn is_redraw(&self) -> bool { fn is_redraw(&self) -> bool {
matches!( matches!(
self, self,
@@ -117,11 +94,11 @@ enum AppStateImpl {
Launching { Launching {
queued_windows: Vec<Id<WinitUIWindow>>, queued_windows: Vec<Id<WinitUIWindow>>,
queued_events: Vec<EventWrapper>, queued_events: Vec<EventWrapper>,
queued_handler: EventLoopHandler, queued_event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
}, },
ProcessingEvents { ProcessingEvents {
handler: EventLoopHandler, event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
}, },
@@ -131,15 +108,15 @@ enum AppStateImpl {
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
}, },
ProcessingRedraws { ProcessingRedraws {
handler: EventLoopHandler, event_handler: Box<dyn EventHandler>,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
}, },
Waiting { Waiting {
waiting_handler: EventLoopHandler, waiting_event_handler: Box<dyn EventHandler>,
start: Instant, start: Instant,
}, },
PollFinished { PollFinished {
waiting_handler: EventLoopHandler, waiting_event_handler: Box<dyn EventHandler>,
}, },
Terminated, Terminated,
} }
@@ -227,7 +204,7 @@ impl AppState {
matches!(self.state(), AppStateImpl::Terminated) matches!(self.state(), AppStateImpl::Terminated)
} }
fn will_launch_transition(&mut self, queued_handler: EventLoopHandler) { fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() { let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::NotLaunched { AppStateImpl::NotLaunched {
queued_windows, queued_windows,
@@ -239,28 +216,28 @@ impl AppState {
self.set_state(AppStateImpl::Launching { self.set_state(AppStateImpl::Launching {
queued_windows, queued_windows,
queued_events, queued_events,
queued_handler, queued_event_handler,
queued_gpu_redraws, queued_gpu_redraws,
}); });
} }
fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) { fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) {
let (windows, events, handler, queued_gpu_redraws) = match self.take_state() { let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching { AppStateImpl::Launching {
queued_windows, queued_windows,
queued_events, queued_events,
queued_handler, queued_event_handler,
queued_gpu_redraws, queued_gpu_redraws,
} => ( } => (
queued_windows, queued_windows,
queued_events, queued_events,
queued_handler, queued_event_handler,
queued_gpu_redraws, queued_gpu_redraws,
), ),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingEvents { self.set_state(AppStateImpl::ProcessingEvents {
handler, event_handler,
active_control_flow: self.control_flow, active_control_flow: self.control_flow,
queued_gpu_redraws, queued_gpu_redraws,
}); });
@@ -274,19 +251,24 @@ impl AppState {
return None; return None;
} }
let (handler, event) = match (self.control_flow, self.take_state()) { let (event_handler, event) = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => ( (
waiting_handler, ControlFlow::Poll,
AppStateImpl::PollFinished {
waiting_event_handler,
},
) => (
waiting_event_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)), EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)),
), ),
( (
ControlFlow::Wait, ControlFlow::Wait,
AppStateImpl::Waiting { AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}, },
) => ( ) => (
waiting_handler, waiting_event_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start, start,
requested_resume: None, requested_resume: None,
@@ -295,7 +277,7 @@ impl AppState {
( (
ControlFlow::WaitUntil(requested_resume), ControlFlow::WaitUntil(requested_resume),
AppStateImpl::Waiting { AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}, },
) => { ) => {
@@ -310,13 +292,13 @@ impl AppState {
requested_resume: Some(requested_resume), requested_resume: Some(requested_resume),
})) }))
}; };
(waiting_handler, event) (waiting_event_handler, event)
} }
s => bug!("`EventHandler` unexpectedly woke up {:?}", s), s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingEvents { self.set_state(AppStateImpl::ProcessingEvents {
handler, event_handler,
queued_gpu_redraws: Default::default(), queued_gpu_redraws: Default::default(),
active_control_flow: self.control_flow, active_control_flow: self.control_flow,
}); });
@@ -361,20 +343,25 @@ impl AppState {
} }
} }
let (handler, queued_gpu_redraws, active_control_flow, processing_redraws) = let (event_handler, queued_gpu_redraws, active_control_flow, processing_redraws) =
match self.take_state() { match self.take_state() {
AppStateImpl::Launching { .. } AppStateImpl::Launching { .. }
| AppStateImpl::NotLaunched { .. } | AppStateImpl::NotLaunched { .. }
| AppStateImpl::InUserCallback { .. } => unreachable!(), | AppStateImpl::InUserCallback { .. } => unreachable!(),
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents {
handler, event_handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
} => (handler, queued_gpu_redraws, active_control_flow, false), } => (
AppStateImpl::ProcessingRedraws { event_handler,
handler, queued_gpu_redraws,
active_control_flow, active_control_flow,
} => (handler, Default::default(), active_control_flow, true), false,
),
AppStateImpl::ProcessingRedraws {
event_handler,
active_control_flow,
} => (event_handler, Default::default(), active_control_flow, true),
AppStateImpl::PollFinished { .. } AppStateImpl::PollFinished { .. }
| AppStateImpl::Waiting { .. } | AppStateImpl::Waiting { .. }
| AppStateImpl::Terminated => unreachable!(), | AppStateImpl::Terminated => unreachable!(),
@@ -384,23 +371,23 @@ impl AppState {
queued_gpu_redraws, queued_gpu_redraws,
}); });
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success {
handler, event_handler,
active_control_flow, active_control_flow,
processing_redraws, processing_redraws,
} }
} }
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> { fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> {
let (handler, queued_gpu_redraws, active_control_flow) = match self.take_state() { let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents {
handler, event_handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
} => (handler, queued_gpu_redraws, active_control_flow), } => (event_handler, queued_gpu_redraws, active_control_flow),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingRedraws { self.set_state(AppStateImpl::ProcessingRedraws {
handler, event_handler,
active_control_flow, active_control_flow,
}); });
queued_gpu_redraws queued_gpu_redraws
@@ -410,11 +397,11 @@ impl AppState {
if !self.has_launched() || self.has_terminated() { if !self.has_launched() || self.has_terminated() {
return; return;
} }
let (waiting_handler, old) = match self.take_state() { let (waiting_event_handler, old) = match self.take_state() {
AppStateImpl::ProcessingRedraws { AppStateImpl::ProcessingRedraws {
handler, event_handler,
active_control_flow, active_control_flow,
} => (handler, active_control_flow), } => (event_handler, active_control_flow),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
@@ -423,7 +410,7 @@ impl AppState {
(ControlFlow::Wait, ControlFlow::Wait) => { (ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}); });
} }
@@ -432,14 +419,14 @@ impl AppState {
{ {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}); });
} }
(_, ControlFlow::Wait) => { (_, ControlFlow::Wait) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}); });
self.waker.stop() self.waker.stop()
@@ -447,22 +434,24 @@ impl AppState {
(_, ControlFlow::WaitUntil(new_instant)) => { (_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}); });
self.waker.start_at(new_instant) self.waker.start_at(new_instant)
} }
// Unlike on macOS, handle Poll to Poll transition here to call the waker // Unlike on macOS, handle Poll to Poll transition here to call the waker
(_, ControlFlow::Poll) => { (_, ControlFlow::Poll) => {
self.set_state(AppStateImpl::PollFinished { waiting_handler }); self.set_state(AppStateImpl::PollFinished {
waiting_event_handler,
});
self.waker.start() self.waker.start()
} }
} }
} }
fn terminated_transition(&mut self) -> EventLoopHandler { fn terminated_transition(&mut self) -> Box<dyn EventHandler> {
match self.replace_state(AppStateImpl::Terminated) { match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { handler, .. } => handler, AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler,
s => bug!("`LoopExiting` happened while not processing events {:?}", s), s => bug!("`LoopExiting` happened while not processing events {:?}", s),
} }
} }
@@ -527,8 +516,8 @@ pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUI
} }
} }
pub(crate) fn will_launch(mtm: MainThreadMarker, queued_handler: EventLoopHandler) { pub fn will_launch(mtm: MainThreadMarker, queued_event_handler: Box<dyn EventHandler>) {
AppState::get_mut(mtm).will_launch_transition(queued_handler) AppState::get_mut(mtm).will_launch_transition(queued_event_handler)
} }
pub fn did_finish_launching(mtm: MainThreadMarker) { pub fn did_finish_launching(mtm: MainThreadMarker) {
@@ -605,17 +594,17 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
return; return;
} }
let (mut handler, active_control_flow, processing_redraws) = let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() { match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
queued_events.extend(events); queued_events.extend(events);
return; return;
} }
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success {
handler, event_handler,
active_control_flow, active_control_flow,
processing_redraws, processing_redraws,
} => (handler, active_control_flow, processing_redraws), } => (event_handler, active_control_flow, processing_redraws),
}; };
drop(this); drop(this);
@@ -630,9 +619,11 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
event event
); );
} }
handler.handle_event(event) event_handler.handle_nonuser_event(event)
}
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
} }
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
} }
} }
@@ -659,12 +650,12 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
"redraw queued while processing redraws" "redraw queued while processing redraws"
); );
AppStateImpl::ProcessingRedraws { AppStateImpl::ProcessingRedraws {
handler, event_handler,
active_control_flow, active_control_flow,
} }
} else { } else {
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents {
handler, event_handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
} }
@@ -684,9 +675,11 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
event event
); );
} }
handler.handle_event(event) event_handler.handle_nonuser_event(event)
}
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
} }
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
} }
} }
} }
@@ -694,23 +687,23 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
fn handle_user_events(mtm: MainThreadMarker) { fn handle_user_events(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
let (mut handler, active_control_flow, processing_redraws) = let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() { match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => { UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
bug!("unexpected attempted to process an event") bug!("unexpected attempted to process an event")
} }
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success {
handler, event_handler,
active_control_flow, active_control_flow,
processing_redraws, processing_redraws,
} => (handler, active_control_flow, processing_redraws), } => (event_handler, active_control_flow, processing_redraws),
}; };
if processing_redraws { if processing_redraws {
bug!("user events attempted to be sent out while `ProcessingRedraws`"); bug!("user events attempted to be sent out while `ProcessingRedraws`");
} }
drop(this); drop(this);
handler.handle_event(Event::UserEvent(HandlePendingUserEvents)); event_handler.handle_user_events();
loop { loop {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
@@ -730,7 +723,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
_ => unreachable!(), _ => unreachable!(),
}; };
this.app_state = Some(AppStateImpl::ProcessingEvents { this.app_state = Some(AppStateImpl::ProcessingEvents {
handler, event_handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
}); });
@@ -740,12 +733,13 @@ fn handle_user_events(mtm: MainThreadMarker) {
for wrapper in queued_events { for wrapper in queued_events {
match wrapper { match wrapper {
EventWrapper::StaticEvent(event) => handler.handle_event(event), EventWrapper::StaticEvent(event) => event_handler.handle_nonuser_event(event),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event), EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
}
} }
} }
event_handler.handle_user_events();
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
} }
} }
@@ -785,13 +779,13 @@ pub fn handle_events_cleared(mtm: MainThreadMarker) {
pub fn terminated(mtm: MainThreadMarker) { pub fn terminated(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
let mut handler = this.terminated_transition(); let mut event_handler = this.terminated_transition();
drop(this); drop(this);
handler.handle_event(Event::LoopExiting) event_handler.handle_nonuser_event(Event::LoopExiting)
} }
fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) { fn handle_hidpi_proxy(event_handler: &mut Box<dyn EventHandler>, event: ScaleFactorChanged) {
let ScaleFactorChanged { let ScaleFactorChanged {
suggested_size, suggested_size,
scale_factor, scale_factor,
@@ -805,7 +799,7 @@ fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged)
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
}, },
}; };
handler.handle_event(event); event_handler.handle_nonuser_event(event);
let (view, screen_frame) = get_view_and_screen_frame(&window); let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_inner_size.lock().unwrap(); let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size); drop(new_inner_size);

View File

@@ -1,6 +1,7 @@
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
ffi::c_void, ffi::c_void,
fmt::{self, Debug},
marker::PhantomData, marker::PhantomData,
ptr, ptr,
sync::mpsc::{self, Receiver, Sender}, sync::mpsc::{self, Receiver, Sender},
@@ -24,7 +25,6 @@ use crate::{
EventLoopWindowTarget as RootEventLoopWindowTarget, EventLoopWindowTarget as RootEventLoopWindowTarget,
}, },
platform::ios::Idiom, platform::ios::Idiom,
platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents},
}; };
use super::{app_state, monitor, view, MonitorHandle}; use super::{app_state, monitor, view, MonitorHandle};
@@ -34,11 +34,12 @@ use super::{
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct EventLoopWindowTarget { pub struct EventLoopWindowTarget<T: 'static> {
pub(super) mtm: MainThreadMarker, pub(super) mtm: MainThreadMarker,
p: PhantomData<T>,
} }
impl EventLoopWindowTarget { impl<T: 'static> EventLoopWindowTarget<T> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> { pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::uiscreens(self.mtm) monitor::uiscreens(self.mtm)
} }
@@ -77,56 +78,19 @@ impl EventLoopWindowTarget {
pub(crate) fn exit(&self) { pub(crate) fn exit(&self) {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html // https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically // it is not possible to quit an iOS app gracefully and programatically
log::warn!("`ControlFlow::Exit` ignored on iOS"); warn!("`ControlFlow::Exit` ignored on iOS");
} }
pub(crate) fn exiting(&self) -> bool { pub(crate) fn exiting(&self) -> bool {
false false
} }
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle
}
}
#[derive(Clone)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::UiKitDisplayHandle::empty().into()
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::UiKitDisplayHandle::new().into())
}
}
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootEventLoopWindowTarget),
receiver: mpsc::Receiver<T>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootEventLoopWindowTarget) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
}
}
}
} }
pub struct EventLoop<T: 'static> { pub struct EventLoop<T: 'static> {
mtm: MainThreadMarker, mtm: MainThreadMarker,
sender: Sender<T>, sender: Sender<T>,
receiver: Receiver<T>, receiver: Receiver<T>,
window_target: RootEventLoopWindowTarget, window_target: RootEventLoopWindowTarget<T>,
} }
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -159,59 +123,59 @@ impl<T: 'static> EventLoop<T> {
sender, sender,
receiver, receiver,
window_target: RootEventLoopWindowTarget { window_target: RootEventLoopWindowTarget {
p: EventLoopWindowTarget { mtm }, p: EventLoopWindowTarget {
mtm,
p: PhantomData,
},
_marker: PhantomData, _marker: PhantomData,
}, },
}) })
} }
pub fn run<F>(self, handler: F) -> ! pub fn run<F>(self, event_handler: F) -> !
where where
F: FnMut(Event<T>, &RootEventLoopWindowTarget), F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{ {
let application = UIApplication::shared(self.mtm); unsafe {
assert!( let application = UIApplication::shared(self.mtm);
application.is_none(), assert!(
"\ application.is_none(),
"\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS", Note: `EventLoop::run` calls `UIApplicationMain` on iOS",
); );
let handler = map_user_event(handler, self.receiver); let event_handler = std::mem::transmute::<
Box<dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>)>,
Box<EventHandlerCallback<T>>,
>(Box::new(event_handler));
let handler = unsafe { let handler = EventLoopHandler {
std::mem::transmute::< f: event_handler,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootEventLoopWindowTarget)>, receiver: self.receiver,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootEventLoopWindowTarget)>, event_loop: self.window_target,
>(Box::new(handler)) };
};
let handler = EventLoopHandler { app_state::will_launch(self.mtm, Box::new(handler));
handler,
event_loop: self.window_target,
};
app_state::will_launch(self.mtm, handler); // Ensure application delegate is initialized
view::WinitApplicationDelegate::class();
// Ensure application delegate is initialized
view::WinitApplicationDelegate::class();
unsafe {
UIApplicationMain( UIApplicationMain(
0, 0,
ptr::null(), ptr::null(),
None, None,
Some(&NSString::from_str("WinitApplicationDelegate")), Some(&NSString::from_str("WinitApplicationDelegate")),
) );
}; unreachable!()
unreachable!() }
} }
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone()) EventLoopProxy::new(self.sender.clone())
} }
pub fn window_target(&self) -> &RootEventLoopWindowTarget { pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
&self.window_target &self.window_target
} }
} }
@@ -378,3 +342,39 @@ fn setup_control_flow_observers() {
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode); CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
} }
} }
#[derive(Debug)]
pub enum Never {}
type EventHandlerCallback<T> = dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>) + 'static;
pub trait EventHandler: Debug {
fn handle_nonuser_event(&mut self, event: Event<Never>);
fn handle_user_events(&mut self);
}
struct EventLoopHandler<T: 'static> {
f: Box<EventHandlerCallback<T>>,
receiver: Receiver<T>,
event_loop: RootEventLoopWindowTarget<T>,
}
impl<T: 'static> Debug for EventLoopHandler<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopHandler")
.field("event_loop", &self.event_loop)
.finish()
}
}
impl<T: 'static> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<Never>) {
(self.f)(event.map_nonuser_event().unwrap(), &self.event_loop);
}
fn handle_user_events(&mut self) {
for event in self.receiver.try_iter() {
(self.f)(Event::UserEvent(event), &self.event_loop);
}
}
}

View File

@@ -68,35 +68,34 @@ mod window;
use std::fmt; use std::fmt;
use crate::event::DeviceId as RootDeviceId;
pub(crate) use self::{ pub(crate) use self::{
event_loop::{ event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, OwnedDisplayHandle, EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
PlatformSpecificEventLoopAttributes,
}, },
monitor::{MonitorHandle, VideoModeHandle}, monitor::{MonitorHandle, VideoMode},
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
}; };
use self::uikit::UIScreen;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor; pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use crate::platform_impl::Fullscreen;
/// There is no way to detect which device that performed a certain event in
/// UIKit (i.e. you can't differentiate between different external keyboards,
/// or wether it was the main touchscreen, assistive technologies, or some
/// other pointer device that caused a touch event).
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId; pub struct DeviceId {
uiscreen: *const UIScreen,
}
impl DeviceId { impl DeviceId {
pub const unsafe fn dummy() -> Self { pub const unsafe fn dummy() -> Self {
DeviceId DeviceId {
uiscreen: std::ptr::null(),
}
} }
} }
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); unsafe impl Send for DeviceId {}
unsafe impl Sync for DeviceId {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {} pub struct KeyEventExtra {}

View File

@@ -8,12 +8,11 @@ use std::{
use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger}; use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger};
use objc2::mutability::IsRetainable; use objc2::mutability::IsRetainable;
use objc2::rc::Id; use objc2::rc::Id;
use objc2::Message;
use super::uikit::{UIScreen, UIScreenMode}; use super::uikit::{UIScreen, UIScreenMode};
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoModeHandle as RootVideoModeHandle, monitor::VideoMode as RootVideoMode,
platform_impl::platform::app_state, platform_impl::platform::app_state,
}; };
@@ -21,15 +20,16 @@ use crate::{
#[derive(Debug)] #[derive(Debug)]
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>); struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> { impl<T: IsRetainable> Clone for MainThreadBoundDelegateImpls<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(MainThreadMarker::run_on_main(|mtm| { Self(
MainThreadBound::new(Id::clone(self.0.get(mtm)), mtm) self.0
})) .get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)),
)
} }
} }
impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> { impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) { fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Marker only used to get the pointer // SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() }; let mtm = unsafe { MainThreadMarker::new_unchecked() };
@@ -37,7 +37,7 @@ impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
} }
} }
impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> { impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// SAFETY: Marker only used to get the pointer // SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() }; let mtm = unsafe { MainThreadMarker::new_unchecked() };
@@ -45,10 +45,10 @@ impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
} }
} }
impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {} impl<T: IsRetainable> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoModeHandle { pub struct VideoMode {
pub(crate) size: (u32, u32), pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16, pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32, pub(crate) refresh_rate_millihertz: u32,
@@ -56,15 +56,15 @@ pub struct VideoModeHandle {
pub(crate) monitor: MonitorHandle, pub(crate) monitor: MonitorHandle,
} }
impl VideoModeHandle { impl VideoMode {
fn new( fn new(
uiscreen: Id<UIScreen>, uiscreen: Id<UIScreen>,
screen_mode: Id<UIScreenMode>, screen_mode: Id<UIScreenMode>,
mtm: MainThreadMarker, mtm: MainThreadMarker,
) -> VideoModeHandle { ) -> VideoMode {
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen); let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size = screen_mode.size(); let size = screen_mode.size();
VideoModeHandle { VideoMode {
size: (size.width as u32, size.height as u32), size: (size.width as u32, size.height as u32),
bit_depth: 32, bit_depth: 32,
refresh_rate_millihertz, refresh_rate_millihertz,
@@ -100,9 +100,11 @@ pub struct MonitorHandle {
impl Clone for MonitorHandle { impl Clone for MonitorHandle {
fn clone(&self) -> Self { fn clone(&self) -> Self {
MainThreadMarker::run_on_main(|mtm| Self { Self {
ui_screen: MainThreadBound::new(self.ui_screen.get(mtm).clone(), mtm), ui_screen: self
}) .ui_screen
.get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)),
}
} }
} }
@@ -135,13 +137,24 @@ impl Ord for MonitorHandle {
impl fmt::Debug for MonitorHandle { impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MonitorHandle") // TODO: Do this using the proper fmt API
.field("name", &self.name()) #[derive(Debug)]
.field("size", &self.size()) #[allow(dead_code)]
.field("position", &self.position()) struct MonitorHandle {
.field("scale_factor", &self.scale_factor()) name: Option<String>,
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz()) size: PhysicalSize<u32>,
.finish_non_exhaustive() position: PhysicalPosition<i32>,
scale_factor: f64,
}
let monitor_id_proxy = MonitorHandle {
name: self.name(),
size: self.size(),
position: self.position(),
scale_factor: self.scale_factor(),
};
monitor_id_proxy.fmt(f)
} }
} }
@@ -155,16 +168,16 @@ impl MonitorHandle {
} }
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
MainThreadMarker::run_on_main(|mtm| { self.ui_screen.get_on_main(|ui_screen, mtm| {
let main = UIScreen::main(mtm); let main = UIScreen::main(mtm);
if *self.ui_screen(mtm) == main { if *ui_screen == main {
Some("Primary".to_string()) Some("Primary".to_string())
} else if *self.ui_screen(mtm) == main.mirroredScreen() { } else if *ui_screen == main.mirroredScreen() {
Some("Mirrored".to_string()) Some("Mirrored".to_string())
} else { } else {
UIScreen::screens(mtm) UIScreen::screens(mtm)
.iter() .iter()
.position(|rhs| rhs == &**self.ui_screen(mtm)) .position(|rhs| rhs == &**ui_screen)
.map(|idx| idx.to_string()) .map(|idx| idx.to_string())
} }
}) })
@@ -173,39 +186,38 @@ impl MonitorHandle {
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
let bounds = self let bounds = self
.ui_screen .ui_screen
.get_on_main(|ui_screen| ui_screen.nativeBounds()); .get_on_main(|ui_screen, _| ui_screen.nativeBounds());
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32) PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
} }
pub fn position(&self) -> PhysicalPosition<i32> { pub fn position(&self) -> PhysicalPosition<i32> {
let bounds = self let bounds = self
.ui_screen .ui_screen
.get_on_main(|ui_screen| ui_screen.nativeBounds()); .get_on_main(|ui_screen, _| ui_screen.nativeBounds());
(bounds.origin.x as f64, bounds.origin.y as f64).into() (bounds.origin.x as f64, bounds.origin.y as f64).into()
} }
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
self.ui_screen self.ui_screen
.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64 .get_on_main(|ui_screen, _| ui_screen.nativeScale()) as f64
} }
pub fn refresh_rate_millihertz(&self) -> Option<u32> { pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some( Some(
self.ui_screen self.ui_screen
.get_on_main(|ui_screen| refresh_rate_millihertz(ui_screen)), .get_on_main(|ui_screen, _| refresh_rate_millihertz(ui_screen)),
) )
} }
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> { pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
MainThreadMarker::run_on_main(|mtm| { self.ui_screen.get_on_main(|ui_screen, mtm| {
let ui_screen = self.ui_screen(mtm); // Use Ord impl of RootVideoMode
// Use Ord impl of RootVideoModeHandle
let modes: BTreeSet<_> = ui_screen let modes: BTreeSet<_> = ui_screen
.availableModes() .availableModes()
.into_iter() .into_iter()
.map(|mode| RootVideoModeHandle { .map(|mode| RootVideoMode {
video_mode: VideoModeHandle::new(ui_screen.clone(), mode, mtm), video_mode: VideoMode::new(ui_screen.clone(), mode, mtm),
}) })
.collect(); .collect();
@@ -217,13 +229,9 @@ impl MonitorHandle {
self.ui_screen.get(mtm) self.ui_screen.get(mtm)
} }
pub fn preferred_video_mode(&self) -> VideoModeHandle { pub fn preferred_video_mode(&self) -> VideoMode {
MainThreadMarker::run_on_main(|mtm| { self.ui_screen.get_on_main(|ui_screen, mtm| {
VideoModeHandle::new( VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm)
self.ui_screen(mtm).clone(),
self.ui_screen(mtm).preferredMode().unwrap(),
mtm,
)
}) })
} }
} }

View File

@@ -1,121 +0,0 @@
use icrate::Foundation::{CGFloat, NSInteger, NSObject, NSUInteger};
use objc2::{
encode::{Encode, Encoding},
extern_class, extern_methods, mutability, ClassType,
};
// https://developer.apple.com/documentation/uikit/uigesturerecognizer
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIGestureRecognizer;
unsafe impl ClassType for UIGestureRecognizer {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIGestureRecognizer {
#[method(state)]
pub fn state(&self) -> UIGestureRecognizerState;
}
);
unsafe impl Encode for UIGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}
// https://developer.apple.com/documentation/uikit/uigesturerecognizer/state
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIGestureRecognizerState(NSInteger);
unsafe impl Encode for UIGestureRecognizerState {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[allow(dead_code)]
impl UIGestureRecognizerState {
pub const Possible: Self = Self(0);
pub const Began: Self = Self(1);
pub const Changed: Self = Self(2);
pub const Ended: Self = Self(3);
pub const Cancelled: Self = Self(4);
pub const Failed: Self = Self(5);
}
// https://developer.apple.com/documentation/uikit/uipinchgesturerecognizer
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIPinchGestureRecognizer;
unsafe impl ClassType for UIPinchGestureRecognizer {
type Super = UIGestureRecognizer;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIPinchGestureRecognizer {
#[method(scale)]
pub fn scale(&self) -> CGFloat;
#[method(velocity)]
pub fn velocity(&self) -> CGFloat;
}
);
unsafe impl Encode for UIPinchGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}
// https://developer.apple.com/documentation/uikit/uirotationgesturerecognizer
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIRotationGestureRecognizer;
unsafe impl ClassType for UIRotationGestureRecognizer {
type Super = UIGestureRecognizer;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIRotationGestureRecognizer {
#[method(rotation)]
pub fn rotation(&self) -> CGFloat;
#[method(velocity)]
pub fn velocity(&self) -> CGFloat;
}
);
unsafe impl Encode for UIRotationGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}
// https://developer.apple.com/documentation/uikit/uitapgesturerecognizer
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UITapGestureRecognizer;
unsafe impl ClassType for UITapGestureRecognizer {
type Super = UIGestureRecognizer;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UITapGestureRecognizer {
#[method(setNumberOfTapsRequired:)]
pub fn setNumberOfTapsRequired(&self, number_of_taps_required: NSUInteger);
#[method(setNumberOfTouchesRequired:)]
pub fn setNumberOfTouchesRequired(&self, number_of_touches_required: NSUInteger);
}
);
unsafe impl Encode for UITapGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}

View File

@@ -9,7 +9,6 @@ mod application;
mod coordinate_space; mod coordinate_space;
mod device; mod device;
mod event; mod event;
mod gesture_recognizer;
mod responder; mod responder;
mod screen; mod screen;
mod screen_mode; mod screen_mode;
@@ -24,10 +23,6 @@ pub(crate) use self::application::UIApplication;
pub(crate) use self::coordinate_space::UICoordinateSpace; pub(crate) use self::coordinate_space::UICoordinateSpace;
pub(crate) use self::device::UIDevice; pub(crate) use self::device::UIDevice;
pub(crate) use self::event::UIEvent; pub(crate) use self::event::UIEvent;
pub(crate) use self::gesture_recognizer::{
UIGestureRecognizer, UIGestureRecognizerState, UIPinchGestureRecognizer,
UIRotationGestureRecognizer, UITapGestureRecognizer,
};
pub(crate) use self::responder::UIResponder; pub(crate) use self::responder::UIResponder;
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation}; pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
pub(crate) use self::screen_mode::UIScreenMode; pub(crate) use self::screen_mode::UIScreenMode;

View File

@@ -3,7 +3,7 @@ use objc2::encode::{Encode, Encoding};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UICoordinateSpace, UIGestureRecognizer, UIResponder, UIViewController}; use super::{UICoordinateSpace, UIResponder, UIViewController};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@@ -65,12 +65,6 @@ extern_methods!(
#[method(setNeedsDisplay)] #[method(setNeedsDisplay)]
pub fn setNeedsDisplay(&self); pub fn setNeedsDisplay(&self);
#[method(addGestureRecognizer:)]
pub fn addGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
#[method(removeGestureRecognizer:)]
pub fn removeGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
} }
); );

View File

@@ -39,7 +39,7 @@ extern_methods!(
} }
); );
bitflags::bitflags! { bitflags! {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct UIInterfaceOrientationMask: NSUInteger { pub struct UIInterfaceOrientationMask: NSUInteger {
const Portrait = 1 << 1; const Portrait = 1 << 1;

View File

@@ -1,38 +1,32 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::cell::{Cell, RefCell}; use std::cell::Cell;
use std::ptr::NonNull;
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet}; use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet};
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::AnyClass; use objc2::runtime::AnyClass;
use objc2::{ use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
declare_class, extern_methods, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
};
use super::app_state::{self, EventWrapper}; use super::app_state::{self, EventWrapper};
use super::uikit::{ use super::uikit::{
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIGestureRecognizerState, UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask,
UIInterfaceOrientationMask, UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UIResponder, UIStatusBarStyle, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView,
UIStatusBarStyle, UITapGestureRecognizer, UITouch, UITouchPhase, UITouchType, UIViewController, UIWindow,
UITraitCollection, UIView, UIViewController, UIWindow,
}; };
use super::window::WindowId; use super::window::WindowId;
use crate::{ use crate::{
dpi::PhysicalPosition, dpi::PhysicalPosition,
event::{Event, Force, Touch, TouchPhase, WindowEvent}, event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::ValidOrientations, platform::ios::ValidOrientations,
platform_impl::platform::{ platform_impl::platform::{
ffi::{UIRectEdge, UIUserInterfaceIdiom}, ffi::{UIRectEdge, UIUserInterfaceIdiom},
Fullscreen, DEVICE_ID, window::PlatformSpecificWindowBuilderAttributes,
DeviceId, Fullscreen,
}, },
window::{WindowAttributes, WindowId as RootWindowId}, window::{WindowAttributes, WindowId as RootWindowId},
}; };
pub struct WinitViewState {
pinch_gesture_recognizer: RefCell<Option<Id<UIPinchGestureRecognizer>>>,
doubletap_gesture_recognizer: RefCell<Option<Id<UITapGestureRecognizer>>>,
rotation_gesture_recognizer: RefCell<Option<Id<UIRotationGestureRecognizer>>>,
}
declare_class!( declare_class!(
pub(crate) struct WinitView; pub(crate) struct WinitView;
@@ -43,10 +37,6 @@ declare_class!(
const NAME: &'static str = "WinitUIView"; const NAME: &'static str = "WinitUIView";
} }
impl DeclaredClass for WinitView {
type Ivars = WinitViewState;
}
unsafe impl WinitView { unsafe impl WinitView {
#[method(drawRect:)] #[method(drawRect:)]
fn draw_rect(&self, rect: CGRect) { fn draw_rect(&self, rect: CGRect) {
@@ -167,79 +157,6 @@ declare_class!(
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) { fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[method(pinchGesture:)]
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
let window = self.window().unwrap();
let phase = match recognizer.state() {
UIGestureRecognizerState::Began => TouchPhase::Started,
UIGestureRecognizerState::Changed => TouchPhase::Moved,
UIGestureRecognizerState::Ended => TouchPhase::Ended,
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
TouchPhase::Cancelled
}
state => panic!("unexpected recognizer state: {:?}", state),
};
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::PinchGesture {
device_id: DEVICE_ID,
delta: recognizer.velocity() as _,
phase,
},
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[method(doubleTapGesture:)]
fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) {
let window = self.window().unwrap();
if recognizer.state() == UIGestureRecognizerState::Ended {
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::DoubleTapGesture {
device_id: DEVICE_ID,
},
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
}
#[method(rotationGesture:)]
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
let window = self.window().unwrap();
let phase = match recognizer.state() {
UIGestureRecognizerState::Began => TouchPhase::Started,
UIGestureRecognizerState::Changed => TouchPhase::Moved,
UIGestureRecognizerState::Ended => TouchPhase::Ended,
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
TouchPhase::Cancelled
}
state => panic!("unexpected recognizer state: {:?}", state),
};
// Flip the velocity to match macOS.
let delta = -recognizer.velocity() as _;
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::RotationGesture {
device_id: DEVICE_ID,
delta,
phase,
},
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
} }
); );
@@ -263,73 +180,24 @@ extern_methods!(
impl WinitView { impl WinitView {
pub(crate) fn new( pub(crate) fn new(
_mtm: MainThreadMarker, _mtm: MainThreadMarker,
window_attributes: &WindowAttributes, _window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect, frame: CGRect,
) -> Id<Self> { ) -> Id<Self> {
let this = Self::alloc().set_ivars(WinitViewState { let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
pinch_gesture_recognizer: RefCell::new(None),
doubletap_gesture_recognizer: RefCell::new(None),
rotation_gesture_recognizer: RefCell::new(None),
});
let this: Id<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
this.setMultipleTouchEnabled(true); this.setMultipleTouchEnabled(true);
if let Some(scale_factor) = window_attributes.platform_specific.scale_factor { if let Some(scale_factor) = platform_attributes.scale_factor {
this.setContentScaleFactor(scale_factor as _); this.setContentScaleFactor(scale_factor as _);
} }
this this
} }
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
if should_recognize {
if self.ivars().pinch_gesture_recognizer.borrow().is_none() {
let pinch: Id<UIPinchGestureRecognizer> = unsafe {
msg_send_id![UIPinchGestureRecognizer::alloc(), initWithTarget: self, action: sel!(pinchGesture:)]
};
self.addGestureRecognizer(&pinch);
self.ivars().pinch_gesture_recognizer.replace(Some(pinch));
}
} else if let Some(recognizer) = self.ivars().pinch_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer);
}
}
pub(crate) fn recognize_doubletap_gesture(&self, should_recognize: bool) {
if should_recognize {
if self.ivars().doubletap_gesture_recognizer.borrow().is_none() {
let tap: Id<UITapGestureRecognizer> = unsafe {
msg_send_id![UITapGestureRecognizer::alloc(), initWithTarget: self, action: sel!(doubleTapGesture:)]
};
tap.setNumberOfTapsRequired(2);
tap.setNumberOfTouchesRequired(1);
self.addGestureRecognizer(&tap);
self.ivars().doubletap_gesture_recognizer.replace(Some(tap));
}
} else if let Some(recognizer) = self.ivars().doubletap_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer);
}
}
pub(crate) fn recognize_rotation_gesture(&self, should_recognize: bool) {
if should_recognize {
if self.ivars().rotation_gesture_recognizer.borrow().is_none() {
let rotation: Id<UIRotationGestureRecognizer> = unsafe {
msg_send_id![UIRotationGestureRecognizer::alloc(), initWithTarget: self, action: sel!(rotationGesture:)]
};
self.addGestureRecognizer(&rotation);
self.ivars()
.rotation_gesture_recognizer
.replace(Some(rotation));
}
} else if let Some(recognizer) = self.ivars().rotation_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer);
}
}
fn handle_touches(&self, touches: &NSSet<UITouch>) { fn handle_touches(&self, touches: &NSSet<UITouch>) {
let window = self.window().unwrap(); let window = self.window().unwrap();
let uiscreen = window.screen();
let mut touch_events = Vec::new(); let mut touch_events = Vec::new();
let os_supports_force = app_state::os_capabilities().force_touch; let os_supports_force = app_state::os_capabilities().force_touch;
for touch in touches { for touch in touches {
@@ -382,7 +250,9 @@ impl WinitView {
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()), window_id: RootWindowId(window.id()),
event: WindowEvent::Touch(Touch { event: WindowEvent::Touch(Touch {
device_id: DEVICE_ID, device_id: RootDeviceId(DeviceId {
uiscreen: Id::as_ptr(&uiscreen),
}),
id: touch_id, id: touch_id,
location: physical_location, location: physical_location,
force, force,
@@ -404,7 +274,11 @@ pub struct ViewControllerState {
} }
declare_class!( declare_class!(
pub(crate) struct WinitViewController; pub(crate) struct WinitViewController {
state: IvarDrop<Box<ViewControllerState>, "_state">,
}
mod view_controller_ivars;
unsafe impl ClassType for WinitViewController { unsafe impl ClassType for WinitViewController {
#[inherits(UIResponder, NSObject)] #[inherits(UIResponder, NSObject)]
@@ -413,8 +287,28 @@ declare_class!(
const NAME: &'static str = "WinitUIViewController"; const NAME: &'static str = "WinitUIViewController";
} }
impl DeclaredClass for WinitViewController { unsafe impl WinitViewController {
type Ivars = ViewControllerState; #[method(init)]
unsafe fn init(this: *mut Self) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = msg_send![super(this), init];
this.map(|this| {
// These are set in WinitViewController::new, it's just to set them
// to _something_.
Ivar::write(
&mut this.state,
Box::new(ViewControllerState {
prefers_status_bar_hidden: Cell::new(false),
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
prefers_home_indicator_auto_hidden: Cell::new(false),
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(
UIRectEdge::NONE,
),
}),
);
NonNull::from(this)
})
}
} }
unsafe impl WinitViewController { unsafe impl WinitViewController {
@@ -425,27 +319,27 @@ declare_class!(
#[method(prefersStatusBarHidden)] #[method(prefersStatusBarHidden)]
fn prefers_status_bar_hidden(&self) -> bool { fn prefers_status_bar_hidden(&self) -> bool {
self.ivars().prefers_status_bar_hidden.get() self.state.prefers_status_bar_hidden.get()
} }
#[method(preferredStatusBarStyle)] #[method(preferredStatusBarStyle)]
fn preferred_status_bar_style(&self) -> UIStatusBarStyle { fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
self.ivars().preferred_status_bar_style.get() self.state.preferred_status_bar_style.get()
} }
#[method(prefersHomeIndicatorAutoHidden)] #[method(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool { fn prefers_home_indicator_auto_hidden(&self) -> bool {
self.ivars().prefers_home_indicator_auto_hidden.get() self.state.prefers_home_indicator_auto_hidden.get()
} }
#[method(supportedInterfaceOrientations)] #[method(supportedInterfaceOrientations)]
fn supported_orientations(&self) -> UIInterfaceOrientationMask { fn supported_orientations(&self) -> UIInterfaceOrientationMask {
self.ivars().supported_orientations.get() self.state.supported_orientations.get()
} }
#[method(preferredScreenEdgesDeferringSystemGestures)] #[method(preferredScreenEdgesDeferringSystemGestures)]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge { fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
self.ivars() self.state
.preferred_screen_edges_deferring_system_gestures .preferred_screen_edges_deferring_system_gestures
.get() .get()
} }
@@ -454,17 +348,17 @@ declare_class!(
impl WinitViewController { impl WinitViewController {
pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) { pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) {
self.ivars().prefers_status_bar_hidden.set(val); self.state.prefers_status_bar_hidden.set(val);
self.setNeedsStatusBarAppearanceUpdate(); self.setNeedsStatusBarAppearanceUpdate();
} }
pub(crate) fn set_preferred_status_bar_style(&self, val: UIStatusBarStyle) { pub(crate) fn set_preferred_status_bar_style(&self, val: UIStatusBarStyle) {
self.ivars().preferred_status_bar_style.set(val); self.state.preferred_status_bar_style.set(val);
self.setNeedsStatusBarAppearanceUpdate(); self.setNeedsStatusBarAppearanceUpdate();
} }
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) { pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.ivars().prefers_home_indicator_auto_hidden.set(val); self.state.prefers_home_indicator_auto_hidden.set(val);
let os_capabilities = app_state::os_capabilities(); let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden { if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden(); self.setNeedsUpdateOfHomeIndicatorAutoHidden();
@@ -474,7 +368,7 @@ impl WinitViewController {
} }
pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) { pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) {
self.ivars() self.state
.preferred_screen_edges_deferring_system_gestures .preferred_screen_edges_deferring_system_gestures
.set(val); .set(val);
let os_capabilities = app_state::os_capabilities(); let os_capabilities = app_state::os_capabilities();
@@ -507,52 +401,30 @@ impl WinitViewController {
| UIInterfaceOrientationMask::PortraitUpsideDown | UIInterfaceOrientationMask::PortraitUpsideDown
} }
}; };
self.ivars().supported_orientations.set(mask); self.state.supported_orientations.set(mask);
UIViewController::attemptRotationToDeviceOrientation(); UIViewController::attemptRotationToDeviceOrientation();
} }
pub(crate) fn new( pub(crate) fn new(
mtm: MainThreadMarker, mtm: MainThreadMarker,
window_attributes: &WindowAttributes, _window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: &UIView, view: &UIView,
) -> Id<Self> { ) -> Id<Self> {
// These are set properly below, we just to set them to something in the meantime. let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), init] };
let this = Self::alloc().set_ivars(ViewControllerState {
prefers_status_bar_hidden: Cell::new(false),
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
prefers_home_indicator_auto_hidden: Cell::new(false),
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::NONE),
});
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
this.set_prefers_status_bar_hidden( this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
window_attributes
.platform_specific
.prefers_status_bar_hidden,
);
this.set_preferred_status_bar_style( this.set_preferred_status_bar_style(platform_attributes.preferred_status_bar_style.into());
window_attributes
.platform_specific
.preferred_status_bar_style
.into(),
);
this.set_supported_interface_orientations( this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations);
mtm,
window_attributes.platform_specific.valid_orientations,
);
this.set_prefers_home_indicator_auto_hidden( this.set_prefers_home_indicator_auto_hidden(
window_attributes platform_attributes.prefers_home_indicator_hidden,
.platform_specific
.prefers_home_indicator_hidden,
); );
this.set_preferred_screen_edges_deferring_system_gestures( this.set_preferred_screen_edges_deferring_system_gestures(
window_attributes platform_attributes
.platform_specific
.preferred_screen_edges_deferring_system_gestures .preferred_screen_edges_deferring_system_gestures
.into(), .into(),
); );
@@ -574,8 +446,6 @@ declare_class!(
const NAME: &'static str = "WinitUIWindow"; const NAME: &'static str = "WinitUIWindow";
} }
impl DeclaredClass for WinitUIWindow {}
unsafe impl WinitUIWindow { unsafe impl WinitUIWindow {
#[method(becomeKeyWindow)] #[method(becomeKeyWindow)]
fn become_key_window(&self) { fn become_key_window(&self) {
@@ -609,6 +479,7 @@ impl WinitUIWindow {
pub(crate) fn new( pub(crate) fn new(
mtm: MainThreadMarker, mtm: MainThreadMarker,
window_attributes: &WindowAttributes, window_attributes: &WindowAttributes,
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect, frame: CGRect,
view_controller: &UIViewController, view_controller: &UIViewController,
) -> Id<Self> { ) -> Id<Self> {
@@ -616,7 +487,7 @@ impl WinitUIWindow {
this.setRootViewController(Some(view_controller)); this.setRootViewController(Some(view_controller));
match window_attributes.fullscreen.clone().map(Into::into) { match window_attributes.fullscreen.0.clone().map(Into::into) {
Some(Fullscreen::Exclusive(ref video_mode)) => { Some(Fullscreen::Exclusive(ref video_mode)) => {
let monitor = video_mode.monitor(); let monitor = video_mode.monitor();
let screen = monitor.ui_screen(mtm); let screen = monitor.ui_screen(mtm);
@@ -647,8 +518,6 @@ declare_class!(
const NAME: &'static str = "WinitApplicationDelegate"; const NAME: &'static str = "WinitApplicationDelegate";
} }
impl DeclaredClass for WinitApplicationDelegate {}
// UIApplicationDelegate protocol // UIApplicationDelegate protocol
unsafe impl WinitApplicationDelegate { unsafe impl WinitApplicationDelegate {
#[method(application:didFinishLaunchingWithOptions:)] #[method(application:didFinishLaunchingWithOptions:)]

View File

@@ -3,7 +3,6 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker}; use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker};
use log::{debug, warn};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::AnyObject; use objc2::runtime::AnyObject;
use objc2::{class, msg_send}; use objc2::{class, msg_send};
@@ -12,7 +11,7 @@ use super::app_state::EventWrapper;
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation}; use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController}; use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{ use crate::{
cursor::Cursor, cursor::CustomCursor,
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent}, event::{Event, WindowEvent},
@@ -22,8 +21,8 @@ use crate::{
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
}, },
window::{ window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowButtons, WindowId as RootWindowId, WindowLevel, WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
}, },
}; };
@@ -175,8 +174,12 @@ impl Inner {
self.view.contentScaleFactor() as _ self.view.contentScaleFactor() as _
} }
pub fn set_cursor(&self, _cursor: Cursor) { pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
debug!("`Window::set_cursor` ignored on iOS") debug!("`Window::set_cursor_icon` ignored on iOS")
}
pub fn set_custom_cursor(&self, _: CustomCursor) {
debug!("`Window::set_custom_cursor` ignored on iOS")
} }
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> { pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
@@ -357,14 +360,23 @@ impl Inner {
} }
#[cfg(feature = "rwh_06")] #[cfg(feature = "rwh_06")]
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle { pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
let mut window_handle = rwh_06::UiKitWindowHandle::new({ let mut window_handle = rwh_06::UiKitWindowHandle::new({
let ui_view = Id::as_ptr(&self.view) as _; let ui_view = Id::as_ptr(&self.view) as _;
std::ptr::NonNull::new(ui_view).expect("Id<T> should never be null") std::ptr::NonNull::new(ui_view).expect("Id<T> should never be null")
}); });
window_handle.ui_view_controller = window_handle.ui_view_controller =
std::ptr::NonNull::new(Id::as_ptr(&self.view_controller) as _); std::ptr::NonNull::new(Id::as_ptr(&self.view_controller) as _);
rwh_06::RawWindowHandle::UiKit(window_handle) Ok(rwh_06::RawWindowHandle::UiKit(window_handle))
}
#[cfg(feature = "rwh_06")]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
} }
pub fn theme(&self) -> Option<Theme> { pub fn theme(&self) -> Option<Theme> {
@@ -398,9 +410,10 @@ pub struct Window {
} }
impl Window { impl Window {
pub(crate) fn new( pub(crate) fn new<T>(
event_loop: &EventLoopWindowTarget, event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes, window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> { ) -> Result<Window, RootOsError> {
let mtm = event_loop.mtm; let mtm = event_loop.mtm;
@@ -414,7 +427,7 @@ impl Window {
// TODO: transparency, visible // TODO: transparency, visible
let main_screen = UIScreen::main(mtm); let main_screen = UIScreen::main(mtm);
let fullscreen = window_attributes.fullscreen.clone().map(Into::into); let fullscreen = window_attributes.fullscreen.0.clone().map(Into::into);
let screen = match fullscreen { let screen = match fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm), Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm), Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
@@ -438,7 +451,7 @@ impl Window {
None => screen_bounds, None => screen_bounds,
}; };
let view = WinitView::new(mtm, &window_attributes, frame); let view = WinitView::new(mtm, &window_attributes, &platform_attributes, frame);
let gl_or_metal_backed = unsafe { let gl_or_metal_backed = unsafe {
let layer_class = WinitView::layerClass(); let layer_class = WinitView::layerClass();
@@ -447,8 +460,15 @@ impl Window {
is_metal || is_gl is_metal || is_gl
}; };
let view_controller = WinitViewController::new(mtm, &window_attributes, &view); let view_controller =
let window = WinitUIWindow::new(mtm, &window_attributes, frame, &view_controller); WinitViewController::new(mtm, &window_attributes, &platform_attributes, &view);
let window = WinitUIWindow::new(
mtm,
&window_attributes,
&platform_attributes,
frame,
&view_controller,
);
app_state::set_key_window(mtm, &window); app_state::set_key_window(mtm, &window);
@@ -501,29 +521,7 @@ impl Window {
} }
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R { pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
self.inner.get_on_main(|inner| f(inner)) self.inner.get_on_main(|inner, _mtm| f(inner))
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_window_handle_rwh_06(
&self,
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
if let Some(mtm) = MainThreadMarker::new() {
Ok(self.inner.get(mtm).raw_window_handle_rwh_06())
} else {
Err(rwh_06::HandleError::Unavailable)
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
} }
} }
@@ -563,18 +561,6 @@ impl Inner {
self.view_controller self.view_controller
.set_preferred_status_bar_style(status_bar_style.into()); .set_preferred_status_bar_style(status_bar_style.into());
} }
pub fn recognize_pinch_gesture(&self, should_recognize: bool) {
self.view.recognize_pinch_gesture(should_recognize);
}
pub fn recognize_doubletap_gesture(&self, should_recognize: bool) {
self.view.recognize_doubletap_gesture(should_recognize);
}
pub fn recognize_rotation_gesture(&self, should_recognize: bool) {
self.view.recognize_rotation_gesture(should_recognize);
}
} }
impl Inner { impl Inner {
@@ -677,7 +663,7 @@ impl From<&AnyObject> for WindowId {
} }
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes { pub struct PlatformSpecificWindowBuilderAttributes {
pub scale_factor: Option<f64>, pub scale_factor: Option<f64>,
pub valid_orientations: ValidOrientations, pub valid_orientations: ValidOrientations,

View File

@@ -6,13 +6,13 @@ use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyC
/// ///
/// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses. /// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses.
pub fn raw_keycode_to_physicalkey(keycode: u32) -> PhysicalKey { pub fn raw_keycode_to_physicalkey(keycode: u32) -> PhysicalKey {
scancode_to_physicalkey(keycode.saturating_sub(8)) scancode_to_keycode(keycode.saturating_sub(8))
} }
/// Map the linux scancode to Keycode. /// Map the linux scancode to Keycode.
/// ///
/// Both X11 and Wayland use keys with `+ 8` offset to linux scancode. /// Both X11 and Wayland use keys with `+ 8` offset to linux scancode.
pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { pub fn scancode_to_keycode(scancode: u32) -> PhysicalKey {
// The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as // The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as
// libxkbcommon's documentation seems to suggest that the keycode values we're interested in // libxkbcommon's documentation seems to suggest that the keycode values we're interested in
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes, // are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
@@ -504,7 +504,7 @@ pub fn keysym_to_key(keysym: u32) -> Key {
keysyms::KP_F4 => NamedKey::F4, keysyms::KP_F4 => NamedKey::F4,
keysyms::KP_Home => NamedKey::Home, keysyms::KP_Home => NamedKey::Home,
keysyms::KP_Left => NamedKey::ArrowLeft, keysyms::KP_Left => NamedKey::ArrowLeft,
keysyms::KP_Up => NamedKey::ArrowUp, keysyms::KP_Up => NamedKey::ArrowLeft,
keysyms::KP_Right => NamedKey::ArrowRight, keysyms::KP_Right => NamedKey::ArrowRight,
keysyms::KP_Down => NamedKey::ArrowDown, keysyms::KP_Down => NamedKey::ArrowDown,
// keysyms::KP_Prior => NamedKey::PageUp, // keysyms::KP_Prior => NamedKey::PageUp,

View File

@@ -398,8 +398,8 @@ impl KbdState {
let text_with_all_modifiers = event.text_with_all_modifiers(); let text_with_all_modifiers = event.text_with_all_modifiers();
let platform_specific = KeyEventExtra { let platform_specific = KeyEventExtra {
text_with_all_modifiers,
key_without_modifiers, key_without_modifiers,
text_with_all_modifiers,
}; };
KeyEvent { KeyEvent {
@@ -498,7 +498,7 @@ impl<'a> KeyEventResults<'a> {
} }
} }
fn physical_key(&self) -> PhysicalKey { fn physical_key(&mut self) -> PhysicalKey {
keymap::raw_keycode_to_physicalkey(self.keycode) keymap::raw_keycode_to_physicalkey(self.keycode)
} }
@@ -553,7 +553,6 @@ impl<'a> KeyEventResults<'a> {
} else { } else {
0 0
}; };
self.keysym_to_key(keysym) self.keysym_to_key(keysym)
.unwrap_or_else(|(key, location)| { .unwrap_or_else(|(key, location)| {
( (
@@ -566,7 +565,7 @@ impl<'a> KeyEventResults<'a> {
}) })
} }
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> { fn keysym_to_key(&mut self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
let location = super::keymap::keysym_location(keysym); let location = super::keymap::keysym_location(keysym);
let key = super::keymap::keysym_to_key(keysym); let key = super::keymap::keysym_to_key(keysym);
if matches!(key, Key::Unidentified(_)) { if matches!(key, Key::Unidentified(_)) {
@@ -683,7 +682,7 @@ fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
std::str::from_utf8(bytes) std::str::from_utf8(bytes)
.map(SmolStr::new) .map(SmolStr::new)
.map_err(|e| { .map_err(|e| {
log::warn!( warn!(
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}", "UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
bytes bytes
) )

View File

@@ -14,36 +14,42 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use smol_str::SmolStr; use smol_str::SmolStr;
use crate::cursor::CustomCursor;
#[cfg(x11_platform)] #[cfg(x11_platform)]
use self::x11::{X11Error, XConnection, XError, XNotSupported}; use crate::platform::x11::XlibErrorHook;
#[cfg(x11_platform)]
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size}, dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError}, error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError},
event::KeyEvent,
event_loop::{ event_loop::{
AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed, AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
EventLoopWindowTarget as RootELW, EventLoopWindowTarget as RootELW,
}, },
icon::Icon, icon::Icon,
keyboard::Key, keyboard::{Key, PhysicalKey},
platform::pump_events::PumpStatus, platform::{
modifier_supplement::KeyEventExtModifierSupplement, pump_events::PumpStatus,
scancode::PhysicalKeyExtScancode,
},
window::{ window::{
ActivationToken, Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, ActivationToken, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme,
UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel,
}, },
}; };
#[cfg(x11_platform)]
pub use x11::XNotSupported;
#[cfg(x11_platform)]
use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError};
pub(crate) use self::common::keymap::{physicalkey_to_scancode, scancode_to_physicalkey}; pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::RgbaIcon as PlatformIcon; pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use crate::platform_impl::Fullscreen;
pub(crate) mod common; pub mod common;
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
pub(crate) mod wayland; pub mod wayland;
#[cfg(x11_platform)] #[cfg(x11_platform)]
pub(crate) mod x11; pub mod x11;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum Backend { pub(crate) enum Backend {
@@ -71,7 +77,7 @@ impl ApplicationName {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes { pub struct PlatformSpecificWindowBuilderAttributes {
pub name: Option<ApplicationName>, pub name: Option<ApplicationName>,
pub activation_token: Option<ActivationToken>, pub activation_token: Option<ActivationToken>,
@@ -79,7 +85,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub x11: X11WindowBuilderAttributes, pub x11: X11WindowBuilderAttributes,
} }
#[derive(Clone, Debug)] #[derive(Clone)]
#[cfg(x11_platform)] #[cfg(x11_platform)]
pub struct X11WindowBuilderAttributes { pub struct X11WindowBuilderAttributes {
pub visual_id: Option<x11rb::protocol::xproto::Visualid>, pub visual_id: Option<x11rb::protocol::xproto::Visualid>,
@@ -248,55 +254,56 @@ impl MonitorHandle {
} }
#[inline] #[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> { pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes())) x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum VideoModeHandle { pub enum VideoMode {
#[cfg(x11_platform)] #[cfg(x11_platform)]
X(x11::VideoModeHandle), X(x11::VideoMode),
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
Wayland(wayland::VideoModeHandle), Wayland(wayland::VideoMode),
} }
impl VideoModeHandle { impl VideoMode {
#[inline] #[inline]
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
x11_or_wayland!(match self; VideoModeHandle(m) => m.size()) x11_or_wayland!(match self; VideoMode(m) => m.size())
} }
#[inline] #[inline]
pub fn bit_depth(&self) -> u16 { pub fn bit_depth(&self) -> u16 {
x11_or_wayland!(match self; VideoModeHandle(m) => m.bit_depth()) x11_or_wayland!(match self; VideoMode(m) => m.bit_depth())
} }
#[inline] #[inline]
pub fn refresh_rate_millihertz(&self) -> u32 { pub fn refresh_rate_millihertz(&self) -> u32 {
x11_or_wayland!(match self; VideoModeHandle(m) => m.refresh_rate_millihertz()) x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate_millihertz())
} }
#[inline] #[inline]
pub fn monitor(&self) -> MonitorHandle { pub fn monitor(&self) -> MonitorHandle {
x11_or_wayland!(match self; VideoModeHandle(m) => m.monitor(); as MonitorHandle) x11_or_wayland!(match self; VideoMode(m) => m.monitor(); as MonitorHandle)
} }
} }
impl Window { impl Window {
#[inline] #[inline]
pub(crate) fn new( pub(crate) fn new<T>(
window_target: &EventLoopWindowTarget, window_target: &EventLoopWindowTarget<T>,
attribs: WindowAttributes, attribs: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> { ) -> Result<Self, RootOsError> {
match *window_target { match *window_target {
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(ref window_target) => { EventLoopWindowTarget::Wayland(ref window_target) => {
wayland::Window::new(window_target, attribs).map(Window::Wayland) wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland)
} }
#[cfg(x11_platform)] #[cfg(x11_platform)]
EventLoopWindowTarget::X(ref window_target) => { EventLoopWindowTarget::X(ref window_target) => {
x11::Window::new(window_target, attribs).map(Window::X) x11::Window::new(window_target, attribs, pl_attribs).map(Window::X)
} }
} }
} }
@@ -415,8 +422,13 @@ impl Window {
} }
#[inline] #[inline]
pub fn set_cursor(&self, cursor: Cursor) { pub fn set_cursor_icon(&self, cursor: CursorIcon) {
x11_or_wayland!(match self; Window(w) => w.set_cursor(cursor)) x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
}
#[inline]
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
x11_or_wayland!(match self; Window(w) => w.set_custom_cursor(cursor))
} }
#[inline] #[inline]
@@ -633,30 +645,32 @@ impl Window {
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra { pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>,
pub key_without_modifiers: Key, pub key_without_modifiers: Key,
pub text_with_all_modifiers: Option<SmolStr>,
} }
#[derive(Clone, Debug, Eq, Hash, PartialEq)] impl KeyEventExtModifierSupplement for KeyEvent {
pub(crate) enum PlatformCustomCursor { #[inline]
#[cfg(wayland_platform)] fn text_with_all_modifiers(&self) -> Option<&str> {
Wayland(wayland::CustomCursor), self.platform_specific
#[cfg(x11_platform)] .text_with_all_modifiers
X(x11::CustomCursor), .as_ref()
.map(|s| s.as_str())
}
#[inline]
fn key_without_modifiers(&self) -> Key {
self.platform_specific.key_without_modifiers.clone()
}
} }
impl PlatformCustomCursor {
pub(crate) fn build( impl PhysicalKeyExtScancode for PhysicalKey {
builder: PlatformCustomCursorBuilder, fn from_scancode(scancode: u32) -> PhysicalKey {
p: &EventLoopWindowTarget, common::keymap::scancode_to_keycode(scancode)
) -> PlatformCustomCursor { }
match p {
#[cfg(wayland_platform)] fn to_scancode(self) -> Option<u32> {
EventLoopWindowTarget::Wayland(_) => { common::keymap::physicalkey_to_scancode(self)
Self::Wayland(wayland::CustomCursor::build(builder, p))
}
#[cfg(x11_platform)]
EventLoopWindowTarget::X(p) => Self::X(x11::CustomCursor::build(builder, p)),
}
} }
} }
@@ -703,7 +717,7 @@ unsafe extern "C" fn x_error_callback(
// Don't log error. // Don't log error.
if !error_handled { if !error_handled {
log::error!("X11 error: {:#?}", error); error!("X11 error: {:#?}", error);
// XXX only update the error, if it wasn't handled by any of the hooks. // XXX only update the error, if it wasn't handled by any of the hooks.
*xconn.latest_error.lock().unwrap() = Some(error); *xconn.latest_error.lock().unwrap() = Some(error);
} }
@@ -750,11 +764,8 @@ impl<T: 'static> EventLoop<T> {
let backend = match ( let backend = match (
attributes.forced_backend, attributes.forced_backend,
env::var("WAYLAND_DISPLAY") env::var("WAYLAND_DISPLAY")
.ok() .map(|var| !var.is_empty())
.filter(|var| !var.is_empty()) .unwrap_or(false),
.or_else(|| env::var("WAYLAND_SOCKET").ok())
.filter(|var| !var.is_empty())
.is_some(),
env::var("DISPLAY") env::var("DISPLAY")
.map(|var| !var.is_empty()) .map(|var| !var.is_empty())
.unwrap_or(false), .unwrap_or(false),
@@ -768,15 +779,10 @@ impl<T: 'static> EventLoop<T> {
#[cfg(x11_platform)] #[cfg(x11_platform)]
(None, _, true) => Backend::X, (None, _, true) => Backend::X,
// No backend is present. // No backend is present.
(_, wayland_display, x11_display) => { _ => {
let msg = if wayland_display && !cfg!(wayland_platform) { return Err(EventLoopError::Os(os_error!(OsError::Misc(
"DISPLAY is not set; note: enable the `winit/wayland` feature to support Wayland" "neither WAYLAND_DISPLAY nor DISPLAY is set."
} else if x11_display && !cfg!(x11_platform) { ))));
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the `winit/x11` feature to support X11"
} else {
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
};
return Err(EventLoopError::Os(os_error!(OsError::Misc(msg))));
} }
}; };
@@ -785,7 +791,7 @@ impl<T: 'static> EventLoop<T> {
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into), Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
#[cfg(x11_platform)] #[cfg(x11_platform)]
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into), Backend::X => Ok(EventLoop::new_x11_any_thread().unwrap()),
} }
} }
@@ -795,10 +801,10 @@ impl<T: 'static> EventLoop<T> {
} }
#[cfg(x11_platform)] #[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> { fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
let xconn = match X11_BACKEND.lock().unwrap().as_ref() { let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
Ok(xconn) => xconn.clone(), Ok(xconn) => xconn.clone(),
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())), Err(err) => return Err(err.clone()),
}; };
Ok(EventLoop::X(x11::EventLoop::new(xconn))) Ok(EventLoop::X(x11::EventLoop::new(xconn)))
@@ -810,26 +816,26 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError> pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
where where
F: FnMut(crate::event::Event<T>, &RootELW), F: FnMut(crate::event::Event<T>, &RootELW<T>),
{ {
self.run_on_demand(callback) self.run_on_demand(callback)
} }
pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError> pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
where where
F: FnMut(crate::event::Event<T>, &RootELW), F: FnMut(crate::event::Event<T>, &RootELW<T>),
{ {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback)) x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback))
} }
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
where where
F: FnMut(crate::event::Event<T>, &RootELW), F: FnMut(crate::event::Event<T>, &RootELW<T>),
{ {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback)) x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
} }
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget { pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target()) x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target())
} }
} }
@@ -852,14 +858,14 @@ impl<T: 'static> EventLoopProxy<T> {
} }
} }
pub enum EventLoopWindowTarget { pub enum EventLoopWindowTarget<T> {
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
Wayland(wayland::EventLoopWindowTarget), Wayland(wayland::EventLoopWindowTarget<T>),
#[cfg(x11_platform)] #[cfg(x11_platform)]
X(x11::EventLoopWindowTarget), X(x11::EventLoopWindowTarget<T>),
} }
impl EventLoopWindowTarget { impl<T> EventLoopWindowTarget<T> {
#[inline] #[inline]
pub fn is_wayland(&self) -> bool { pub fn is_wayland(&self) -> bool {
match *self { match *self {
@@ -919,10 +925,6 @@ impl EventLoopWindowTarget {
x11_or_wayland!(match self; Self(evlp) => evlp.control_flow()) x11_or_wayland!(match self; Self(evlp) => evlp.control_flow())
} }
pub(crate) fn clear_exit(&self) {
x11_or_wayland!(match self; Self(evlp) => evlp.clear_exit())
}
pub(crate) fn exit(&self) { pub(crate) fn exit(&self) {
x11_or_wayland!(match self; Self(evlp) => evlp.exit()) x11_or_wayland!(match self; Self(evlp) => evlp.exit())
} }
@@ -931,87 +933,15 @@ impl EventLoopWindowTarget {
x11_or_wayland!(match self; Self(evlp) => evlp.exiting()) x11_or_wayland!(match self; Self(evlp) => evlp.exiting())
} }
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
match self {
#[cfg(x11_platform)]
Self::X(conn) => OwnedDisplayHandle::X(conn.x_connection().clone()),
#[cfg(wayland_platform)]
Self::Wayland(conn) => OwnedDisplayHandle::Wayland(conn.connection.clone()),
}
}
#[allow(dead_code)]
fn set_exit_code(&self, code: i32) { fn set_exit_code(&self, code: i32) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code)) x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code))
} }
#[allow(dead_code)]
fn exit_code(&self) -> Option<i32> { fn exit_code(&self) -> Option<i32> {
x11_or_wayland!(match self; Self(evlp) => evlp.exit_code()) x11_or_wayland!(match self; Self(evlp) => evlp.exit_code())
} }
} }
#[derive(Clone)]
#[allow(dead_code)]
pub(crate) enum OwnedDisplayHandle {
#[cfg(x11_platform)]
X(Arc<XConnection>),
#[cfg(wayland_platform)]
Wayland(wayland_client::Connection),
}
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
match self {
#[cfg(x11_platform)]
Self::X(xconn) => {
let mut xlib_handle = rwh_05::XlibDisplayHandle::empty();
xlib_handle.display = xconn.display.cast();
xlib_handle.screen = xconn.default_screen_index() as _;
xlib_handle.into()
}
#[cfg(wayland_platform)]
Self::Wayland(conn) => {
use sctk::reexports::client::Proxy;
let mut wayland_handle = rwh_05::WaylandDisplayHandle::empty();
wayland_handle.display = conn.display().id().as_ptr() as *mut _;
wayland_handle.into()
}
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
use std::ptr::NonNull;
match self {
#[cfg(x11_platform)]
Self::X(xconn) => Ok(rwh_06::XlibDisplayHandle::new(
NonNull::new(xconn.display.cast()),
xconn.default_screen_index() as _,
)
.into()),
#[cfg(wayland_platform)]
Self::Wayland(conn) => {
use sctk::reexports::client::Proxy;
Ok(rwh_06::WaylandDisplayHandle::new(
NonNull::new(conn.display().id().as_ptr().cast()).unwrap(),
)
.into())
}
}
}
}
/// Returns the minimum `Option<Duration>`, taking into account that `None` /// Returns the minimum `Option<Duration>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use /// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`) /// `Option::min`)

View File

@@ -16,7 +16,7 @@ use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::globals; use sctk::reexports::client::globals;
use sctk::reexports::client::{Connection, QueueHandle}; use sctk::reexports::client::{Connection, QueueHandle};
use crate::dpi::LogicalSize; use crate::dpi::{LogicalSize, PhysicalSize};
use crate::error::{EventLoopError, OsError as RootOsError}; use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent}; use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{ use crate::event_loop::{
@@ -34,7 +34,7 @@ use sink::EventSink;
use super::state::{WindowCompositorUpdate, WinitState}; use super::state::{WindowCompositorUpdate, WinitState};
use super::window::state::FrameCallbackState; use super::window::state::FrameCallbackState;
use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId}; use super::{DeviceId, WaylandError, WindowId};
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>; type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
@@ -63,7 +63,7 @@ pub struct EventLoop<T: 'static> {
connection: Connection, connection: Connection,
/// Event loop window target. /// Event loop window target.
window_target: RootEventLoopWindowTarget, window_target: RootEventLoopWindowTarget<T>,
// XXX drop after everything else, just to be safe. // XXX drop after everything else, just to be safe.
/// Calloop's event loop. /// Calloop's event loop.
@@ -167,6 +167,7 @@ impl<T: 'static> EventLoop<T> {
control_flow: Cell::new(ControlFlow::default()), control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(None), exit: Cell::new(None),
state: RefCell::new(winit_state), state: RefCell::new(winit_state),
_marker: PhantomData,
}; };
let event_loop = Self { let event_loop = Self {
@@ -190,8 +191,12 @@ impl<T: 'static> EventLoop<T> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError> pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(Event<T>, &RootEventLoopWindowTarget), F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{ {
if self.loop_running {
return Err(EventLoopError::AlreadyRunning);
}
let exit = loop { let exit = loop {
match self.pump_events(None, &mut event_handler) { match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => { PumpStatus::Exit(0) => {
@@ -217,7 +222,7 @@ impl<T: 'static> EventLoop<T> {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where where
F: FnMut(Event<T>, &RootEventLoopWindowTarget), F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{ {
if !self.loop_running { if !self.loop_running {
self.loop_running = true; self.loop_running = true;
@@ -244,7 +249,7 @@ impl<T: 'static> EventLoop<T> {
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F) pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where where
F: FnMut(Event<T>, &RootEventLoopWindowTarget), F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{ {
let cause = loop { let cause = loop {
let start = Instant::now(); let start = Instant::now();
@@ -320,7 +325,7 @@ impl<T: 'static> EventLoop<T> {
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause) fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where where
F: FnMut(Event<T>, &RootEventLoopWindowTarget), F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{ {
// NOTE currently just indented to simplify the diff // NOTE currently just indented to simplify the diff
@@ -351,13 +356,15 @@ impl<T: 'static> EventLoop<T> {
for mut compositor_update in compositor_updates.drain(..) { for mut compositor_update in compositor_updates.drain(..) {
let window_id = compositor_update.window_id; let window_id = compositor_update.window_id;
if compositor_update.scale_changed { if let Some(scale_factor) = compositor_update.scale_factor {
let (physical_size, scale_factor) = self.with_state(|state| { let physical_size = self.with_state(|state| {
let windows = state.windows.get_mut(); let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap(); let mut window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor();
let size = logical_to_physical_rounded(window.inner_size(), scale_factor); // Set the new scale factor.
(size, scale_factor) window.set_scale_factor(scale_factor);
let window_size = compositor_update.size.unwrap_or(window.inner_size());
logical_to_physical_rounded(window_size, scale_factor)
}); });
// Stash the old window size. // Stash the old window size.
@@ -379,32 +386,30 @@ impl<T: 'static> EventLoop<T> {
let physical_size = *new_inner_size.lock().unwrap(); let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size); drop(new_inner_size);
let new_logical_size = physical_size.to_logical(scale_factor);
// Resize the window when user altered the size. // Resize the window when user altered the size.
if old_physical_size != physical_size { if old_physical_size != physical_size {
self.with_state(|state| { self.with_state(|state| {
let windows = state.windows.get_mut(); let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap(); let mut window = windows.get(&window_id).unwrap().lock().unwrap();
let new_logical_size: LogicalSize<f64> =
physical_size.to_logical(scale_factor);
window.request_inner_size(new_logical_size.into()); window.request_inner_size(new_logical_size.into());
}); });
// Make it queue resize.
compositor_update.resized = true;
} }
// Make it queue resize.
compositor_update.size = Some(new_logical_size);
} }
// NOTE: Rescale changed the physical size which winit operates in, thus we should if let Some(size) = compositor_update.size.take() {
// resize.
if compositor_update.resized || compositor_update.scale_changed {
let physical_size = self.with_state(|state| { let physical_size = self.with_state(|state| {
let windows = state.windows.get_mut(); let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap(); let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor(); let scale_factor = window.scale_factor();
let size = logical_to_physical_rounded(window.inner_size(), scale_factor); let physical_size = logical_to_physical_rounded(size, scale_factor);
// TODO could probably bring back size reporting optimization.
// Mark the window as needed a redraw. // Mark the window as needed a redraw.
state state
@@ -415,7 +420,7 @@ impl<T: 'static> EventLoop<T> {
.redraw_requested .redraw_requested
.store(true, Ordering::Relaxed); .store(true, Ordering::Relaxed);
size physical_size
}); });
callback( callback(
@@ -461,45 +466,45 @@ impl<T: 'static> EventLoop<T> {
window_ids.extend(state.window_requests.get_mut().keys()); window_ids.extend(state.window_requests.get_mut().keys());
}); });
for window_id in window_ids.iter() { for window_id in window_ids.drain(..) {
let event = self.with_state(|state| { let request_redraw = self.with_state(|state| {
let window_requests = state.window_requests.get_mut(); let window_requests = state.window_requests.get_mut();
if window_requests.get(window_id).unwrap().take_closed() { if window_requests.get(&window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(window_id)); mem::drop(window_requests.remove(&window_id));
mem::drop(state.windows.get_mut().remove(window_id)); mem::drop(state.windows.get_mut().remove(&window_id));
return Some(WindowEvent::Destroyed); false
} else {
let mut window = state
.windows
.get_mut()
.get_mut(&window_id)
.unwrap()
.lock()
.unwrap();
if window.frame_callback_state() == FrameCallbackState::Requested {
false
} else {
// Reset the frame callbacks state.
window.frame_callback_reset();
let mut redraw_requested = window_requests
.get(&window_id)
.unwrap()
.take_redraw_requested();
// Redraw the frame while at it.
redraw_requested |= window.refresh_frame();
redraw_requested
}
} }
let mut window = state
.windows
.get_mut()
.get_mut(window_id)
.unwrap()
.lock()
.unwrap();
if window.frame_callback_state() == FrameCallbackState::Requested {
return None;
}
// Reset the frame callbacks state.
window.frame_callback_reset();
let mut redraw_requested = window_requests
.get(window_id)
.unwrap()
.take_redraw_requested();
// Redraw the frame while at it.
redraw_requested |= window.refresh_frame();
redraw_requested.then_some(WindowEvent::RedrawRequested)
}); });
if let Some(event) = event { if request_redraw {
callback( callback(
Event::WindowEvent { Event::WindowEvent {
window_id: crate::window::WindowId(*window_id), window_id: crate::window::WindowId(window_id),
event, event: WindowEvent::RedrawRequested,
}, },
&self.window_target, &self.window_target,
); );
@@ -514,42 +519,6 @@ impl<T: 'static> EventLoop<T> {
// This is always the last event we dispatch before poll again // This is always the last event we dispatch before poll again
callback(Event::AboutToWait, &self.window_target); callback(Event::AboutToWait, &self.window_target);
// Update the window frames and schedule redraws.
let mut wake_up = false;
for window_id in window_ids.drain(..) {
wake_up |= self.with_state(|state| match state.windows.get_mut().get_mut(&window_id) {
Some(window) => {
let refresh = window.lock().unwrap().refresh_frame();
if refresh {
state
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
}
refresh
}
None => false,
});
}
// Wakeup event loop if needed.
//
// If the user draws from the `AboutToWait` this is likely not required, however
// we can't do much about it.
if wake_up {
match &self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.event_loop_awakener.ping();
}
#[cfg(x11_platform)]
PlatformEventLoopWindowTarget::X(_) => unreachable!(),
}
}
std::mem::swap(&mut self.compositor_updates, &mut compositor_updates); std::mem::swap(&mut self.compositor_updates, &mut compositor_updates);
std::mem::swap(&mut self.buffer_sink, &mut buffer_sink); std::mem::swap(&mut self.buffer_sink, &mut buffer_sink);
std::mem::swap(&mut self.window_ids, &mut window_ids); std::mem::swap(&mut self.window_ids, &mut window_ids);
@@ -561,7 +530,7 @@ impl<T: 'static> EventLoop<T> {
} }
#[inline] #[inline]
pub fn window_target(&self) -> &RootEventLoopWindowTarget { pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
&self.window_target &self.window_target
} }
@@ -583,7 +552,7 @@ impl<T: 'static> EventLoop<T> {
}; };
self.event_loop.dispatch(timeout, state).map_err(|error| { self.event_loop.dispatch(timeout, state).map_err(|error| {
log::error!("Error dispatching event loop: {}", error); error!("Error dispatching event loop: {}", error);
error.into() error.into()
}) })
} }
@@ -633,7 +602,7 @@ impl<T> AsRawFd for EventLoop<T> {
} }
} }
pub struct EventLoopWindowTarget { pub struct EventLoopWindowTarget<T> {
/// The event loop wakeup source. /// The event loop wakeup source.
pub event_loop_awakener: calloop::ping::Ping, pub event_loop_awakener: calloop::ping::Ping,
@@ -655,37 +624,11 @@ pub struct EventLoopWindowTarget {
/// Connection to the wayland server. /// Connection to the wayland server.
pub connection: Connection, pub connection: Connection,
_marker: std::marker::PhantomData<T>,
} }
impl EventLoopWindowTarget { impl<T> EventLoopWindowTarget<T> {
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub(crate) fn exit(&self) {
self.exit.set(Some(0))
}
pub(crate) fn clear_exit(&self) {
self.exit.set(None)
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get().is_some()
}
pub(crate) fn set_exit_code(&self, code: i32) {
self.exit.set(Some(code))
}
pub(crate) fn exit_code(&self) -> Option<i32> {
self.exit.get()
}
#[inline] #[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {} pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
@@ -713,3 +656,10 @@ impl EventLoopWindowTarget {
.into()) .into())
} }
} }
// The default routine does floor, but we need round on Wayland.
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
let width = size.width as f64 * scale_factor;
let height = size.height as f64 * scale_factor;
(width.round(), height.round()).into()
}

View File

@@ -9,11 +9,9 @@ use sctk::reexports::client::globals::{BindError, GlobalError};
use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy}; use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy};
pub(super) use crate::cursor::OnlyCursorImage as CustomCursor;
use crate::dpi::{LogicalSize, PhysicalSize};
pub use crate::platform_impl::platform::{OsError, WindowId}; pub use crate::platform_impl::platform::{OsError, WindowId};
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use output::{MonitorHandle, VideoModeHandle}; pub use output::{MonitorHandle, VideoMode};
pub use window::Window; pub use window::Window;
mod event_loop; mod event_loop;
@@ -78,10 +76,3 @@ impl DeviceId {
fn make_wid(surface: &WlSurface) -> WindowId { fn make_wid(surface: &WlSurface) -> WindowId {
WindowId(surface.id().as_ptr() as u64) WindowId(surface.id().as_ptr() as u64)
} }
/// The default routine does floor, but we need round on Wayland.
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
let width = size.width as f64 * scale_factor;
let height = size.height as f64 * scale_factor;
(width.round(), height.round()).into()
}

View File

@@ -4,11 +4,12 @@ use sctk::reexports::client::Proxy;
use sctk::output::OutputData; use sctk::output::OutputData;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::platform_impl::platform::VideoModeHandle as PlatformVideoModeHandle; use crate::event_loop::ControlFlow;
use crate::platform_impl::platform::VideoMode as PlatformVideoMode;
use super::event_loop::EventLoopWindowTarget; use super::event_loop::EventLoopWindowTarget;
impl EventLoopWindowTarget { impl<T> EventLoopWindowTarget<T> {
#[inline] #[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> { pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.state self.state
@@ -23,6 +24,30 @@ impl EventLoopWindowTarget {
// There's no primary monitor on Wayland. // There's no primary monitor on Wayland.
None None
} }
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub(crate) fn exit(&self) {
self.exit.set(Some(0))
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get().is_some()
}
pub(crate) fn set_exit_code(&self, code: i32) {
self.exit.set(Some(code))
}
pub(crate) fn exit_code(&self) -> Option<i32> {
self.exit.get()
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -98,14 +123,14 @@ impl MonitorHandle {
} }
#[inline] #[inline]
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> { pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoMode> {
let output_data = self.proxy.data::<OutputData>().unwrap(); let output_data = self.proxy.data::<OutputData>().unwrap();
let modes = output_data.with_output_info(|info| info.modes.clone()); let modes = output_data.with_output_info(|info| info.modes.clone());
let monitor = self.clone(); let monitor = self.clone();
modes.into_iter().map(move |mode| { modes.into_iter().map(move |mode| {
PlatformVideoModeHandle::Wayland(VideoModeHandle { PlatformVideoMode::Wayland(VideoMode {
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(), size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
refresh_rate_millihertz: mode.refresh_rate as u32, refresh_rate_millihertz: mode.refresh_rate as u32,
bit_depth: 32, bit_depth: 32,
@@ -142,14 +167,14 @@ impl std::hash::Hash for MonitorHandle {
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle { pub struct VideoMode {
pub(crate) size: PhysicalSize<u32>, pub(crate) size: PhysicalSize<u32>,
pub(crate) bit_depth: u16, pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32, pub(crate) refresh_rate_millihertz: u32,
pub(crate) monitor: MonitorHandle, pub(crate) monitor: MonitorHandle,
} }
impl VideoModeHandle { impl VideoMode {
#[inline] #[inline]
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
self.size self.size

View File

@@ -61,13 +61,8 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let window_id = wayland::make_wid(&surface); let window_id = wayland::make_wid(&surface);
// Mark the window as focused. // Mark the window as focused.
let was_unfocused = match state.windows.get_mut().get(&window_id) { match state.windows.get_mut().get(&window_id) {
Some(window) => { Some(window) => window.lock().unwrap().set_has_focus(true),
let mut window = window.lock().unwrap();
let was_unfocused = !window.has_focus();
window.add_seat_focus(data.seat.id());
was_unfocused
}
None => return, None => return,
}; };
@@ -78,14 +73,12 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
keyboard_state.loop_handle.remove(token); keyboard_state.loop_handle.remove(token);
} }
*data.window_id.lock().unwrap() = Some(window_id);
// The keyboard focus is considered as general focus. // The keyboard focus is considered as general focus.
if was_unfocused { state
state .events_sink
.events_sink .push_window_event(WindowEvent::Focused(true), window_id);
.push_window_event(WindowEvent::Focused(true), window_id);
} *data.window_id.lock().unwrap() = Some(window_id);
// HACK: this is just for GNOME not fixing their ordering issue of modifiers. // HACK: this is just for GNOME not fixing their ordering issue of modifiers.
if std::mem::take(&mut seat_state.modifiers_pending) { if std::mem::take(&mut seat_state.modifiers_pending) {
@@ -108,30 +101,24 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
// NOTE: The check whether the window exists is essential as we might get a // NOTE: The check whether the window exists is essential as we might get a
// nil surface, regardless of what protocol says. // nil surface, regardless of what protocol says.
let focused = match state.windows.get_mut().get(&window_id) { match state.windows.get_mut().get(&window_id) {
Some(window) => { Some(window) => window.lock().unwrap().set_has_focus(false),
let mut window = window.lock().unwrap();
window.remove_seat_focus(&data.seat.id());
window.has_focus()
}
None => return, None => return,
}; };
// Notify that no modifiers are being pressed.
state.events_sink.push_window_event(
WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
window_id,
);
// We don't need to update it above, because the next `Enter` will overwrite // We don't need to update it above, because the next `Enter` will overwrite
// anyway. // anyway.
*data.window_id.lock().unwrap() = None; *data.window_id.lock().unwrap() = None;
if !focused { state
// Notify that no modifiers are being pressed. .events_sink
state.events_sink.push_window_event( .push_window_event(WindowEvent::Focused(false), window_id);
WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
window_id,
);
state
.events_sink
.push_window_event(WindowEvent::Focused(false), window_id);
}
} }
WlKeyboardEvent::Key { WlKeyboardEvent::Key {
key, key,

View File

@@ -4,7 +4,6 @@ use std::sync::Arc;
use ahash::AHashMap; use ahash::AHashMap;
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_touch::WlTouch; use sctk::reexports::client::protocol::wl_touch::WlTouch;
use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::client::{Connection, Proxy, QueueHandle};
@@ -14,7 +13,6 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
use crate::event::WindowEvent;
use crate::keyboard::ModifiersState; use crate::keyboard::ModifiersState;
use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::state::WinitState;
@@ -145,10 +143,6 @@ impl SeatHandler for WinitState {
) { ) {
let seat_state = self.seats.get_mut(&seat.id()).unwrap(); let seat_state = self.seats.get_mut(&seat.id()).unwrap();
if let Some(text_input) = seat_state.text_input.take() {
text_input.destroy();
}
match capability { match capability {
SeatCapability::Touch => { SeatCapability::Touch => {
if let Some(touch) = seat_state.touch.take() { if let Some(touch) = seat_state.touch.take() {
@@ -180,10 +174,13 @@ impl SeatHandler for WinitState {
} }
SeatCapability::Keyboard => { SeatCapability::Keyboard => {
seat_state.keyboard_state = None; seat_state.keyboard_state = None;
self.on_keyboard_destroy(&seat.id());
} }
_ => (), _ => (),
} }
if let Some(text_input) = seat_state.text_input.take() {
text_input.destroy();
}
} }
fn new_seat( fn new_seat(
@@ -202,21 +199,6 @@ impl SeatHandler for WinitState {
seat: WlSeat, seat: WlSeat,
) { ) {
let _ = self.seats.remove(&seat.id()); let _ = self.seats.remove(&seat.id());
self.on_keyboard_destroy(&seat.id());
}
}
impl WinitState {
fn on_keyboard_destroy(&mut self, seat: &ObjectId) {
for (window_id, window) in self.windows.get_mut() {
let mut window = window.lock().unwrap();
let had_focus = window.has_focus();
window.remove_seat_focus(seat);
if had_focus != window.has_focus() {
self.events_sink
.push_window_event(WindowEvent::Focused(false), *window_id);
}
}
} }
} }

View File

@@ -23,19 +23,21 @@ use sctk::shm::slot::SlotPool;
use sctk::shm::{Shm, ShmHandler}; use sctk::shm::{Shm, ShmHandler};
use sctk::subcompositor::SubcompositorState; use sctk::subcompositor::SubcompositorState;
use crate::platform_impl::wayland::event_loop::sink::EventSink; use crate::dpi::LogicalSize;
use crate::platform_impl::wayland::output::MonitorHandle; use crate::platform_impl::OsError;
use crate::platform_impl::wayland::seat::{
use super::event_loop::sink::EventSink;
use super::output::MonitorHandle;
use super::seat::{
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData, PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
WinitPointerDataExt, WinitSeatState, WinitPointerDataExt, WinitSeatState,
}; };
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager; use super::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager; use super::types::wp_fractional_scaling::FractionalScalingManager;
use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState; use super::types::wp_viewporter::ViewporterState;
use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState; use super::types::xdg_activation::XdgActivationState;
use crate::platform_impl::wayland::window::{WindowRequests, WindowState}; use super::window::{WindowRequests, WindowState};
use crate::platform_impl::wayland::{WaylandError, WindowId}; use super::{WaylandError, WindowId};
use crate::platform_impl::OsError;
/// Winit's Wayland state. /// Winit's Wayland state.
pub struct WinitState { pub struct WinitState {
@@ -133,7 +135,7 @@ impl WinitState {
) { ) {
Ok(c) => Some(c), Ok(c) => Some(c),
Err(e) => { Err(e) => {
log::warn!("Subcompositor protocol not available, ignoring CSD: {e:?}"); warn!("Subcompositor protocol not available, ignoring CSD: {e:?}");
None None
} }
}; };
@@ -225,7 +227,7 @@ impl WinitState {
// Update the scale factor right away. // Update the scale factor right away.
window.lock().unwrap().set_scale_factor(scale_factor); window.lock().unwrap().set_scale_factor(scale_factor);
self.window_compositor_updates[pos].scale_changed = true; self.window_compositor_updates[pos].scale_factor = Some(scale_factor);
} else if let Some(pointer) = self.pointer_surfaces.get(&surface.id()) { } else if let Some(pointer) = self.pointer_surfaces.get(&surface.id()) {
// Get the window, where the pointer resides right now. // Get the window, where the pointer resides right now.
let focused_window = match pointer.pointer().winit_data().focused_window() { let focused_window = match pointer.pointer().winit_data().focused_window() {
@@ -289,26 +291,23 @@ impl WindowHandler for WinitState {
}; };
// Populate the configure to the window. // Populate the configure to the window.
self.window_compositor_updates[pos].resized |= self //
// XXX the size on the window will be updated right before dispatching the size to the user.
let new_size = self
.windows .windows
.get_mut() .get_mut()
.get_mut(&window_id) .get_mut(&window_id)
.expect("got configure for dead window.") .expect("got configure for dead window.")
.lock() .lock()
.unwrap() .unwrap()
.configure(configure, &self.shm, &self.subcompositor_state); .configure(
configure,
&self.shm,
&self.subcompositor_state,
&mut self.events_sink,
);
// NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the self.window_compositor_updates[pos].size = Some(new_size);
// users, since it can break a lot of things, thus it'll ask users to redraw instead.
self.window_requests
.get_mut()
.get(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
// Manually mark that we've got an event, since configure may not generate a resize.
self.dispatched_events = true;
} }
} }
@@ -402,10 +401,10 @@ pub struct WindowCompositorUpdate {
pub window_id: WindowId, pub window_id: WindowId,
/// New window size. /// New window size.
pub resized: bool, pub size: Option<LogicalSize<u32>>,
/// New scale factor. /// New scale factor.
pub scale_changed: bool, pub scale_factor: Option<f64>,
/// Close the window. /// Close the window.
pub close_window: bool, pub close_window: bool,
@@ -415,8 +414,8 @@ impl WindowCompositorUpdate {
fn new(window_id: WindowId) -> Self { fn new(window_id: WindowId) -> Self {
Self { Self {
window_id, window_id,
resized: false, size: None,
scale_changed: false, scale_factor: None,
close_window: false, close_window: false,
} }
} }

View File

@@ -27,7 +27,7 @@ pub struct CustomCursor {
} }
impl CustomCursor { impl CustomCursor {
pub(crate) fn new(pool: &mut SlotPool, image: &CursorImage) -> Self { pub fn new(pool: &mut SlotPool, image: &CursorImage) -> Self {
let (buffer, canvas) = pool let (buffer, canvas) = pool
.create_buffer( .create_buffer(
image.width as i32, image.width as i32,
@@ -37,15 +37,12 @@ impl CustomCursor {
) )
.unwrap(); .unwrap();
for (canvas_chunk, rgba) in canvas.chunks_exact_mut(4).zip(image.rgba.chunks_exact(4)) { for (canvas_chunk, rgba_chunk) in canvas.chunks_exact_mut(4).zip(image.rgba.chunks_exact(4))
// Alpha in buffer is premultiplied. {
let alpha = rgba[3] as f32 / 255.; canvas_chunk[0] = rgba_chunk[2];
let r = (rgba[0] as f32 * alpha) as u32; canvas_chunk[1] = rgba_chunk[1];
let g = (rgba[1] as f32 * alpha) as u32; canvas_chunk[2] = rgba_chunk[0];
let b = (rgba[2] as f32 * alpha) as u32; canvas_chunk[3] = rgba_chunk[3];
let color = ((rgba[3] as u32) << 24) + (r << 16) + (g << 8) + b;
let array: &mut [u8; 4] = canvas_chunk.try_into().unwrap();
*array = color.to_le_bytes();
} }
CustomCursor { CustomCursor {

View File

@@ -15,17 +15,17 @@ use sctk::shell::xdg::window::Window as SctkWindow;
use sctk::shell::xdg::window::WindowDecorations; use sctk::shell::xdg::window::WindowDecorations;
use sctk::shell::WaylandSurface; use sctk::shell::WaylandSurface;
use log::warn; use crate::cursor::CustomCursor;
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Ime, WindowEvent}; use crate::event::{Ime, WindowEvent};
use crate::event_loop::AsyncRequestSerial; use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::{ use crate::platform_impl::{
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon, Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
}; };
use crate::window::{ use crate::window::{
Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel, WindowAttributes, WindowButtons, WindowLevel,
}; };
@@ -80,9 +80,10 @@ pub struct Window {
} }
impl Window { impl Window {
pub(crate) fn new( pub(crate) fn new<T>(
event_loop_window_target: &EventLoopWindowTarget, event_loop_window_target: &EventLoopWindowTarget<T>,
attributes: WindowAttributes, attributes: WindowAttributes,
platform_attributes: PlatformAttributes,
) -> Result<Self, RootOsError> { ) -> Result<Self, RootOsError> {
let queue_handle = event_loop_window_target.queue_handle.clone(); let queue_handle = event_loop_window_target.queue_handle.clone();
let mut state = event_loop_window_target.state.borrow_mut(); let mut state = event_loop_window_target.state.borrow_mut();
@@ -132,7 +133,7 @@ impl Window {
window_state.set_decorate(attributes.decorations); window_state.set_decorate(attributes.decorations);
// Set the app_id. // Set the app_id.
if let Some(name) = attributes.platform_specific.name.map(|name| name.general) { if let Some(name) = platform_attributes.name.map(|name| name.general) {
window.set_app_id(name); window.set_app_id(name);
} }
@@ -150,7 +151,7 @@ impl Window {
window_state.set_resizable(attributes.resizable); window_state.set_resizable(attributes.resizable);
// Set startup mode. // Set startup mode.
match attributes.fullscreen.map(Into::into) { match attributes.fullscreen.0.map(Into::into) {
Some(Fullscreen::Exclusive(_)) => { Some(Fullscreen::Exclusive(_)) => {
warn!("`Fullscreen::Exclusive` is ignored on Wayland"); warn!("`Fullscreen::Exclusive` is ignored on Wayland");
} }
@@ -167,15 +168,10 @@ impl Window {
_ => (), _ => (),
}; };
match attributes.cursor {
Cursor::Icon(icon) => window_state.set_cursor(icon),
Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
}
// Activate the window when the token is passed. // Activate the window when the token is passed.
if let (Some(xdg_activation), Some(token)) = ( if let (Some(xdg_activation), Some(token)) = (
xdg_activation.as_ref(), xdg_activation.as_ref(),
attributes.platform_specific.activation_token, platform_attributes.activation_token,
) { ) {
xdg_activation.activate(token._token, &surface); xdg_activation.activate(token._token, &surface);
} }
@@ -285,7 +281,7 @@ impl Window {
pub fn inner_size(&self) -> PhysicalSize<u32> { pub fn inner_size(&self) -> PhysicalSize<u32> {
let window_state = self.window_state.lock().unwrap(); let window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor(); let scale_factor = window_state.scale_factor();
super::logical_to_physical_rounded(window_state.inner_size(), scale_factor) window_state.inner_size().to_physical(scale_factor)
} }
#[inline] #[inline]
@@ -313,7 +309,7 @@ impl Window {
pub fn outer_size(&self) -> PhysicalSize<u32> { pub fn outer_size(&self) -> PhysicalSize<u32> {
let window_state = self.window_state.lock().unwrap(); let window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor(); let scale_factor = window_state.scale_factor();
super::logical_to_physical_rounded(window_state.outer_size(), scale_factor) window_state.outer_size().to_physical(scale_factor)
} }
#[inline] #[inline]
@@ -332,9 +328,7 @@ impl Window {
self.window_state self.window_state
.lock() .lock()
.unwrap() .unwrap()
.set_min_inner_size(min_size); .set_min_inner_size(min_size)
// NOTE: Requires commit to be applied.
self.request_redraw();
} }
/// Set the maximum inner size for the window. /// Set the maximum inner size for the window.
@@ -345,9 +339,7 @@ impl Window {
self.window_state self.window_state
.lock() .lock()
.unwrap() .unwrap()
.set_max_inner_size(max_size); .set_max_inner_size(max_size)
// NOTE: Requires commit to be applied.
self.request_redraw();
} }
#[inline] #[inline]
@@ -396,10 +388,7 @@ impl Window {
#[inline] #[inline]
pub fn set_resizable(&self, resizable: bool) { pub fn set_resizable(&self, resizable: bool) {
if self.window_state.lock().unwrap().set_resizable(resizable) { self.window_state.lock().unwrap().set_resizable(resizable);
// NOTE: Requires commit to be applied.
self.request_redraw();
}
} }
#[inline] #[inline]
@@ -514,13 +503,13 @@ impl Window {
} }
#[inline] #[inline]
pub fn set_cursor(&self, cursor: Cursor) { pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let window_state = &mut self.window_state.lock().unwrap(); self.window_state.lock().unwrap().set_cursor(cursor);
}
match cursor { #[inline]
Cursor::Icon(icon) => window_state.set_cursor(icon), pub fn set_custom_cursor(&self, cursor: CustomCursor) {
Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor), self.window_state.lock().unwrap().set_custom_cursor(cursor);
}
} }
#[inline] #[inline]

View File

@@ -4,10 +4,8 @@ use std::num::NonZeroU32;
use std::sync::{Arc, Mutex, Weak}; use std::sync::{Arc, Mutex, Weak};
use std::time::Duration; use std::time::Duration;
use ahash::HashSet;
use log::{info, warn}; use log::{info, warn};
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_shm::WlShm; use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_surface::WlSurface;
@@ -33,10 +31,12 @@ use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::cursor::CustomCursor as RootCustomCursor; use crate::cursor::CustomCursor as RootCustomCursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size}; use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
use crate::error::{ExternalError, NotSupportedError}; use crate::error::{ExternalError, NotSupportedError};
use crate::platform_impl::wayland::logical_to_physical_rounded; use crate::event::WindowEvent;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::make_wid;
use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor}; use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager; use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::{PlatformCustomCursor, WindowId}; use crate::platform_impl::WindowId;
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme}; use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
use crate::platform_impl::wayland::seat::{ use crate::platform_impl::wayland::seat::{
@@ -92,10 +92,8 @@ pub struct WindowState {
/// Whether the frame is resizable. /// Whether the frame is resizable.
resizable: bool, resizable: bool,
// NOTE: we can't use simple counter, since it's racy when seat getting destroyed and new /// Whether the window has focus.
// is created, since add/removed stuff could be delivered a bit out of order. has_focus: bool,
/// Seats that has keyboard focus on that window.
seat_focus: HashSet<ObjectId>,
/// The scale factor of the window. /// The scale factor of the window.
scale_factor: f64, scale_factor: f64,
@@ -191,7 +189,7 @@ impl WindowState {
fractional_scale, fractional_scale,
frame: None, frame: None,
frame_callback_state: FrameCallbackState::None, frame_callback_state: FrameCallbackState::None,
seat_focus: Default::default(), has_focus: false,
has_pending_move: None, has_pending_move: None,
ime_allowed: false, ime_allowed: false,
ime_purpose: ImePurpose::Normal, ime_purpose: ImePurpose::Normal,
@@ -263,7 +261,8 @@ impl WindowState {
configure: WindowConfigure, configure: WindowConfigure,
shm: &Shm, shm: &Shm,
subcompositor: &Option<Arc<SubcompositorState>>, subcompositor: &Option<Arc<SubcompositorState>>,
) -> bool { event_sink: &mut EventSink,
) -> LogicalSize<u32> {
// NOTE: when using fractional scaling or wl_compositor@v6 the scaling // NOTE: when using fractional scaling or wl_compositor@v6 the scaling
// should be delivered before the first configure, thus apply it to // should be delivered before the first configure, thus apply it to
// properly scale the physical sizes provided by the users. // properly scale the physical sizes provided by the users.
@@ -306,6 +305,19 @@ impl WindowState {
let stateless = Self::is_stateless(&configure); let stateless = Self::is_stateless(&configure);
// Emit `Occluded` event on suspension change.
let occluded = configure.state.contains(XdgWindowState::SUSPENDED);
if self
.last_configure
.as_ref()
.map(|c| c.state.contains(XdgWindowState::SUSPENDED))
.unwrap_or(false)
!= occluded
{
let window_id = make_wid(self.window.wl_surface());
event_sink.push_window_event(WindowEvent::Occluded(occluded), window_id);
}
let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() { let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
// Configure the window states. // Configure the window states.
frame.update_state(configure.state); frame.update_state(configure.state);
@@ -313,9 +325,14 @@ impl WindowState {
match configure.new_size { match configure.new_size {
(Some(width), Some(height)) => { (Some(width), Some(height)) => {
let (width, height) = frame.subtract_borders(width, height); let (width, height) = frame.subtract_borders(width, height);
let width = width.map(|w| w.get()).unwrap_or(1); (
let height = height.map(|h| h.get()).unwrap_or(1); (
((width, height).into(), false) width.map(|w| w.get()).unwrap_or(1),
height.map(|h| h.get()).unwrap_or(1),
)
.into(),
false,
)
} }
(_, _) if stateless => (self.stateless_size, true), (_, _) if stateless => (self.stateless_size, true),
_ => (self.size, true), _ => (self.size, true),
@@ -341,31 +358,13 @@ impl WindowState {
.unwrap_or(new_size.height); .unwrap_or(new_size.height);
} }
let new_state = configure.state; // XXX Set the configure before doing a resize.
let old_state = self
.last_configure
.as_ref()
.map(|configure| configure.state);
let state_change_requires_resize = old_state
.map(|old_state| {
!old_state
.symmetric_difference(new_state)
.difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
.is_empty()
})
// NOTE: `None` is present for the initial configure, thus we must always resize.
.unwrap_or(true);
// NOTE: Set the configure before doing a resize, since we query it during it.
self.last_configure = Some(configure); self.last_configure = Some(configure);
if state_change_requires_resize || new_size != self.inner_size() { // XXX Update the new size right away.
self.resize(new_size); self.resize(new_size);
true
} else { new_size
false
}
} }
/// Compute the bounds for the inner size of the surface. /// Compute the bounds for the inner size of the surface.
@@ -504,12 +503,10 @@ impl WindowState {
} }
/// Set the resizable state on the window. /// Set the resizable state on the window.
///
/// Returns `true` when the state was applied.
#[inline] #[inline]
pub fn set_resizable(&mut self, resizable: bool) -> bool { pub fn set_resizable(&mut self, resizable: bool) {
if self.resizable == resizable { if self.resizable == resizable {
return false; return;
} }
self.resizable = resizable; self.resizable = resizable;
@@ -525,14 +522,12 @@ impl WindowState {
if let Some(frame) = self.frame.as_mut() { if let Some(frame) = self.frame.as_mut() {
frame.set_resizable(resizable); frame.set_resizable(resizable);
} }
true
} }
/// Whether the window is focused by any seat. /// Whether the window is focused.
#[inline] #[inline]
pub fn has_focus(&self) -> bool { pub fn has_focus(&self) -> bool {
!self.seat_focus.is_empty() self.has_focus
} }
/// Whether the IME is allowed. /// Whether the IME is allowed.
@@ -648,7 +643,7 @@ impl WindowState {
self.resize(inner_size.to_logical(self.scale_factor())) self.resize(inner_size.to_logical(self.scale_factor()))
} }
logical_to_physical_rounded(self.inner_size(), self.scale_factor()) self.inner_size().to_physical(self.scale_factor())
} }
/// Resize the window to the new inner size. /// Resize the window to the new inner size.
@@ -718,23 +713,10 @@ impl WindowState {
} }
/// Set the custom cursor icon. /// Set the custom cursor icon.
pub(crate) fn set_custom_cursor(&mut self, cursor: RootCustomCursor) { pub fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
let cursor = match cursor {
RootCustomCursor {
inner: PlatformCustomCursor::Wayland(cursor),
} => cursor.0,
#[cfg(x11_platform)]
RootCustomCursor {
inner: PlatformCustomCursor::X(_),
} => {
log::error!("passed a X11 cursor to Wayland backend");
return;
}
};
let cursor = { let cursor = {
let mut pool = self.custom_cursor_pool.lock().unwrap(); let mut pool = self.custom_cursor_pool.lock().unwrap();
CustomCursor::new(&mut pool, &cursor) CustomCursor::new(&mut pool, &cursor.inner)
}; };
if self.cursor_visible { if self.cursor_visible {
@@ -959,16 +941,12 @@ impl WindowState {
} }
} }
/// Add seat focus for the window. /// Mark that the window has focus.
///
/// Should be used from routine that sends focused event.
#[inline] #[inline]
pub fn add_seat_focus(&mut self, seat: ObjectId) { pub fn set_has_focus(&mut self, has_focus: bool) {
self.seat_focus.insert(seat); self.has_focus = has_focus;
}
/// Remove seat focus from the window.
#[inline]
pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
self.seat_focus.remove(seat);
} }
/// Returns `true` if the requested state was applied. /// Returns `true` if the requested state was applied.
@@ -992,7 +970,7 @@ impl WindowState {
/// Set the IME position. /// Set the IME position.
pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) { pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
// FIXME: This won't fly unless user will have a way to request IME window per seat, since // XXX This won't fly unless user will have a way to request IME window per seat, since
// the ime windows will be overlapping, but winit doesn't expose API to specify for // the ime windows will be overlapping, but winit doesn't expose API to specify for
// which seat we're setting IME position. // which seat we're setting IME position.
let (x, y) = (position.x as i32, position.y as i32); let (x, y) = (position.x as i32, position.y as i32);
@@ -1023,7 +1001,7 @@ impl WindowState {
pub fn set_scale_factor(&mut self, scale_factor: f64) { pub fn set_scale_factor(&mut self, scale_factor: f64) {
self.scale_factor = scale_factor; self.scale_factor = scale_factor;
// NOTE: When fractional scaling is not used update the buffer scale. // XXX when fractional scaling is not used update the buffer scale.
if self.fractional_scale.is_none() { if self.fractional_scale.is_none() {
let _ = self.window.set_buffer_scale(self.scale_factor as _); let _ = self.window.set_buffer_scale(self.scale_factor as _);
} }
@@ -1171,7 +1149,7 @@ impl From<ResizeDirection> for XdgResizeEdge {
} }
} }
// NOTE: Rust doesn't allow `From<Option<Theme>>`. // XXX rust doesn't allow from `Option`.
#[cfg(feature = "sctk-adwaita")] #[cfg(feature = "sctk-adwaita")]
fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig { fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
match theme { match theme {

View File

@@ -6,7 +6,7 @@ macro_rules! atom_manager {
($($name:ident $(:$lit:literal)?),*) => { ($($name:ident $(:$lit:literal)?),*) => {
x11rb::atom_manager! { x11rb::atom_manager! {
/// The atoms used by `winit` /// The atoms used by `winit`
pub Atoms: AtomsCookie { pub(crate) Atoms: AtomsCookie {
$($name $(:$lit)?,)* $($name $(:$lit)?,)*
} }
} }
@@ -14,7 +14,7 @@ macro_rules! atom_manager {
/// Indices into the `Atoms` struct. /// Indices into the `Atoms` struct.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub enum AtomName { pub(crate) enum AtomName {
$($name,)* $($name,)*
} }
@@ -40,7 +40,6 @@ atom_manager! {
WM_DELETE_WINDOW, WM_DELETE_WINDOW,
WM_PROTOCOLS, WM_PROTOCOLS,
WM_STATE, WM_STATE,
XIM_SERVERS,
// Assorted ICCCM Atoms // Assorted ICCCM Atoms
_NET_WM_ICON, _NET_WM_ICON,
@@ -100,8 +99,7 @@ atom_manager! {
_NET_FRAME_EXTENTS, _NET_FRAME_EXTENTS,
_NET_SUPPORTED, _NET_SUPPORTED,
_NET_SUPPORTING_WM_CHECK, _NET_SUPPORTING_WM_CHECK,
_XEMBED, _XEMBED
_XSETTINGS_SETTINGS
} }
impl Index<AtomName> for Atoms { impl Index<AtomName> for Atoms {

View File

@@ -41,10 +41,10 @@ impl From<io::Error> for DndDataParseError {
} }
} }
pub struct Dnd { pub(crate) struct Dnd {
xconn: Arc<XConnection>, xconn: Arc<XConnection>,
// Populated by XdndEnter event handler // Populated by XdndEnter event handler
pub version: Option<c_long>, pub version: Option<u32>,
pub type_list: Option<Vec<xproto::Atom>>, pub type_list: Option<Vec<xproto::Atom>>,
// Populated by XdndPosition event handler // Populated by XdndPosition event handler
pub source_window: Option<xproto::Window>, pub source_window: Option<xproto::Window>,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,862 @@
//! IME handler, using the xim-rs crate.
use super::{X11Error, X11rbConnection, XConnection};
use x11rb::connection::Connection;
use x11rb::protocol::xproto::Window;
use x11rb::protocol::Event;
use xim::x11rb::{HasConnection, X11rbClient};
use xim::{AttributeName, Client as _, ClientError, ClientHandler, InputStyle, Point};
use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::rc::Rc;
use std::sync::Arc;
impl HasConnection for XConnection {
type Connection = X11rbConnection;
fn conn(&self) -> &Self::Connection {
self.xcb_connection()
}
}
/// A collection of the IME events that can occur.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ImeEvent {
Enabled,
Start,
Update(String, Option<usize>),
Commit(String),
End,
Disabled,
}
/// Invalid states that an IME client can enter.
#[derive(Debug, Clone)]
pub enum InvalidImeState {
/// The IME has no style information.
NoStyle,
/// No windows in the pending window queue.
NoWindows,
/// Invalid input context.
InvalidIc(u16),
}
impl fmt::Display for InvalidImeState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InvalidImeState::NoStyle => write!(f, "IME has no style information"),
InvalidImeState::NoWindows => {
write!(f, "IME has no windows in the pending window queue")
}
InvalidImeState::InvalidIc(ic) => write!(f, "IME has invalid input context {}", ic),
}
}
}
/// Request to control XIM handler from the window.
pub enum ImeRequest {
/// Set IME spot position for given `window_id`.
Position(Window, i16, i16),
/// Allow IME input for the given `window_id`.
Allow(Window, bool),
}
/// The IME data for winit.
pub(super) struct ImeData {
/// The XIM client manager.
client: X11rbClient<Arc<XConnection>>,
/// Relevant IME data.
handler: ImeHandler,
}
/// Inner IME handler.
struct ImeHandler {
/// Handle to the event queue.
event_queue: Rc<RefCell<VecDeque<Event>>>,
/// Whether IME is currently disconnected.
disconnected: bool,
/// IME events waiting to be read.
ime_events: VecDeque<(Window, ImeEvent)>,
/// Windows waiting to be assigned an input context.
pending_windows: VecDeque<WindowData>,
/// Currently registered input styles.
styles: Option<(Style, Style)>,
/// The input method for the display, if there is one.
input_method: Option<u16>,
/// Hash map between input contexts and their associated data.
input_contexts: HashMap<u16, IcData>,
/// Map between window IDs and their associated input contexts.
window_contexts: HashMap<Window, u16>,
}
/// Data relevant for each input context.
struct IcData {
/// Data associated with the window.
window: WindowData,
/// Newly set point for the context.
new_spot: Option<Point>,
/// The current preedit string.
///
/// We use a `Vec<char>` here instead of a string because the IME indices operate on chars,
/// not bytes.
text: Vec<char>,
/// The current cursor position in the preedit string.
cursor: usize,
}
/// Windows waiting for IME events.
struct WindowData {
/// The window ID.
id: Window,
/// The style of the window.
style: Style,
/// Current "spot" for the context.
spot: Point,
}
#[derive(Copy, Clone)]
enum Style {
Preedit,
Nothing,
None,
}
impl ImeData {
/// Creates the IME data for the display.
pub(super) fn new(
conn: &Arc<XConnection>,
screen: usize,
event_queue: &Rc<RefCell<VecDeque<Event>>>,
) -> Result<Self, X11Error> {
// IM servers to try, in order:
// - None, which defaults to the environment variable `XMODIFIERS` in xim's impl.
// - "local", which is the default for most IMEs.
// - empty string, which may work in some cases.
let input_methods = [None, Some("local"), Some("")];
let mut last_error = X11Error::Ime(ClientError::NoXimServer);
for im in input_methods {
// Try to initialize a client here.
match X11rbClient::init(conn.clone(), screen, im) {
Ok(client) => {
return Ok(Self {
client,
handler: ImeHandler {
event_queue: event_queue.clone(),
disconnected: true,
ime_events: VecDeque::new(),
pending_windows: VecDeque::new(),
styles: None,
input_method: None,
input_contexts: HashMap::new(),
window_contexts: HashMap::new(),
},
})
}
Err(err) => {
log::warn!("Failed to create XIM client for {:?}: {err}", ImData(im));
last_error = X11Error::Ime(err);
}
}
}
Err(last_error)
}
/// Filter an event.
pub(super) fn filter_event(&mut self, event: &Event) -> Result<bool, X11Error> {
self.client
.filter_event(event, &mut self.handler)
.map_err(X11Error::Ime)
}
/// Connection to the X server.
fn conn(&self) -> &X11rbConnection {
self.client.conn()
}
/// Get an IME event.
pub(super) fn next_ime_event(&mut self) -> Option<(Window, ImeEvent)> {
self.handler.ime_events.pop_front()
}
/// Create a new IME context for the provided window.
pub(super) fn create_context(
&mut self,
window: Window,
with_preedit: bool,
spot: Option<Point>,
) -> Result<bool, X11Error> {
// If we aren't connected, nothing can be done.
if self.handler.disconnected {
return Ok(false);
}
let method = match self.handler.input_method {
Some(im) => im,
None => return Ok(false),
};
// Get the current style.
let style = match (self.handler.styles, with_preedit) {
(None, _) => return Err(X11Error::InvalidImeState(InvalidImeState::NoStyle)),
(Some((preedit_style, _)), true) => preedit_style,
(Some((_, none_style)), false) => none_style,
};
// Setup IC attributes.
let ic_attributes = {
let mut ic_attributes = self
.client
.build_ic_attributes()
.push(AttributeName::ClientWindow, window);
let ic_style = match style {
Style::Preedit => InputStyle::PREEDIT_POSITION | InputStyle::STATUS_NOTHING,
Style::Nothing => InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING,
Style::None => InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE,
};
if let Some(spot) = spot.clone() {
ic_attributes = ic_attributes.push(AttributeName::SpotLocation, spot);
}
ic_attributes
.push(AttributeName::InputStyle, ic_style)
.build()
};
// Create the IC.
self.client.create_ic(method, ic_attributes)?;
// Add to the waiting window list.
self.handler.pending_windows.push_back(WindowData {
id: window,
style,
spot: spot.unwrap_or(Point { x: 0, y: 0 }),
});
Ok(true)
}
/// Remove an IME context for a window.
pub(super) fn remove_context(&mut self, window: Window) -> Result<bool, X11Error> {
if self.handler.disconnected {
return Ok(false);
}
let method = match self.handler.input_method {
Some(im) => im,
None => return Ok(false),
};
// Remove the pending window if it's still pending.
let mut removed = false;
self.handler.pending_windows.retain(|pending| {
if pending.id == window {
removed = true;
false
} else {
true
}
});
if removed {
return Ok(true);
}
// Remove the IC if it's already created.
if let Some(ic) = self.handler.window_contexts.remove(&window) {
self.handler.input_contexts.remove(&ic);
// Destroy the IC.
self.client.destroy_ic(method, ic)?;
}
Ok(false)
}
/// Focus an IME context.
pub(super) fn focus_window(&mut self, window: Window) -> Result<bool, X11Error> {
if self.handler.disconnected {
return Ok(false);
}
let method = self.wait_for_method()?;
let ic = self.wait_for_context(window)?;
if let Some(ic) = ic {
self.client.set_focus(method, ic)?;
return Ok(true);
}
Ok(false)
}
/// Unfocus an IME context.
pub(super) fn unfocus_window(&mut self, window: Window) -> Result<bool, X11Error> {
if self.handler.disconnected {
return Ok(false);
}
let method = self.wait_for_method()?;
let ic = self.wait_for_context(window)?;
if let Some(ic) = ic {
self.client.unset_focus(method, ic)?;
return Ok(true);
}
Ok(false)
}
/// Set the spot for an IME context.
pub(super) fn set_spot(&mut self, window: Window, x: i16, y: i16) -> Result<(), X11Error> {
if self.handler.disconnected {
return Ok(());
}
let method = self.wait_for_method()?;
let ic = self.wait_for_context(window)?;
if let Some(ic) = ic {
// If the IC is not available, or if the spot is the same, then we don't need to update.
let ic_data = match self.handler.input_contexts.get_mut(&ic) {
Some(ic_data) => ic_data,
None => return Ok(()),
};
let new_point = Point { x, y };
if !matches!(ic_data.window.style, Style::None) || ic_data.window.spot == new_point {
return Ok(());
}
let new_attrs = self
.client
.build_ic_attributes()
.push(AttributeName::SpotLocation, new_point.clone())
.build();
self.client.set_ic_values(method, ic, new_attrs)?;
// Indicate that we have a new spot.
debug_assert!(ic_data.new_spot.is_none());
ic_data.new_spot = Some(new_point);
}
Ok(())
}
pub(super) fn set_ime_allowed(
&mut self,
window: Window,
allowed: bool,
) -> Result<(), X11Error> {
if self.handler.disconnected {
return Ok(());
}
// Get the client info.
let _ = self.wait_for_method()?;
let ic = self.wait_for_context(window)?;
if let Some(ic) = ic {
let mut spot = None;
// See if we need to update the allowed state.
if let Some(ic_data) = self.handler.input_contexts.get(&ic) {
spot = Some(ic_data.window.spot.clone());
if matches!(ic_data.window.style, Style::None) != allowed {
return Ok(());
}
}
// Delete and re-install the IC.
self.remove_context(window)?;
self.create_context(window, allowed, spot)?;
}
Ok(())
}
/// Wait for the input method to be set.
fn wait_for_method(&mut self) -> Result<u16, X11Error> {
loop {
if let Some(im) = self.handler.input_method {
return Ok(im);
}
// Wait and hope the input method is set.
self.block_for_ime()?;
}
}
/// Wait for an input context to be set.
fn wait_for_context(&mut self, window: Window) -> Result<Option<u16>, X11Error> {
if let Some(cid) = self.handler.window_contexts.get(&window) {
return Ok(Some(*cid));
}
// If the window isn't in our pending windows queue, there's no way for it to get an IC.
if !self
.handler
.pending_windows
.iter()
.any(|WindowData { id, .. }| *id == window)
{
return Ok(None);
}
loop {
self.block_for_ime()?;
if let Some(cid) = self.handler.window_contexts.get(&window) {
return Ok(Some(*cid));
}
}
}
/// Wait until we've acted on an IME event.
fn block_for_ime(&mut self) -> Result<(), X11Error> {
let mut last_event = self.conn().poll_for_event()?;
loop {
if let Some(last_event) = last_event.as_ref() {
if self.filter_event(last_event)? {
return Ok(());
}
}
// This scope keeps track of the event queue handle.
{
// Check the event queue for events.
let event_queue = self.handler.event_queue.clone();
let mut event_queue = event_queue.borrow_mut();
// Check the event queue for events we can use.
let mut found_event = false;
let mut last_err = None;
event_queue.retain(|event| match self.filter_event(event) {
Ok(false) => {
found_event = true;
true
}
Ok(true) => false,
Err(err) => {
last_err = Some(err);
true
}
});
// Push our own event to the queue.
if let Some(last_event) = last_event.take() {
event_queue.push_back(last_event);
}
// Check for errors.
if let Some(err) = last_err {
return Err(err);
}
// If we found an event, then we're done.
if found_event {
return Ok(());
}
}
log::info!("Waiting for IME event");
last_event = Some(self.conn().wait_for_event()?);
}
}
}
impl<C: xim::Client> ClientHandler<C> for ImeHandler {
fn handle_connect(&mut self, client: &mut C) -> Result<(), ClientError> {
// We have been connected, now request a new input method for our current locale.
self.disconnected = false;
client.open(&locale())
}
fn handle_disconnect(&mut self) {
// We are now disconnected.
self.disconnected = true;
}
fn handle_open(&mut self, client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
// We now have an input method.
debug_assert!(self.input_method.is_none());
self.input_method = Some(input_method_id);
// Ask for the IM's attributes.
client.get_im_values(input_method_id, &[AttributeName::QueryInputStyle])
}
fn handle_close(&mut self, _client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
// No more input method.
debug_assert_eq!(self.input_method, Some(input_method_id));
self.input_method = None;
Ok(())
}
fn handle_get_im_values(
&mut self,
_client: &mut C,
input_method_id: u16,
mut attributes: xim::AHashMap<xim::AttributeName, Vec<u8>>,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
// Get the input styles.
let mut preedit_style = None;
let mut none_style = None;
let styles = {
let style = attributes
.remove(&AttributeName::QueryInputStyle)
.expect("No query input style");
let mut result = vec![0u32; style.len() / 4];
bytemuck::cast_slice_mut::<u32, u8>(&mut result).copy_from_slice(&style);
result
};
{
// The styles that we're looking for.
let lu_preedit_style = InputStyle::PREEDIT_CALLBACKS | InputStyle::STATUS_NOTHING;
let lu_nothing_style = InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING;
let lu_none_style = InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE;
for style in styles {
let style = InputStyle::from_bits_truncate(style);
if style == lu_preedit_style {
preedit_style = Some(Style::Preedit);
} else if style == lu_nothing_style {
preedit_style = Some(Style::Nothing);
} else if style == lu_none_style {
none_style = Some(Style::None);
}
}
}
let (preedit_style, none_style) = match (preedit_style, none_style) {
(None, None) => {
log::error!("No supported input styles found");
return Ok(());
}
(Some(style), None) | (None, Some(style)) => (style, style),
(Some(preedit_style), Some(none_style)) => (preedit_style, none_style),
};
self.styles = Some((preedit_style, none_style));
Ok(())
}
fn handle_create_ic(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
// Get the window that wanted the IC context.
let window = self
.pending_windows
.pop_front()
.ok_or_else(|| invalid_state(InvalidImeState::NoWindows))?;
// Create the IC data.
let ic_data = IcData {
window,
new_spot: None,
text: Vec::new(),
cursor: 0,
};
// Store the context.
let (window, style) = (ic_data.window.id, ic_data.window.style);
self.input_contexts.insert(input_context_id, ic_data);
self.window_contexts.insert(window, input_context_id);
// Indicate our status.
let event = if matches!(style, Style::Nothing) {
ImeEvent::Disabled
} else {
ImeEvent::Enabled
};
self.ime_events.push_back((window, event));
Ok(())
}
fn handle_destroy_ic(
&mut self,
_client: &mut C,
_input_method_id: u16,
_input_context_id: u16,
) -> Result<(), ClientError> {
// This is already handled by the higher-level function.
Ok(())
}
fn handle_set_ic_values(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
// Get the IC data.
let ic_data = self
.input_contexts
.get_mut(&input_context_id)
.ok_or_else(|| invalid_state(InvalidImeState::InvalidIc(input_context_id)))?;
// Move up the new spot
if let Some(spot) = ic_data.new_spot.take() {
ic_data.window.spot = spot;
}
Ok(())
}
fn handle_preedit_start(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
// Start a pre-edit.
ic_data.text.clear();
ic_data.cursor = 0;
// Indicate the start.
self.ime_events
.push_back((ic_data.window.id, ImeEvent::Start));
}
Ok(())
}
fn handle_preedit_draw(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
caret: i32,
chg_first: i32,
chg_len: i32,
_status: xim::PreeditDrawStatus,
preedit_string: &str,
_feedbacks: Vec<xim::Feedback>,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
// Set the cursor.
ic_data.cursor = caret as usize;
// Figure out the range of text to change.
let change_range = chg_first as usize..(chg_first + chg_len) as usize;
// If the range doesn't fit our current text, warn and return.
if change_range.start > ic_data.text.len() || change_range.end > ic_data.text.len() {
warn!(
"Preedit draw range {}..{} doesn't fit text of length {}",
change_range.start,
change_range.end,
ic_data.text.len()
);
return Ok(());
}
// Update the text in the changed range.
{
let text = &mut ic_data.text;
let mut old_text_tail = text.split_off(change_range.end);
text.truncate(change_range.start);
text.extend(preedit_string.chars());
text.append(&mut old_text_tail);
}
// Send the event.
let cursor_byte_pos = calc_byte_position(&ic_data.text, ic_data.cursor);
let event = ImeEvent::Update(ic_data.text.iter().collect(), Some(cursor_byte_pos));
self.ime_events.push_back((ic_data.window.id, event));
}
Ok(())
}
fn handle_preedit_caret(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
position: &mut i32,
direction: xim::CaretDirection,
_style: xim::CaretStyle,
) -> Result<(), ClientError> {
// We only care about absolute position.
if matches!(direction, xim::CaretDirection::AbsolutePosition) {
debug_assert_eq!(self.input_method, Some(input_method_id));
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
ic_data.cursor = *position as usize;
// Send the event
let event =
ImeEvent::Update(ic_data.text.iter().collect(), Some(*position as usize));
self.ime_events.push_back((ic_data.window.id, event));
}
}
Ok(())
}
fn handle_preedit_done(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
// Get the client data.
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
// We're done with a preedit.
ic_data.text.clear();
ic_data.cursor = 0;
// Send a message to the window.
let window = ic_data.window.id;
self.ime_events.push_back((window, ImeEvent::End));
}
Ok(())
}
fn handle_commit(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
text: &str,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
// Get the client data.
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
// Send a message to the window.
let window = ic_data.window.id;
self.ime_events
.push_back((window, ImeEvent::Commit(text.to_owned())));
}
Ok(())
}
fn handle_query_extension(
&mut self,
_client: &mut C,
_extensions: &[xim::Extension],
) -> Result<(), ClientError> {
// Don't care.
Ok(())
}
fn handle_forward_event(
&mut self,
_client: &mut C,
_input_method_id: u16,
_input_context_id: u16,
_flag: xim::ForwardEventFlag,
_xev: C::XEvent,
) -> Result<(), ClientError> {
// Don't care.
Ok(())
}
fn handle_set_event_mask(
&mut self,
_client: &mut C,
_input_method_id: u16,
_input_context_id: u16,
_forward_event_mask: u32,
_synchronous_event_mask: u32,
) -> Result<(), ClientError> {
// Don't care.
Ok(())
}
}
#[inline(always)]
fn invalid_state(state: InvalidImeState) -> ClientError {
ClientError::Other(Box::new(X11Error::InvalidImeState(state)))
}
struct ImData(Option<&'static str>);
impl fmt::Debug for ImData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Some(name) => write!(f, "\"{}\"", name),
None => write!(f, "default input method"),
}
}
}
/// Get the current locale.
fn locale() -> String {
use std::ffi::CStr;
const EN_US: &str = "en_US.UTF-8";
// Get the pointer to the current locale.
let locale_ptr = unsafe { libc::setlocale(libc::LC_CTYPE, std::ptr::null()) };
// If locale_ptr is null, just default to en_US.UTF-8.
if locale_ptr.is_null() {
return EN_US.to_owned();
}
// Convert the pointer to a CStr.
let locale_cstr = unsafe { CStr::from_ptr(locale_ptr) };
// Convert the CStr to a String to prevent the result from getting clobbered.
locale_cstr.to_str().unwrap_or(EN_US).to_owned()
}
fn calc_byte_position(text: &[char], pos: usize) -> usize {
text.iter().take(pos).map(|c| c.len_utf8()).sum()
}

View File

@@ -1,215 +0,0 @@
use std::{collections::HashMap, os::raw::c_char, ptr, sync::Arc};
use super::{ffi, XConnection, XError};
use super::{
context::{ImeContext, ImeContextCreationError},
inner::{close_im, ImeInner},
input_method::PotentialInputMethods,
};
pub(crate) unsafe fn xim_set_callback(
xconn: &Arc<XConnection>,
xim: ffi::XIM,
field: *const c_char,
callback: *mut ffi::XIMCallback,
) -> Result<(), XError> {
// It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize
// access that isn't type-checked.
unsafe { (xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>()) };
xconn.check_errors()
}
// Set a callback for when an input method matching the current locale modifiers becomes
// available. Note that this has nothing to do with what input methods are open or able to be
// opened, and simply uses the modifiers that are set when the callback is set.
// * This is called per locale modifier, not per input method opened with that locale modifier.
// * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt
// input contexts would always silently fail to use the input method.
pub(crate) unsafe fn set_instantiate_callback(
xconn: &Arc<XConnection>,
client_data: ffi::XPointer,
) -> Result<(), XError> {
unsafe {
(xconn.xlib.XRegisterIMInstantiateCallback)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
Some(xim_instantiate_callback),
client_data,
)
};
xconn.check_errors()
}
pub(crate) unsafe fn unset_instantiate_callback(
xconn: &Arc<XConnection>,
client_data: ffi::XPointer,
) -> Result<(), XError> {
unsafe {
(xconn.xlib.XUnregisterIMInstantiateCallback)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
Some(xim_instantiate_callback),
client_data,
)
};
xconn.check_errors()
}
pub(crate) unsafe fn set_destroy_callback(
xconn: &Arc<XConnection>,
im: ffi::XIM,
inner: &ImeInner,
) -> Result<(), XError> {
unsafe {
xim_set_callback(
xconn,
im,
ffi::XNDestroyCallback_0.as_ptr() as *const _,
&inner.destroy_callback as *const _ as *mut _,
)
}
}
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
enum ReplaceImError {
// Boxed to prevent large error type
MethodOpenFailed(#[allow(dead_code)] Box<PotentialInputMethods>),
ContextCreationFailed(#[allow(dead_code)] ImeContextCreationError),
SetDestroyCallbackFailed(#[allow(dead_code)] XError),
}
// Attempt to replace current IM (which may or may not be presently valid) with a new one. This
// includes replacing all existing input contexts and free'ing resources as necessary. This only
// modifies existing state if all operations succeed.
unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
let xconn = unsafe { &(*inner).xconn };
let (new_im, is_fallback) = {
let new_im = unsafe { (*inner).potential_input_methods.open_im(xconn, None) };
let is_fallback = new_im.is_fallback();
(
new_im.ok().ok_or_else(|| {
ReplaceImError::MethodOpenFailed(Box::new(unsafe {
(*inner).potential_input_methods.clone()
}))
})?,
is_fallback,
)
};
// It's important to always set a destroy callback, since there's otherwise potential for us
// to try to use or free a resource that's already been destroyed on the server.
{
let result = unsafe { set_destroy_callback(xconn, new_im.im, &*inner) };
if result.is_err() {
let _ = unsafe { close_im(xconn, new_im.im) };
}
result
}
.map_err(ReplaceImError::SetDestroyCallbackFailed)?;
let mut new_contexts = HashMap::new();
for (window, old_context) in unsafe { (*inner).contexts.iter() } {
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
// Check if the IME was allowed on that context.
let is_allowed = old_context
.as_ref()
.map(|old_context| old_context.is_allowed())
.unwrap_or_default();
// We can't use the style from the old context here, since it may change on reload, so
// pick style from the new XIM based on the old state.
let style = if is_allowed {
new_im.preedit_style
} else {
new_im.none_style
};
let new_context = {
let result = unsafe {
ImeContext::new(
xconn,
new_im.im,
style,
*window,
spot,
(*inner).event_sender.clone(),
)
};
if result.is_err() {
let _ = unsafe { close_im(xconn, new_im.im) };
}
result.map_err(ReplaceImError::ContextCreationFailed)?
};
new_contexts.insert(*window, Some(new_context));
}
// If we've made it this far, everything succeeded.
unsafe {
let _ = (*inner).destroy_all_contexts_if_necessary();
let _ = (*inner).close_im_if_necessary();
(*inner).im = Some(new_im);
(*inner).contexts = new_contexts;
(*inner).is_destroyed = false;
(*inner).is_fallback = is_fallback;
}
Ok(())
}
pub unsafe extern "C" fn xim_instantiate_callback(
_display: *mut ffi::Display,
client_data: ffi::XPointer,
// This field is unsupplied.
_call_data: ffi::XPointer,
) {
let inner: *mut ImeInner = client_data as _;
if !inner.is_null() {
let xconn = unsafe { &(*inner).xconn };
match unsafe { replace_im(inner) } {
Ok(()) => unsafe {
let _ = unset_instantiate_callback(xconn, client_data);
(*inner).is_fallback = false;
},
Err(err) => unsafe {
if (*inner).is_destroyed {
// We have no usable input methods!
panic!("Failed to reopen input method: {err:?}");
}
},
}
}
}
// This callback is triggered when the input method is closed on the server end. When this
// happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been
// free'd (attempting to do so causes our connection to freeze).
pub unsafe extern "C" fn xim_destroy_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
// This field is unsupplied.
_call_data: ffi::XPointer,
) {
let inner: *mut ImeInner = client_data as _;
if !inner.is_null() {
unsafe { (*inner).is_destroyed = true };
let xconn = unsafe { &(*inner).xconn };
if unsafe { !(*inner).is_fallback } {
let _ = unsafe { set_instantiate_callback(xconn, client_data) };
// Attempt to open fallback input method.
match unsafe { replace_im(inner) } {
Ok(()) => unsafe { (*inner).is_fallback = true },
Err(err) => {
// We have no usable input methods!
panic!("Failed to open fallback input method: {err:?}");
}
}
}
}
}

View File

@@ -1,385 +0,0 @@
use std::ffi::CStr;
use std::os::raw::c_short;
use std::sync::Arc;
use std::{mem, ptr};
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
use super::{ffi, util, XConnection, XError};
/// IME creation error.
#[derive(Debug)]
pub enum ImeContextCreationError {
/// Got the error from Xlib.
XError(XError),
/// Got null pointer from Xlib but without exact reason.
Null,
}
/// The callback used by XIM preedit functions.
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);
/// Wrapper for creating XIM callbacks.
#[inline]
fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
XIMCallback {
client_data,
callback: Some(callback),
}
}
/// The server started preedit.
extern "C" fn preedit_start_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
_call_data: ffi::XPointer,
) -> i32 {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
client_data.text.clear();
client_data.cursor_pos = 0;
client_data
.event_sender
.send((client_data.window, ImeEvent::Start))
.expect("failed to send preedit start event");
-1
}
/// Done callback is used when the preedit should be hidden.
extern "C" fn preedit_done_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
_call_data: ffi::XPointer,
) {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
// Drop text buffer and reset cursor position on done.
client_data.text = Vec::new();
client_data.cursor_pos = 0;
client_data
.event_sender
.send((client_data.window, ImeEvent::End))
.expect("failed to send preedit end event");
}
fn calc_byte_position(text: &[char], pos: usize) -> usize {
text.iter()
.take(pos)
.fold(0, |byte_pos, text| byte_pos + text.len_utf8())
}
/// Preedit text information to be drawn inline by the client.
extern "C" fn preedit_draw_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
call_data: ffi::XPointer,
) {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) };
client_data.cursor_pos = call_data.caret as usize;
let chg_range =
call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize;
if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() {
log::warn!(
"invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}",
client_data.text.len(),
call_data.chg_first,
call_data.chg_length
);
return;
}
// NULL indicate text deletion
let mut new_chars = if call_data.text.is_null() {
Vec::new()
} else {
let xim_text = unsafe { &mut *(call_data.text) };
if xim_text.encoding_is_wchar > 0 {
return;
}
let new_text = unsafe { xim_text.string.multi_byte };
if new_text.is_null() {
return;
}
let new_text = unsafe { CStr::from_ptr(new_text) };
String::from(new_text.to_str().expect("Invalid UTF-8 String from IME"))
.chars()
.collect()
};
let mut old_text_tail = client_data.text.split_off(chg_range.end);
client_data.text.truncate(chg_range.start);
client_data.text.append(&mut new_chars);
client_data.text.append(&mut old_text_tail);
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
client_data
.event_sender
.send((
client_data.window,
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
))
.expect("failed to send preedit update event");
}
/// Handling of cursor movements in preedit text.
extern "C" fn preedit_caret_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
call_data: ffi::XPointer,
) {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) };
if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition {
client_data.cursor_pos = call_data.position as usize;
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
client_data
.event_sender
.send((
client_data.window,
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
))
.expect("failed to send preedit update event");
}
}
/// Struct to simplify callback creation and latter passing into Xlib XIM.
struct PreeditCallbacks {
start_callback: ffi::XIMCallback,
done_callback: ffi::XIMCallback,
draw_callback: ffi::XIMCallback,
caret_callback: ffi::XIMCallback,
}
impl PreeditCallbacks {
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
let start_callback = create_xim_callback(client_data, unsafe {
mem::transmute(preedit_start_callback as usize)
});
let done_callback = create_xim_callback(client_data, preedit_done_callback);
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
let draw_callback = create_xim_callback(client_data, preedit_draw_callback);
PreeditCallbacks {
start_callback,
done_callback,
caret_callback,
draw_callback,
}
}
}
struct ImeContextClientData {
window: ffi::Window,
event_sender: ImeEventSender,
text: Vec<char>,
cursor_pos: usize,
}
// XXX: this struct doesn't destroy its XIC resource when dropped.
// This is intentional, as it doesn't have enough information to know whether or not the context
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
// through `ImeInner`.
pub struct ImeContext {
pub(crate) ic: ffi::XIC,
pub(crate) ic_spot: ffi::XPoint,
pub(crate) style: Style,
// Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from
// there we keep the pointer to automatically deallocate it.
_client_data: Box<ImeContextClientData>,
}
impl ImeContext {
pub(crate) unsafe fn new(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: Style,
window: ffi::Window,
ic_spot: Option<ffi::XPoint>,
event_sender: ImeEventSender,
) -> Result<Self, ImeContextCreationError> {
let client_data = Box::into_raw(Box::new(ImeContextClientData {
window,
event_sender,
text: Vec::new(),
cursor_pos: 0,
}));
let ic = match style as _ {
Style::Preedit(style) => unsafe {
ImeContext::create_preedit_ic(
xconn,
im,
style,
window,
client_data as ffi::XPointer,
)
},
Style::Nothing(style) => unsafe {
ImeContext::create_nothing_ic(xconn, im, style, window)
},
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
}
.ok_or(ImeContextCreationError::Null)?;
xconn
.check_errors()
.map_err(ImeContextCreationError::XError)?;
let mut context = ImeContext {
ic,
ic_spot: ffi::XPoint { x: 0, y: 0 },
style,
_client_data: unsafe { Box::from_raw(client_data) },
};
// Set the spot location, if it's present.
if let Some(ic_spot) = ic_spot {
context.set_spot(xconn, ic_spot.x, ic_spot.y)
}
Ok(context)
}
unsafe fn create_none_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: XIMStyle,
window: ffi::Window,
) -> Option<ffi::XIC> {
let ic = unsafe {
(xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
)
};
(!ic.is_null()).then_some(ic)
}
unsafe fn create_preedit_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: XIMStyle,
window: ffi::Window,
client_data: ffi::XPointer,
) -> Option<ffi::XIC> {
let preedit_callbacks = PreeditCallbacks::new(client_data);
let preedit_attr = util::memory::XSmartPointer::new(xconn, unsafe {
(xconn.xlib.XVaCreateNestedList)(
0,
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
&(preedit_callbacks.start_callback) as *const _,
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
&(preedit_callbacks.done_callback) as *const _,
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
&(preedit_callbacks.caret_callback) as *const _,
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
&(preedit_callbacks.draw_callback) as *const _,
ptr::null_mut::<()>(),
)
})
.expect("XVaCreateNestedList returned NULL");
let ic = unsafe {
(xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
preedit_attr.ptr,
ptr::null_mut::<()>(),
)
};
(!ic.is_null()).then_some(ic)
}
unsafe fn create_nothing_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: XIMStyle,
window: ffi::Window,
) -> Option<ffi::XIC> {
let ic = unsafe {
(xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
)
};
(!ic.is_null()).then_some(ic)
}
pub(crate) fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
unsafe {
(xconn.xlib.XSetICFocus)(self.ic);
}
xconn.check_errors()
}
pub(crate) fn unfocus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
unsafe {
(xconn.xlib.XUnsetICFocus)(self.ic);
}
xconn.check_errors()
}
pub fn is_allowed(&self) -> bool {
!matches!(self.style, Style::None(_))
}
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
// are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
// window and couldn't be changed.
//
// For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
pub(crate) fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y {
return;
}
self.ic_spot = ffi::XPoint { x, y };
unsafe {
let preedit_attr = util::memory::XSmartPointer::new(
xconn,
(xconn.xlib.XVaCreateNestedList)(
0,
ffi::XNSpotLocation_0.as_ptr(),
&self.ic_spot,
ptr::null_mut::<()>(),
),
)
.expect("XVaCreateNestedList returned NULL");
(xconn.xlib.XSetICValues)(
self.ic,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
preedit_attr.ptr,
ptr::null_mut::<()>(),
);
}
}
}

View File

@@ -1,75 +0,0 @@
use std::{collections::HashMap, mem, sync::Arc};
use super::{ffi, XConnection, XError};
use super::{
context::ImeContext,
input_method::{InputMethod, PotentialInputMethods},
};
use crate::platform_impl::platform::x11::ime::ImeEventSender;
pub(crate) unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
unsafe { (xconn.xlib.XCloseIM)(im) };
xconn.check_errors()
}
pub(crate) unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), XError> {
unsafe { (xconn.xlib.XDestroyIC)(ic) };
xconn.check_errors()
}
pub(crate) struct ImeInner {
pub xconn: Arc<XConnection>,
pub im: Option<InputMethod>,
pub potential_input_methods: PotentialInputMethods,
pub contexts: HashMap<ffi::Window, Option<ImeContext>>,
// WARNING: this is initially zeroed!
pub destroy_callback: ffi::XIMCallback,
pub event_sender: ImeEventSender,
// Indicates whether or not the the input method was destroyed on the server end
// (i.e. if ibus/fcitx/etc. was terminated/restarted)
pub is_destroyed: bool,
pub is_fallback: bool,
}
impl ImeInner {
pub(crate) fn new(
xconn: Arc<XConnection>,
potential_input_methods: PotentialInputMethods,
event_sender: ImeEventSender,
) -> Self {
ImeInner {
xconn,
im: None,
potential_input_methods,
contexts: HashMap::new(),
destroy_callback: unsafe { mem::zeroed() },
event_sender,
is_destroyed: false,
is_fallback: false,
}
}
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
if !self.is_destroyed && self.im.is_some() {
unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true)
} else {
Ok(false)
}
}
pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result<bool, XError> {
if !self.is_destroyed {
unsafe { destroy_ic(&self.xconn, ic) }.map(|_| true)
} else {
Ok(false)
}
}
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
for context in self.contexts.values().flatten() {
unsafe { self.destroy_ic_if_necessary(context.ic)? };
}
Ok(!self.is_destroyed)
}
}

Some files were not shown because too many files have changed in this diff Show More