Compare commits

..

2 Commits

Author SHA1 Message Date
Christophe Massolin
e004bd2bb3 Gamepad device events - Web/WASM (#1414)
Add gamepad support for stdweb and web-sys, as well as web-specific gamepad examples. 

* [web] Fix compilation error from device api

* [wasm] Apply device api changes

* [wasm] Format and cleanup

* [wasm32] Implement gamepad connections

* [wasm] Harmonize

* [Test] Made some tests with wasm-pack

* Quick fix instant non supporting Hash trait

* Fix on_received_character

* [web_sys] Split add_event and add_window_event

* [web] split device implementations

* Update tests/web...still does not work

* [tests/web] do not ignore index.html

* [web/web_sys] split canvas and window

* [tests/web] enable stack trace

* [web] fix borrowmut

* [web_sys] fix gamepad registration

* [web] harmonize naming

* [web_sys] create global emitter

* [web] implement gamepad buttons

* [web] implement gamepad axis

* [web] cleanup

* [web] update test

* [web] move tests/web to examples/web

* [web] axis does produce stick event

* [web] Support Stick event

* [web] implement gamepad to stdweb

* [web] rename examples/web to examples/wasm

* [web/web-sys] Move gamepad_manager from backend

* [web/web_sys] implement EventLoop::gamepads

* [web/web_sys] Drain gamepad events

* [web/stdweb] apply web_sys changes

* [web] update web/examples

* [web] move gamepads code to gamepad_manager

* [web] simplify and optimise

* [web] replace EventCode to GamepadAxis and GamepadButton structs

* [web] reuse gamepad events due to chrome issue

* [web] rumble does not work

* [web/stdweb] try debugging

* [web] fix Chrome gamepad not updated

* [web/stdweb] created an example

* [examples] fix paths

* fix warnings

* [web/examples] update comments

* [web/stdweb] add experimental support to vibrate()

* [web] add CR
2020-03-03 09:56:11 -05:00
Osspial
0729074ce3 Overhaul device events API and add gamepad support on Windows (#804)
* Initial implementation

* Corrected RAWINPUT buffer sizing

* Mostly complete XInput implementation

* XInput triggers

* Add preliminary CHANGELOG entry.

* match unix common API to evl 2.0

* wayland: eventloop2.0

* make EventLoopProxy require T: 'static

* Revamp device event API, as well as several misc. fixes on Windows:

* When you have multiple windows, you no longer receive duplicate device
  events
* Mouse Device Events now send X-button input
* Mouse Device Events now send horizontal scroll wheel input

* Add MouseEvent documentation and Device ID debug passthrough

* Improve type safety on get_raw_input_data

* Remove button_id field from MouseEvent::Button in favor of utton

* Remove regex dependency on Windows

* Remove axis filtering in XInput

* Make gamepads not use lazy_static

* Publicly expose gamepad rumble

* Unstack DeviceEvent and fix examples/tests

* Add HANDLE retrieval method to DeviceExtWindows

* Add distinction between non-joystick axes and joystick axes.

This helps with properly calculating the deadzone for controller
joysticks. One potential issue is that the `Stick` variant isn't used
for *all* joysticks, which could be potentially confusing - for example,
raw input joysticks will never use the `Stick` variant because we don't
understand the semantic meaning of raw input joystick axes.

* Add ability to get gamepad port

* Fix xinput controller hot swapping

* Add functions for enumerating attached devices

* Clamp input to [0.0, 1.0] on gamepad rumble

* Expose gamepad rumble errors

* Add method to check if device is still connected

* Add docs

* Rename AxisHint and ButtonHint to GamepadAxis and GamepadButton

* Add CHANGELOG entry

* Update CHANGELOG.md

* Add HidId and MovedAbsolute

* Fix xinput deprecation warnings

* Add ability to retrieve gamepad battery level

* Fix weird imports in gamepad example

* Update CHANGELOG.md

* Resolve francesca64 comments
2019-11-29 16:50:50 -05:00
196 changed files with 14650 additions and 21662 deletions

View File

@@ -1,2 +0,0 @@
[alias]
run-wasm = ["run", "--release", "--package", "run-wasm", "--"]

2
.gitattributes vendored
View File

@@ -20,3 +20,5 @@
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
/CHANGELOG.md merge=union

View File

@@ -1,4 +1,7 @@
- [ ] Tested on all platforms changed
- [ ] Compilation warnings were addressed
- [ ] `cargo fmt` has been run on this branch
- [ ] `cargo doc` builds successfully
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
- [ ] Created or updated an example program if it would help users understand this functionality

View File

@@ -1,122 +0,0 @@
name: CI
on:
pull_request:
push:
branches: [master]
jobs:
Check_Formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
components: rustfmt
- name: Check Formatting
run: cargo +stable fmt --all -- --check
tests:
name: Tests
strategy:
fail-fast: false
matrix:
rust_version: [1.57.0, stable, nightly]
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
- { target: i686-pc-windows-msvc, os: windows-latest, }
- { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' }
- { target: x86_64-apple-darwin, os: macos-latest, }
- { target: x86_64-apple-ios, os: macos-latest, }
- { target: aarch64-apple-ios, os: macos-latest, }
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
# doesn't currently work on Linux.
- { target: wasm32-unknown-unknown, os: windows-latest, }
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
PKG_CONFIG_ALLOW_CROSS: 1
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
OPTIONS: ${{ matrix.platform.options }}
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
CMD: ${{ matrix.platform.cmd }}
RUSTDOCFLAGS: -Dwarnings
runs-on: ${{ matrix.platform.os }}
steps:
- uses: actions/checkout@v2
# Used to cache cargo-web
- name: Cache cargo folder
uses: actions/cache@v1
with:
path: ~/.cargo
key: ${{ matrix.platform.target }}-cargo-${{ matrix.rust_version }}
- uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
components: clippy
- name: Setup NDK path
shell: bash
# "Temporary" workaround until https://github.com/actions/virtual-environments/issues/5879#issuecomment-1195156618
# gets looked into.
run: echo "ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME" >> $GITHUB_ENV
- name: Install Linux dependencies
if: (matrix.platform.os == 'ubuntu-latest')
run: sudo apt-get update && sudo apt-get install pkg-config cmake libfreetype6-dev libfontconfig1-dev
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install g++-multilib gcc-multilib libfreetype6-dev:i386 libfontconfig1-dev:i386
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android')
run: cargo install cargo-apk
- name: Check documentation
shell: bash
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
- name: Build
shell: bash
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Build tests
shell: bash
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Run tests
shell: bash
if: (
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32'))
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Lint with clippy
shell: bash
if: (matrix.rust_version == '1.57.0') && !contains(matrix.platform.options, '--no-default-features')
run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings
- name: Build with serde enabled
shell: bash
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
- name: Build tests with serde enabled
shell: bash
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
- name: Run tests with serde enabled
shell: bash
if: (
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32'))
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES

View File

@@ -1,18 +0,0 @@
name: Publish
on:
push:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
Publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
components: rustfmt
- name: Publish to crates.io
run: cargo publish --token ${{ secrets.cratesio_token }}

3
.gitignore vendored
View File

@@ -2,9 +2,10 @@ Cargo.lock
target/
rls/
.vscode/
.cargo/
util/
*~
*.wasm
*.ts
*.js
#*#
.DS_Store

104
.travis.yml Normal file
View File

@@ -0,0 +1,104 @@
language: rust
matrix:
include:
# Linux 32bit
- env: TARGET=i686-unknown-linux-gnu
os: linux
rust: nightly
addons:
apt:
# Cross compiler and cross compiled C libraries
packages: &i686_packages
- gcc-multilib
- env: TARGET=i686-unknown-linux-gnu
os: linux
rust: stable
addons:
apt:
packages: *i686_packages
# Linux 64bit
- env: TARGET=x86_64-unknown-linux-gnu
os: linux
rust: nightly
- env: TARGET=x86_64-unknown-linux-gnu
os: linux
rust: stable
# macOS
- env: TARGET=x86_64-apple-darwin
os: osx
rust: nightly
- env: TARGET=x86_64-apple-darwin
os: osx
rust: stable
# iOS x86_64
- env: TARGET=x86_64-apple-ios
os: osx
rust: nightly
- env: TARGET=x86_64-apple-ios
os: osx
rust: stable
# iOS armv7
- env: TARGET=armv7-apple-ios
os: osx
rust: nightly
- env: TARGET=armv7-apple-ios
os: osx
rust: stable
# iOS arm64
- env: TARGET=aarch64-apple-ios
os: osx
rust: nightly
- env: TARGET=aarch64-apple-ios
os: osx
rust: stable
# wasm stdweb
- env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb
os: linux
rust: stable
- env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb
os: linux
rust: nightly
# wasm web-sys
- env: TARGET=wasm32-unknown-unknown FEATURES=web-sys
os: linux
rust: stable
- env: TARGET=wasm32-unknown-unknown FEATURES=web-sys
os: linux
rust: nightly
install:
- rustup self update
- rustup target add $TARGET; true
- rustup toolchain install stable
- rustup component add rustfmt --toolchain stable
script:
- cargo +stable fmt --all -- --check
# Ensure that the documentation builds properly.
- cargo doc --no-deps
# Install cargo-web to build stdweb
- if [[ $WEB = "web" ]]; then cargo install -f cargo-web; fi
# Build without serde then with serde
- if [[ -z "$FEATURES" ]]; then
cargo $WEB build --target $TARGET --verbose;
else
cargo $WEB build --target $TARGET --features $FEATURES --verbose;
fi
- cargo $WEB build --target $TARGET --features serde,$FEATURES --verbose
# Running iOS apps on macOS requires the Simulator so we skip that for now
# The web targets also don't support running tests
- if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --verbose; fi
- if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --features serde --verbose; fi
after_success:
- |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&
cargo publish --token ${CRATESIO_TOKEN}

View File

@@ -1,352 +1,5 @@
# Changelog
All notable changes to this project will be documented in this file.
Please keep one empty line before and after all headers. (This is required for `git` to produce a conflict when a release is made while a PR is open and the PR's changelog entry would go into the wrong section).
And please only add new entries to the top of this list, right below the `# Unreleased` header.
# Unreleased
# 0.27.1 (2022-07-30)
- The minimum supported Rust version was lowered to `1.57.0` and now explicitly tested.
- On X11, fix crash on start due to inability to create an IME context without any preedit.
# 0.27.0 (2022-07-26)
- On Windows, fix hiding a maximized window.
- On Android, `ndk-glue`'s `NativeWindow` lock is now held between `Event::Resumed` and `Event::Suspended`.
- On Web, added `EventLoopExtWebSys` with a `spawn` method to start the event loop without throwing an exception.
- Added `WindowEvent::Occluded(bool)`, currently implemented on macOS and X11.
- On X11, fix events for caps lock key not being sent
- Build docs on `docs.rs` for iOS and Android as well.
- **Breaking:** Removed the `WindowAttributes` struct, since all its functionality is accessible from `WindowBuilder`.
- Added `WindowBuilder::transparent` getter to check if the user set `transparent` attribute.
- On macOS, Fix emitting `Event::LoopDestroyed` on CMD+Q.
- On macOS, fixed an issue where having multiple windows would prevent run_return from ever returning.
- On Wayland, fix bug where the cursor wouldn't hide in GNOME.
- On macOS, Windows, and Wayland, add `set_cursor_hittest` to let the window ignore mouse events.
- On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`.
- On Windows, added `EventLoopBuilderExtWindows::with_msg_hook`.
- On Windows, remove internally unique DC per window.
- On macOS, remove the need to call `set_ime_position` after moving the window.
- Added `Window::is_visible`.
- Added `Window::is_resizable`.
- Added `Window::is_decorated`.
- On X11, fix for repeated event loop iteration when `ControlFlow` was `Wait`
- On X11, fix scale factor calculation when the only monitor is reconnected
- On Wayland, report unaccelerated mouse deltas in `DeviceEvent::MouseMotion`.
- On Web, a focused event is manually generated when a click occurs to emulate behaviour of other backends.
- **Breaking:** Bump `ndk` version to 0.6, ndk-sys to `v0.3`, `ndk-glue` to `0.6`.
- Remove no longer needed `WINIT_LINK_COLORSYNC` environment variable.
- **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs.
- Add `EventLoopBuilder`, which allows you to create and tweak the settings of an event loop before creating it.
- Deprecated `EventLoop::with_user_event`; use `EventLoopBuilder::with_user_event` instead.
- **Breaking:** Replaced `EventLoopExtMacOS` with `EventLoopBuilderExtMacOS` (which also has renamed methods).
- **Breaking:** Replaced `EventLoopExtWindows` with `EventLoopBuilderExtWindows` (which also has renamed methods).
- **Breaking:** Replaced `EventLoopExtUnix` with `EventLoopBuilderExtUnix` (which also has renamed methods).
- **Breaking:** The platform specific extensions for Windows `winit::platform::windows` have changed. All `HANDLE`-like types e.g. `HWND` and `HMENU` were converted from winapi types or `*mut c_void` to `isize`. This was done to be consistent with the type definitions in windows-sys and to not expose internal dependencies.
- The internal bindings to the [Windows API](https://docs.microsoft.com/en-us/windows/) were changed from the unofficial [winapi](https://github.com/retep998/winapi-rs) bindings to the official Microsoft [windows-sys](https://github.com/microsoft/windows-rs) bindings.
- On Wayland, fix polling during consecutive `EventLoop::run_return` invocations.
- On Windows, fix race issue creating fullscreen windows with `WindowBuilder::with_fullscreen`
- On Android, `virtual_keycode` for `KeyboardInput` events is now filled in where a suitable match is found.
- Added helper methods on `ControlFlow` to set its value.
- On Wayland, fix `TouchPhase::Ended` always reporting the location of the first touch down, unless the compositor
sent a cancel or frame event.
- On iOS, send `RedrawEventsCleared` even if there are no redraw events, consistent with other platforms.
- **Breaking:** Replaced `Window::with_app_id` and `Window::with_class` with `Window::with_name` on `WindowBuilderExtUnix`.
- On Wayland, fallback CSD was replaced with proper one:
- `WindowBuilderExtUnix::with_wayland_csd_theme` to set color theme in builder.
- `WindowExtUnix::wayland_set_csd_theme` to set color theme when creating a window.
- `WINIT_WAYLAND_CSD_THEME` env variable was added, it can be used to set "dark"/"light" theme in apps that don't expose theme setting.
- `wayland-csd-adwaita` feature that enables proper CSD with title rendering using FreeType system library.
- `wayland-csd-adwaita-notitle` feature that enables CSD but without title rendering.
- On Wayland and X11, fix window not resizing with `Window::set_inner_size` after calling `Window:set_resizable(false)`.
- On Windows, fix wrong fullscreen monitors being recognized when handling WM_WINDOWPOSCHANGING messages
- **Breaking:** Added new `WindowEvent::Ime` supported on desktop platforms.
- Added `Window::set_ime_allowed` supported on desktop platforms.
- **Breaking:** IME input on desktop platforms won't be received unless it's explicitly allowed via `Window::set_ime_allowed` and new `WindowEvent::Ime` events are handled.
- On macOS, `WindowEvent::Resized` is now emitted in `frameDidChange` instead of `windowDidResize`.
- **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level.
- Implemented `Default` on `EventLoop<()>`.
- Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`.
- **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior.
- On Wayland, add support for `Window::set_cursor_position`.
- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value.
- `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once.
- Added `From<u64>` for `WindowId` and `From<WindowId>` for `u64`.
- Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate.
- **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision.
- On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated.
- On Windows, fix focus events being sent to inactive windows.
- **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`.
- On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors comming from Xlib.
- On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`.
- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability.
- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down.
# 0.26.1 (2022-01-05)
- Fix linking to the `ColorSync` framework on macOS 10.7, and in newer Rust versions.
- On Web, implement cursor grabbing through the pointer lock API.
- On X11, add mappings for numpad comma, numpad enter, numlock and pause.
- On macOS, fix Pinyin IME input by reverting a change that intended to improve IME.
- On Windows, fix a crash with transparent windows on Windows 11.
# 0.26.0 (2021-12-01)
- Update `raw-window-handle` to `v0.4`. This is _not_ a breaking change, we still implement `HasRawWindowHandle` from `v0.3`, see [rust-windowing/raw-window-handle#74](https://github.com/rust-windowing/raw-window-handle/pull/74). Note that you might have to run `cargo update -p raw-window-handle` after upgrading.
- On X11, bump `mio` to 0.8.
- On Android, fixed `WindowExtAndroid::config` initially returning an empty `Configuration`.
- On Android, fixed `Window::scale_factor` and `MonitorHandle::scale_factor` initially always returning 1.0.
- On X11, select an appropriate visual for transparency if is requested
- On Wayland and X11, fix diagonal window resize cursor orientation.
- On macOS, drop the event callback before exiting.
- On Android, implement `Window::request_redraw`
- **Breaking:** On Web, remove the `stdweb` backend.
- Added `Window::focus_window`to bring the window to the front and set input focus.
- On Wayland and X11, implement `is_maximized` method on `Window`.
- On Windows, prevent ghost window from showing up in the taskbar after either several hours of use or restarting `explorer.exe`.
- On macOS, fix issue where `ReceivedCharacter` was not being emitted during some key repeat events.
- On Wayland, load cursor icons `hand2` and `hand1` for `CursorIcon::Hand`.
- **Breaking:** On Wayland, Theme trait and its support types are dropped.
- On Wayland, bump `smithay-client-toolkit` to 0.15.1.
- On Wayland, implement `request_user_attention` with `xdg_activation_v1`.
- On X11, emit missing `WindowEvent::ScaleFactorChanged` when the only monitor gets reconnected.
- On X11, if RANDR based scale factor is higher than 20 reset it to 1
- On Wayland, add an enabled-by-default feature called `wayland-dlopen` so users can opt out of using `dlopen` to load system libraries.
- **Breaking:** On Android, bump `ndk` and `ndk-glue` to 0.5.
- On Windows, increase wait timer resolution for more accurate timing when using `WaitUntil`.
- On macOS, fix native file dialogs hanging the event loop.
- On Wayland, implement a workaround for wrong configure size when using `xdg_decoration` in `kwin_wayland`
- On macOS, fix an issue that prevented the menu bar from showing in borderless fullscreen mode.
- On X11, EINTR while polling for events no longer causes a panic. Instead it will be treated as a spurious wakeup.
# 0.25.0 (2021-05-15)
- **Breaking:** On macOS, replace `WindowBuilderExtMacOS::with_activation_policy` with `EventLoopExtMacOS::set_activation_policy`
- On macOS, wait with activating the application until the application has initialized.
- On macOS, fix creating new windows when the application has a main menu.
- On Windows, fix fractional deltas for mouse wheel device events.
- On macOS, fix segmentation fault after dropping the main window.
- On Android, `InputEvent::KeyEvent` is partially implemented providing the key scancode.
- Added `is_maximized` method to `Window`.
- On Windows, fix bug where clicking the decoration bar would make the cursor blink.
- On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor.
- On macOS, wake up the event loop immediately when a redraw is requested.
- On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600).
- On Windows, fix bug causing mouse capture to not be released.
- On Windows, fix fullscreen not preserving minimized/maximized state.
- On Android, unimplemented events are marked as unhandled on the native event loop.
- On Windows, added `WindowBuilderExtWindows::with_menu` to set a custom menu at window creation time.
- On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`.
- On macOS, fix objects captured by the event loop closure not being dropped on panic.
- On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme.
- On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements.
- Added `WindowBuilder::with_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11.
- Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland.
- On X11, bump `mio` to 0.7.
- On Windows, added `WindowBuilderExtWindows::with_owner_window` to allow creating popup windows.
- On Windows, added `WindowExtWindows::set_enable` to allow creating modal popup windows.
- On macOS, emit `RedrawRequested` events immediately while the window is being resized.
- Implement `Default`, `Hash`, and `Eq` for `LogicalPosition`, `PhysicalPosition`, `LogicalSize`, and `PhysicalSize`.
- On macOS, initialize the Menu Bar with minimal defaults. (Can be prevented using `enable_default_menu_creation`)
- On macOS, change the default behavior for first click when the window was unfocused. Now the window becomes focused and then emits a `MouseInput` event on a "first mouse click".
- Implement mint (math interoperability standard types) conversions (under feature flag `mint`).
# 0.24.0 (2020-12-09)
- On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory.
- On Windows, implement `Window::set_ime_position`.
- **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`.
- **Breaking:** On Windows, renamed `WindowBuilderExtWindows::is_dark_mode` to `theme`.
- On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme.
- On Windows, fix bug causing message boxes to appear delayed.
- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false.
- On Windows, fix Alt-Tab behaviour by removing borderless fullscreen "always on top" flag.
- On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions.
- **Breaking:** On Windows, include prefix byte in scancodes.
- On Wayland, fix window not being resizeable when using `WindowBuilder::with_min_inner_size`.
- On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland.
- On Windows, fix use-after-free crash during window destruction.
- On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input.
- On macOS, fix compilation when targeting aarch64.
- On X11, fix `Window::request_redraw` not waking the event loop.
- On Wayland, the keypad arrow keys are now recognized.
- **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`.
- Added `request_user_attention` method to `Window`.
- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`.
- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`.
- On Wayland, default font size in CSD increased from 11 to 17.
- On Windows, fix bug causing message boxes to appear delayed.
- On Android, support multi-touch.
- On Wayland, extra mouse buttons are not dropped anymore.
- **Breaking**: `MouseButton::Other` now uses `u16`.
# 0.23.0 (2020-10-02)
- On iOS, fixed support for the "Debug View Heirarchy" feature in Xcode.
- On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop.
- On Unix, X11 and Wayland are now optional features (enabled by default)
- On X11, fix deadlock when calling `set_fullscreen_inner`.
- On Web, prevent the webpage from scrolling when the user is focused on a winit canvas
- On Web, calling `window.set_cursor_icon` no longer breaks HiDPI scaling
- On Windows, drag and drop is now optional (enabled by default) and can be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`.
- On Wayland, fix deadlock when calling to `set_inner_size` from a callback.
- On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`.
- On android added support for `run_return`.
- On MacOS, Fixed fullscreen and dialog support for `run_return`.
- On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops.
- On Web, use mouse events if pointer events aren't supported. This affects Safari.
- On Windows, `set_ime_position` is now a no-op instead of a runtime crash.
- On Android, `set_fullscreen` is now a no-op instead of a runtime crash.
- On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash.
- On Android, fix `ControlFlow::Poll` not polling the Android event queue.
- On macOS, add `NSWindow.hasShadow` support.
- On Web, fix vertical mouse wheel scrolling being inverted.
- On Web, implement mouse capturing for click-dragging out of the canvas.
- On Web, fix `ControlFlow::Exit` not properly handled.
- On Web (web-sys only), send `WindowEvent::ScaleFactorChanged` event when `window.devicePixelRatio` is changed.
- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error.
- **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`.
- On NetBSD, fixed crash due to incorrect detection of the main thread.
- **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract`.
- On macOS, fix inverted horizontal scroll.
- **Breaking:** `current_monitor` now returns `Option<MonitorHandle>`.
- **Breaking:** `primary_monitor` now returns `Option<MonitorHandle>`.
- On macOS, updated core-* dependencies and cocoa.
- Bump `parking_lot` to 0.11
- On Android, bump `ndk`, `ndk-sys` and `ndk-glue` to 0.2. Checkout the new ndk-glue main proc attribute.
- On iOS, fixed starting the app in landscape where the view still had portrait dimensions.
- Deprecate the stdweb backend, to be removed in a future release
- **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`.
- Added `Asterisk` and `Plus` virtual key codes.
- On Web (web-sys only), the `Event::LoopDestroyed` event is correctly emitted when leaving the page.
- On Web, the `WindowEvent::Destroyed` event now gets emitted when a `Window` is dropped.
- On Web (web-sys only), the event listeners are now removed when a `Window` is dropped or when the event loop is destroyed.
- On Web, the event handler closure passed to `EventLoop::run` now gets dropped after the event loop is destroyed.
- **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped.
- On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called.
- **Breaking:** `Fullscreen` enum now uses `Borderless(Option<MonitorHandle>)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor.
- On MacOS, fix `WindowEvent::Moved` ignoring the scale factor.
- On Wayland, add missing virtual keycodes.
- On Wayland, implement proper `set_cursor_grab`.
- On Wayland, the cursor will use similar icons if the requested one isn't available.
- On Wayland, right clicking on client side decorations will request application menu.
- On Wayland, fix tracking of window size after state changes.
- On Wayland, fix client side decorations not being hidden properly in fullscreen.
- On Wayland, fix incorrect size event when entering fullscreen with client side decorations.
- On Wayland, fix `resizable` attribute not being applied properly on startup.
- On Wayland, fix disabled repeat rate not being handled.
- On Wayland, fix decoration buttons not working after tty switch.
- On Wayland, fix scaling not being applied on output re-enable.
- On Wayland, fix crash when `XCURSOR_SIZE` is `0`.
- On Wayland, fix pointer getting created in some cases without pointer capability.
- On Wayland, on kwin, fix space between window and decorations on startup.
- **Breaking:** On Wayland, `Theme` trait was reworked.
- On Wayland, disable maximize button for non-resizable window.
- On Wayland, added support for `set_ime_position`.
- On Wayland, fix crash on startup since GNOME 3.37.90.
- On X11, fix incorrect modifiers state on startup.
# 0.22.2 (2020-05-16)
- Added Clone implementation for 'static events.
- On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`.
- On Windows, fix `WindowBuilder::with_maximized` being ignored.
- On Android, minimal platform support.
- On iOS, touch positions are now properly converted to physical pixels.
- On macOS, updated core-* dependencies and cocoa
# 0.22.1 (2020-04-16)
- On X11, fix `ResumeTimeReached` being fired too early.
- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame`
- On Web, fix a possible panic during event handling
- On macOS, fix `EventLoopProxy` leaking memory for every instance.
# 0.22.0 (2020-03-09)
- On Windows, fix minor timing issue in wait_until_time_or_msg
- On Windows, rework handling of request_redraw() to address panics.
- On macOS, fix `set_simple_screen` to remember frame excluding title bar.
- On Wayland, fix coordinates in touch events when scale factor isn't 1.
- On Wayland, fix color from `close_button_icon_color` not applying.
- Ignore locale if unsupported by X11 backend
- On Wayland, Add HiDPI cursor support
- On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change.
- Fix `Event::to_static` returning `None` for user events.
- On Wayland, Hide CSD for fullscreen windows.
- On Windows, ignore spurious mouse move messages.
- **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`.
- On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource
- Add `BadIcon::OsError` variant for when OS icon functionality fails
- On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode
- Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
- on macOS, fix incorrect ReceivedCharacter events for some key combinations.
- **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`.
- On macOS, a mouse motion event is now generated before every mouse click.
# 0.21.0 (2020-02-04)
- On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows.
- On macOS, fix set_minimized(true) works only with decorations.
- On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`.
- On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
- On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates.
- On macOS, fix issue where unbundled applications would sometimes open without being focused.
- On macOS, fix `run_return` does not return unless it receives a message.
- On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop.
- On X11, fix deadlock on window state when handling certain window events.
- `WindowBuilder` now implements `Default`.
- **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends
- On Wayland, fix coordinates in mouse events when scale factor isn't 1
- On Web, add the ability to provide a custom canvas
- **Breaking:** On Wayland, the `WaylandTheme` struct has been replaced with a `Theme` trait, allowing for extra configuration
# 0.20.0 (2020-01-05)
- On X11, fix `ModifiersChanged` emitting incorrect modifier change events
- **Breaking**: Overhaul how Winit handles DPI:
- Window functions and events now return `PhysicalSize` instead of `LogicalSize`.
- Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types.
- `hidpi_factor` has been renamed to `scale_factor`.
- `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS
resizes the window in response to the change.
- On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`.
- `Size` and `Position` types are now generic over their exact pixel type.
# 0.20.0 Alpha 6 (2020-01-03)
- On macOS, fix `set_cursor_visible` hides cursor outside of window.
- On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size.
- On macOS, fix error when `set_fullscreen` is called during fullscreen transition.
- On all platforms except mobile and WASM, implement `Window::set_minimized`.
- On X11, fix `CursorEntered` event being generated for non-winit windows.
- On macOS, fix crash when starting maximized without decorations.
- On macOS, fix application not terminating on `run_return`.
- On Wayland, fix cursor icon updates on window borders when using CSD.
- On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode.
- On Windows, theme the title bar according to whether the system theme is "Light" or "Dark".
- Added `WindowEvent::ThemeChanged` variant to handle changes to the system theme. Currently only implemented on Windows.
- **Breaking**: Changes to the `RedrawRequested` event (#1041):
- `RedrawRequested` has been moved from `WindowEvent` to `Event`.
- `EventsCleared` has been renamed to `MainEventsCleared`.
- `RedrawRequested` is now issued only after `MainEventsCleared`.
- `RedrawEventsCleared` is issued after each set of `RedrawRequested` events.
- Implement synthetic window focus key events on Windows.
- **Breaking**: Change `ModifiersState` to a `bitflags` struct.
- On Windows, implement `VirtualKeyCode` translation for `LWin` and `RWin`.
- On Windows, fix closing the last opened window causing `DeviceEvent`s to stop getting emitted.
- On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior.
- Add `DeviceEvent::ModifiersChanged`.
- Deprecate `modifiers` fields in other events in favor of `ModifiersChanged`.
- On X11, `WINIT_HIDPI_FACTOR` now dominates `Xft.dpi` when picking DPI factor for output.
- On X11, add special value `randr` for `WINIT_HIDPI_FACTOR` to make winit use self computed DPI factor instead of the one from `Xft.dpi`.
# 0.20.0 Alpha 5 (2019-12-09)
- On macOS, fix application termination on `ControlFlow::Exit`
- On Windows, fix missing `ReceivedCharacter` events when Alt is held.
- On macOS, stop emitting private corporate characters in `ReceivedCharacter` events.
@@ -355,13 +8,6 @@ And please only add new entries to the top of this list, right below the `# Unre
- On X11, fix key modifiers being incorrectly reported.
- On X11, fix window creation hanging when another window is fullscreen.
- On Windows, fix focusing unfocused windows when switching from fullscreen to windowed.
- On X11, fix reporting incorrect DPI factor when waking from suspend.
- Change `EventLoopClosed` to contain the original event.
- **Breaking**: Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`,
indicating that the event is generated by winit.
- On X11, generate synthetic key events for keys held when a window gains or loses focus.
- On X11, issue a `CursorMoved` event when a `Touch` event occurs,
as X11 implicitly moves the cursor for such events.
# 0.20.0 Alpha 4 (2019-10-18)
@@ -389,7 +35,6 @@ And please only add new entries to the top of this list, right below the `# Unre
- On X11, return dummy monitor data to avoid panicking when no monitors exist.
- On X11, prevent stealing input focus when creating a new window.
Only steal input focus when entering fullscreen mode.
- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced
- On Wayland, add support for set_cursor_visible and set_cursor_grab.
- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced.
- Removed `derivative` crate dependency.
@@ -406,7 +51,6 @@ And please only add new entries to the top of this list, right below the `# Unre
reduces the potential for cross-platform compatibility gotchyas.
- On Windows and Linux X11/Wayland, add platform-specific functions for creating an `EventLoop` outside the main thread.
- On Wayland, drop resize events identical to the current window size.
- On Windows, fix window rectangle not getting set correctly on high-DPI systems.
# 0.20.0 Alpha 3 (2019-08-14)
@@ -420,7 +64,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
consists of `Fullscreen::Exclusive(VideoMode)` and
`Fullscreen::Borderless(MonitorHandle)` variants.
- Adds support for exclusive fullscreen mode.
- Adds support for exclusive fullscreen mode.
- On iOS, add support for hiding the home indicator.
- On iOS, add support for deferring system gestures.
- On iOS, fix a crash that occurred while acquiring a monitor's name.
@@ -506,6 +150,20 @@ and `WindowEvent::HoveredFile`.
- On Windows, fix initial dimensions of a fullscreen window.
- On Windows, Fix transparent borderless windows rendering wrong.
- Improve event API documentation.
- Overhaul device event API:
- **Breaking**: `Event::DeviceEvent` split into `MouseEvent`, `KeyboardEvent`, and `GamepadEvent`.
- **Breaking**: Remove `DeviceEvent::Text` variant.
- **Breaking**: `DeviceId` split into `MouseId`, `KeyboardId`, and `GamepadHandle`.
- **Breaking**: Removed device IDs from `WindowEvent` variants.
- Add `enumerate` function on device ID types to list all attached devices of that type.
- Add `is_connected` function on device ID types check if the specified device is still available.
- **Breaking**: On Windows, rename `DeviceIdExtWindows` to `DeviceExtWindows`.
- Add `handle` function to retrieve the underlying `HANDLE`.
- On Windows, fix duplicate device events getting sent if Winit managed multiple windows.
- On Windows, raw mouse events now report Mouse4 and Mouse5 presses and releases.
- Added gamepad support on Windows via raw input and XInput.
# Version 0.19.1 (2019-04-08)
- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.
@@ -619,7 +277,7 @@ and `WindowEvent::HoveredFile`.
# Version 0.16.1 (2018-07-02)
- Added logging through `log`. Logging will become more extensive over time.
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This _greatly_ cuts back on unsightly auto-resizing that would occur immediately after window creation.
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This *greatly* cuts back on unsightly auto-resizing that would occur immediately after window creation.
- Fixed X11 backend compilation for environments where `c_char` is unsigned.
# Version 0.16.0 (2018-06-25)
@@ -769,7 +427,7 @@ and `WindowEvent::HoveredFile`.
# Version 0.10.1 (2018-02-05)
_Yanked_
*Yanked*
# Version 0.10.0 (2017-12-27)

View File

@@ -20,16 +20,12 @@ your description of the issue as detailed as possible:
When making a code contribution to winit, before opening your pull request, please make sure that:
- your patch builds with Winit's minimal supported rust version - Rust 1.57.0.
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit
- you left comments in your code explaining any part that is not straightforward, so that the
maintainers and future contributors don't have to try to guess what your code is supposed to do
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users.
You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will
handle those for you when merging (see below).
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users
- if your PR affects the platform compatibility of one or more features or adds another feature, the
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
should be updated.
@@ -38,26 +34,9 @@ Once your PR is open, you can ask for review by a maintainer of your platform. W
is that a PR must be approved by at least two maintainers of winit before being merged, including
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
Once your PR is deemed ready, the merging maintainer will take care of resolving conflicts in
`CHANGELOG.md` (but you must resolve other conflicts yourself). Doing this requires that you check the
"give contributors write access to the branch" checkbox when creating the PR.
## Maintainers & Testers
The current [list of testers and contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors)
can be found on the Wiki.
If you are interested in contributing or testing on a platform, please add yourself to that table!
## Making a new release
If you believe a new release is warranted, you can make a pull-request with:
- An updated version number (remember to change the version everywhere it is used).
- A new section in the changelog (below the `# Unreleased` section).
This gives contributors an opportunity to squeeze in an extra PR or two that they feel is valuable
enough to warrant blocking the release a little.
Once the PR is merged, a maintainer will create a new tag matching the version name (e.g. `v0.26.1`),
and a CI job will automatically release the new version. Remember that the release date in the
changelog must be kept in check with the actual release date.

View File

@@ -1,123 +1,99 @@
[package]
name = "winit"
version = "0.27.1"
version = "0.20.0-alpha4"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
edition = "2018"
keywords = ["windowing"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version = "1.57.0"
[package.metadata.docs.rs]
features = ["serde"]
default-target = "x86_64-unknown-linux-gnu"
# These are all tested in CI
targets = [
# Windows
"i686-pc-windows-msvc",
"x86_64-pc-windows-msvc",
# macOS
"x86_64-apple-darwin",
# Unix (X11 & Wayland)
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# iOS
"x86_64-apple-ios",
# Android
"aarch64-linux-android",
# WebAssembly
"wasm32-unknown-unknown",
]
[features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "mio", "percent-encoding", "parking_lot"]
wayland = ["wayland-client", "wayland-protocols", "sctk"]
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/title"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
stdweb = ["std_web", "instant/stdweb"]
[dependencies]
instant = { version = "0.1", features = ["wasm-bindgen"] }
once_cell = "1.12"
instant = "0.1"
lazy_static = "1"
libc = "0.2.64"
log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] }
raw-window-handle = "0.5.0"
bitflags = "1"
mint = { version = "0.5.6", optional = true }
raw-window-handle = "0.3"
[dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] }
simple_logger = "2.1.0"
image = "0.21"
env_logger = "0.5"
[target.'cfg(target_os = "android")'.dependencies]
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
ndk = "0.7.0"
ndk-glue = "0.7.0"
[target.'cfg(target_os = "android")'.dependencies.android_glue]
version = "0.2"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
objc = "0.2.7"
[target.'cfg(target_os = "ios")'.dependencies]
objc = "0.2.3"
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.24"
core-foundation = "0.9"
core-graphics = "0.22"
dispatch = "0.2.0"
cocoa = "0.19.1"
core-foundation = "0.6"
core-graphics = "0.17.3"
dispatch = "0.1.4"
objc = "0.2.3"
[target.'cfg(target_os = "windows")'.dependencies]
parking_lot = "0.12"
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
version = "0.1.3"
default_features = false
features = ["display_link"]
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.36"
[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies]
bitflags = "1"
rusty-xinput = "1.0"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3.6"
features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Media",
"Win32_System_Com_StructuredStorage",
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
"Win32_System_WindowsProgramming",
"Win32_UI_Accessibility",
"Win32_UI_Controls",
"Win32_UI_HiDpi",
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Input_Pointer",
"Win32_UI_Input_Touch",
"Win32_UI_Shell",
"Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging",
"combaseapi",
"commctrl",
"dwmapi",
"errhandlingapi",
"hidpi",
"hidusage",
"libloaderapi",
"objbase",
"ole2",
"processthreadsapi",
"shellapi",
"shellscalingapi",
"shobjidl_core",
"unknwnbase",
"winbase",
"windowsx",
"winerror",
"wingdi",
"winnt",
"winuser",
"xinput",
]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true }
wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.4.1", optional = true }
mio = { version = "0.8", features = ["os-ext"], optional = true }
x11-dl = { version = "2.18.5", optional = true }
percent-encoding = { version = "2.0", optional = true }
parking_lot = { version = "0.12.0", optional = true }
libc = "0.2.64"
wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] }
calloop = "0.4.2"
smithay-client-toolkit = "0.6"
x11-dl = "2.18.3"
percent-encoding = "2.0"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
version = "0.10"
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.22"
optional = true
features = [
'console',
"AddEventListenerOptions",
'CssStyleDeclaration',
'BeforeUnloadEvent',
'Document',
'DomRect',
@@ -128,22 +104,34 @@ features = [
'HtmlCanvasElement',
'HtmlElement',
'KeyboardEvent',
'MediaQueryList',
'MediaQueryListEvent',
'MouseEvent',
'Node',
'Navigator',
'PointerEvent',
'Window',
'WheelEvent'
'WheelEvent',
'Gamepad',
'GamepadAxisMoveEvent',
'GamepadAxisMoveEventInit',
'GamepadButton',
'GamepadButtonEvent',
'GamepadButtonEventInit',
'GamepadEvent',
'GamepadEventInit',
'GamepadHand',
'GamepadHapticActuator',
'GamepadHapticActuatorType',
'GamepadMappingType',
'GamepadPose',
'GamepadServiceTest'
]
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2.45"
optional = true
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_log = "0.2"
[workspace]
members = [
"run-wasm",
]
[target.'cfg(target_arch = "wasm32")'.dependencies.std_web]
package = "stdweb"
version = "=0.4.20"
optional = true
features = ["experimental_features_which_may_break_on_minor_version_bumps"]

View File

@@ -80,7 +80,6 @@ If your PR makes notable changes to Winit's features, please update this section
- **Window maximization**: The windows created by winit can be maximized upon creation.
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
creation.
- **Window minimization**: The windows created by winit can be minimized after creation.
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
@@ -100,10 +99,8 @@ If your PR makes notable changes to Winit's features, please update this section
### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied.
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
@@ -111,16 +108,14 @@ If your PR makes notable changes to Winit's features, please update this section
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
- **Gamepad/Joystick events**: Capturing input from gampads and joysticks.
- **Device movement events:**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows
* Setting the taskbar icon
* Setting the parent window
* Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
### macOS
* Window activation policy
@@ -152,9 +147,6 @@ If your PR makes notable changes to Winit's features, please update this section
* Getting the device idiom
* Getting the preferred video mode
### Web
* Get if systems preferred color scheme is "dark"
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
@@ -174,18 +166,17 @@ Legend:
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A**|
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ |
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |**N/A**|
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
### System information
@@ -198,20 +189,17 @@ Legend:
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|
|Cursor locking |❌ |✔️ | |✔️ |**N/A**|**N/A**|✔️ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|Mouse set location |✔️ |✔️ |✔️ | |**N/A**|**N/A**|**N/A**|
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**| |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Cursor hittest |✔️ |✔️ | |✔️ |**N/A**|**N/A**|❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ | |
|Touch pressure |✔️ |❌ | | | |✔️ | |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|Touch events |✔️ | |✔️ |✔️ |✔️ |✔️ |✔️ |
|Touch pressure |✔️ |❌ | | | |✔️ |✔️ |
|Multitouch |✔️ |❌ |✔️ |✔️ | |✔️ |✔️ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.

View File

@@ -1,23 +1,14 @@
# Hall of Champions
The winit maintainers would like to recognize the following former winit
contributors, without whom winit would not exist in its current form. We thank
The Winit maintainers would like to recognize the following former Winit
contributors, without whom Winit would not exist in its current form. We thank
them deeply for their time and efforts, and wish them best of luck in their
future endeavors:
* [@tomaka]: For creating the winit project and guiding it through its early
* [@tomaka]: For creating the Winit project and guiding it through its early
years of existence.
* [@vberger]: For diligently creating the Wayland backend, and being its
extremely helpful and benevolent maintainer for years.
* [@francesca64]: For taking over the responsibility of maintaining almost every
winit backend, and standardizing HiDPI support across all of them.
* [@Osspial]: For heroically landing EventLoop 2.0, and valiantly ushering in a
vastly more sustainable era of winit.
* [@goddessfreya]: For selflessly taking over maintainership of glutin, and her
stellar dedication to improving both winit and glutin.
Winit backend, and standardizing HiDPI support across all of them
[@tomaka]: https://github.com/tomaka
[@vberger]: https://github.com/vberger
[@francesca64]: https://github.com/francesca64
[@Osspial]: https://github.com/Osspial
[@goddessfreya]: https://github.com/goddessfreya

View File

@@ -2,11 +2,12 @@
[![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)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
[![Build Status](https://travis-ci.org/rust-windowing/winit.svg?branch=master)](https://travis-ci.org/rust-windowing/winit)
[![Build status](https://ci.appveyor.com/api/projects/status/hr89but4x1n3dphq/branch/master?svg=true)](https://ci.appveyor.com/project/Osspial/winit/branch/master)
```toml
[dependencies]
winit = "0.27.1"
winit = "0.20.0-alpha4"
```
## [Documentation](https://docs.rs/winit)
@@ -19,8 +20,9 @@ For features _outside_ the scope of winit, see [Missing features provided by oth
Join us in any of these:
[![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)
[![Freenode](https://img.shields.io/badge/freenode.net-%23glutin-red.svg)](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a)
[![Matrix](https://img.shields.io/badge/Matrix-%23Glutin%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#Glutin:matrix.org)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tomaka/glutin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Usage
@@ -44,14 +46,12 @@ fn main() {
let window = WindowBuilder::new().build(&event_loop).unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
_ => (),
_ => *control_flow = ControlFlow::Wait,
}
});
}
@@ -63,82 +63,15 @@ Winit is only officially supported on the latest stable version of the Rust comp
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
* `x11` (enabled by default): On Unix platform, compiles with the X11 backend
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
* `mint`: Enables mint (math interoperability standard types) conversions.
### Platform-specific usage
#### Wayland
Note that windows don't appear on Wayland until you draw/present to them.
`winit` doesn't do drawing, try the examples in [`glutin`] instead.
[`glutin`]: https://github.com/rust-windowing/glutin
#### WebAssembly
To run the web example: `cargo run-wasm --example web`
Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to:
Winit supports compiling to the `wasm32-unknown-unknown` target with `web-sys`.
On the web platform, a Winit window is backed by a `<canvas>` element. You can
either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
create a `<canvas>` element which you can then retrieve][web canvas getter] and
insert it into the DOM yourself.
For example code using Winit with WebAssembly, check out the [web example]. For
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
book].
[web with_canvas]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowBuilderExtWebSys.html#tymethod.with_canvas
[web canvas getter]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowExtWebSys.html#tymethod.canvas
[web example]: ./examples/web.rs
[Rust and WebAssembly book]: https://rustwasm.github.io/book/
#### Android
This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
The `ndk-glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk-glue`'s internal `NativeActivity` static is not the same due to version mismatch.
`winit` compatibility table with `ndk-glue`:
| winit | ndk-glue |
| :---: | :------------------: |
| 0.24 | `ndk-glue = "0.2.0"` |
| 0.25 | `ndk-glue = "0.3.0"` |
| 0.26 | `ndk-glue = "0.5.0"` |
| 0.27 | `ndk-glue = "0.7.0"` |
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
```toml
[[example]]
name = "request_redraw_threaded"
crate-type = ["cdylib"]
```
And add this to the example file to add the native activity glue:
```rust
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
fn main() {
...
}
```
And run the application with `cargo apk run --example request_redraw_threaded`
#### MacOS
A lot of functionality expects the application to be ready before you start
doing anything; this includes creating windows, fetching monitors, drawing,
and so on, see issues [#2238], [#2051] and [#2087].
If you encounter problems, you should try doing your initialization inside
`Event::NewEvents(StartCause::Init)`.
[#2238]: https://github.com/rust-windowing/winit/issues/2238
[#2051]: https://github.com/rust-windowing/winit/issues/2051
[#2087]: https://github.com/rust-windowing/winit/issues/2087
- Put a `<canvas id="my_id"></canvas>` element somewhere. A canvas corresponds to a winit "window".
- Write a Javascript code that creates a global variable named `Module`. Set `Module.canvas` to
the element of the `<canvas>` element (in the example you would retrieve it via `document.getElementById("my_id")`).
More information [here](https://kripken.github.io/emscripten-site/docs/api_reference/module.html).
- Make sure that you insert the `.js` file generated by Rust after the `Module` variable is created.

35
appveyor.yml Normal file
View File

@@ -0,0 +1,35 @@
environment:
matrix:
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: i686-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly
- TARGET: i686-pc-windows-gnu
CHANNEL: nightly
matrix:
allow_failures:
- CHANNEL: nightly
install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain %CHANNEL% --default-host %TARGET%
- SET PATH=%PATH%;%USERPROFILE%\.cargo\bin
- SET PATH=%PATH%;C:\MinGW\bin
- rustc -V
- cargo -V
build: false
test_script:
- cargo test --verbose
- cargo test --features serde --verbose
- cargo doc --no-deps

View File

@@ -1,114 +0,0 @@
#![allow(clippy::single_match)]
use std::{thread, time};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, KeyboardInput, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Wait,
WaitUntil,
Poll,
}
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
fn main() {
SimpleLogger::new().init().unwrap();
println!("Press '1' to switch to Wait mode.");
println!("Press '2' to switch to WaitUntil mode.");
println!("Press '3' to switch to Poll mode.");
println!("Press 'R' to toggle request_redraw() calls.");
println!("Press 'Esc' to close the window.");
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
.build(&event_loop)
.unwrap();
let mut mode = Mode::Wait;
let mut request_redraw = false;
let mut wait_cancelled = false;
let mut close_requested = false;
event_loop.run(move |event, _, control_flow| {
use winit::event::{ElementState, StartCause, VirtualKeyCode};
println!("{:?}", event);
match event {
Event::NewEvents(start_cause) => {
wait_cancelled = match start_cause {
StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil,
_ => false,
}
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => {
close_requested = true;
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: ElementState::Pressed,
..
},
..
} => match virtual_code {
VirtualKeyCode::Key1 => {
mode = Mode::Wait;
println!("\nmode: {:?}\n", mode);
}
VirtualKeyCode::Key2 => {
mode = Mode::WaitUntil;
println!("\nmode: {:?}\n", mode);
}
VirtualKeyCode::Key3 => {
mode = Mode::Poll;
println!("\nmode: {:?}\n", mode);
}
VirtualKeyCode::R => {
request_redraw = !request_redraw;
println!("\nrequest_redraw: {}\n", request_redraw);
}
VirtualKeyCode::Escape => {
close_requested = true;
}
_ => (),
},
_ => (),
},
Event::MainEventsCleared => {
if request_redraw && !wait_cancelled && !close_requested {
window.request_redraw();
}
if close_requested {
control_flow.set_exit();
}
}
Event::RedrawRequested(_window_id) => {}
Event::RedrawEventsCleared => {
match mode {
Mode::Wait => control_flow.set_wait(),
Mode::WaitUntil => {
if !wait_cancelled {
control_flow.set_wait_until(instant::Instant::now() + WAIT_TIME);
}
}
Mode::Poll => {
thread::sleep(POLL_SLEEP_TIME);
control_flow.set_poll();
}
};
}
_ => (),
}
});
}

View File

@@ -1,14 +1,10 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, WindowEvent},
event_loop::EventLoop,
event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, WindowBuilder},
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
@@ -16,38 +12,31 @@ fn main() {
let mut cursor_idx = 0;
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
..
},
..
},
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1;
} else {
cursor_idx = 0;
}
event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Pressed,
..
}),
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1;
} else {
cursor_idx = 0;
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
control_flow.set_exit();
}
_ => (),
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
*control_flow = ControlFlow::Exit;
return;
}
_ => (),
});
}

View File

@@ -1,14 +1,10 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
event_loop::EventLoop,
window::{CursorGrabMode, WindowBuilder},
event::{DeviceEvent, ElementState, Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
@@ -16,44 +12,25 @@ fn main() {
.build(&event_loop)
.unwrap();
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
..
},
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
} => {
}) => {
use winit::event::VirtualKeyCode::*;
let result = match key {
Escape => {
control_flow.set_exit();
Ok(())
}
G => window.set_cursor_grab(CursorGrabMode::Confined),
L => window.set_cursor_grab(CursorGrabMode::Locked),
A => window.set_cursor_grab(CursorGrabMode::None),
H => {
window.set_cursor_visible(modifiers.shift());
Ok(())
}
_ => Ok(()),
};
if let Err(err) = result {
println!("error: {}", err);
match key {
Escape => *control_flow = ControlFlow::Exit,
G => window.set_cursor_grab(!modifiers.shift).unwrap(),
H => window.set_cursor_visible(modifiers.shift),
_ => (),
}
}
WindowEvent::ModifiersChanged(m) => modifiers = m,
_ => (),
},
Event::DeviceEvent { event, .. } => match event {

View File

@@ -1,11 +1,8 @@
#![allow(clippy::single_match)]
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoopBuilder,
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
@@ -14,8 +11,7 @@ fn main() {
Timer,
}
SimpleLogger::new().init().unwrap();
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
let event_loop = EventLoop::<CustomEvent>::with_user_event();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -35,17 +31,13 @@ fn main() {
}
});
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::UserEvent(event) => println!("user event: {:?}", event),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
_ => (),
}
event_loop.run(move |event, _, control_flow| match event {
Event::UserEvent(event) => println!("user event: {:?}", event),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
});
}

View File

@@ -1,75 +0,0 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
},
event_loop::EventLoop,
window::{Window, WindowBuilder, WindowId},
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
let mut switched = false;
let mut entered_id = window_2.id();
event_loop.run(move |event, _, control_flow| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Switch which window is to be dragged by pressing \"x\".")
}
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
let window = if (window_id == window_1.id() && switched)
|| (window_id == window_2.id() && !switched)
{
&window_2
} else {
&window_1
};
window.drag_window().unwrap()
}
WindowEvent::CursorEntered { .. } => {
entered_id = window_id;
name_windows(entered_id, switched, &window_1, &window_2)
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::X),
..
},
..
} => {
switched = !switched;
name_windows(entered_id, switched, &window_1, &window_2);
println!("Switched!")
}
_ => (),
},
_ => (),
});
}
fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) {
let (drag_target, other) =
if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) {
(&window_2, &window_1)
} else {
(&window_1, &window_2)
};
drag_target.set_title("drag target");
other.set_title("winit window");
}

View File

@@ -1,108 +1,64 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use std::io::{stdin, stdout, Write};
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::EventLoop;
use winit::event_loop::{ControlFlow, EventLoop};
use winit::monitor::{MonitorHandle, VideoMode};
use winit::window::{Fullscreen, WindowBuilder};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
stdout().flush().unwrap();
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let fullscreen = Some(match num {
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
_ => panic!("Please enter a valid number"),
});
let mut is_maximized = false;
let mut decorations = true;
let mut minimized = false;
let window = WindowBuilder::new()
.with_title("Hello world!")
.with_fullscreen(fullscreen.clone())
.build(&event_loop)
.unwrap();
let mut monitor_index = 0;
let mut monitor = event_loop
.available_monitors()
.next()
.expect("no monitor found!");
println!("Monitor: {:?}", monitor.name());
let mut mode_index = 0;
let mut mode = monitor.video_modes().next().expect("no mode found");
println!("Mode: {}", mode);
println!("Keys:");
println!("- Esc\tExit");
println!("- F\tToggle exclusive fullscreen mode");
println!("- B\tToggle borderless mode");
println!("- S\tNext screen");
println!("- M\tNext mode for this screen");
println!("- D\tToggle window decorations");
println!("- X\tMaximize window");
println!("- Z\tMinimize window");
event_loop.run(move |event, elwt, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: ElementState::Pressed,
..
},
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(virtual_code),
state,
..
} => match virtual_code {
VirtualKeyCode::Escape => control_flow.set_exit(),
VirtualKeyCode::F | VirtualKeyCode::B if window.fullscreen().is_some() => {
window.set_fullscreen(None);
}
VirtualKeyCode::F => {
let fullscreen = Some(Fullscreen::Exclusive(mode.clone()));
println!("Setting mode: {:?}", fullscreen);
window.set_fullscreen(fullscreen);
}
VirtualKeyCode::B => {
let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone())));
println!("Setting mode: {:?}", fullscreen);
window.set_fullscreen(fullscreen);
}
VirtualKeyCode::S => {
monitor_index += 1;
if let Some(mon) = elwt.available_monitors().nth(monitor_index) {
monitor = mon;
}) => match (virtual_code, state) {
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
(VirtualKeyCode::F, ElementState::Pressed) => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
monitor_index = 0;
monitor = elwt.available_monitors().next().expect("no monitor found!");
window.set_fullscreen(fullscreen.clone());
}
println!("Monitor: {:?}", monitor.name());
mode_index = 0;
mode = monitor.video_modes().next().expect("no mode found");
println!("Mode: {}", mode);
}
VirtualKeyCode::M => {
mode_index += 1;
if let Some(m) = monitor.video_modes().nth(mode_index) {
mode = m;
} else {
mode_index = 0;
mode = monitor.video_modes().next().expect("no mode found");
}
println!("Mode: {}", mode);
(VirtualKeyCode::S, ElementState::Pressed) => {
println!("window.fullscreen {:?}", window.fullscreen());
}
VirtualKeyCode::D => {
(VirtualKeyCode::M, ElementState::Pressed) => {
is_maximized = !is_maximized;
window.set_maximized(is_maximized);
}
(VirtualKeyCode::D, ElementState::Pressed) => {
decorations = !decorations;
window.set_decorations(decorations);
}
VirtualKeyCode::X => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
VirtualKeyCode::Z => {
minimized = !minimized;
window.set_minimized(minimized);
}
_ => (),
},
_ => (),
@@ -111,3 +67,46 @@ fn main() {
}
});
}
// Enumerate monitors and prompt user to choose one
fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
for (num, monitor) in event_loop.available_monitors().enumerate() {
println!("Monitor #{}: {:?}", num, monitor.name());
}
print!("Please write the number of the monitor to use: ");
stdout().flush().unwrap();
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let monitor = event_loop
.available_monitors()
.nth(num)
.expect("Please enter a valid ID");
println!("Using {:?}", monitor.name());
monitor
}
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
for (i, video_mode) in monitor.video_modes().enumerate() {
println!("Video mode #{}: {}", i, video_mode);
}
print!("Please write the number of the video mode to use: ");
stdout().flush().unwrap();
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let video_mode = monitor
.video_modes()
.nth(num)
.expect("Please enter a valid ID");
println!("Using {}", video_mode);
video_mode
}

52
examples/gamepad.rs Normal file
View File

@@ -0,0 +1,52 @@
use winit::{
event::{
device::{GamepadEvent, GamepadHandle},
Event, WindowEvent,
},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("The world's worst video game")
.build(&event_loop)
.unwrap();
println!("enumerating gamepads:");
for gamepad in GamepadHandle::enumerate(&event_loop) {
println!(
" gamepad={:?}\tport={:?}\tbattery level={:?}",
gamepad,
gamepad.port(),
gamepad.battery_level()
);
}
let deadzone = 0.12;
event_loop.run(move |event, _, control_flow| {
match event {
Event::GamepadEvent(gamepad_handle, event) => {
match event {
// Discard any Axis events that has a corresponding Stick event.
GamepadEvent::Axis { stick: true, .. } => (),
// Discard any Stick event that falls inside the stick's deadzone.
GamepadEvent::Stick {
x_value, y_value, ..
} if (x_value.powi(2) + y_value.powi(2)).sqrt() < deadzone => (),
_ => println!("[{:?}] {:#?}", gamepad_handle, event),
}
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}

View File

@@ -0,0 +1,60 @@
use std::time::Instant;
use winit::event_loop::EventLoop;
#[derive(Debug, Clone)]
enum Rumble {
None,
Left,
Right,
}
fn main() {
let event_loop = EventLoop::new();
// You should generally use `GamepadEvent::Added/Removed` to detect gamepads, as doing that will
// allow you to more easily support gamepad hotswapping. However, we're using `enumerate` here
// because it makes this example more concise.
let gamepads = winit::event::device::GamepadHandle::enumerate(&event_loop).collect::<Vec<_>>();
let rumble_patterns = &[
(0.5, Rumble::None),
(2.0, Rumble::Left),
(0.5, Rumble::None),
(2.0, Rumble::Right),
];
let mut rumble_iter = rumble_patterns.iter().cloned().cycle();
let mut active_pattern = rumble_iter.next().unwrap();
let mut timeout = active_pattern.0;
let mut timeout_start = Instant::now();
event_loop.run(move |_, _, _| {
if timeout <= active_pattern.0 {
let t = (timeout / active_pattern.0) * std::f64::consts::PI;
let intensity = t.sin();
for g in &gamepads {
let result = match active_pattern.1 {
Rumble::Left => g.rumble(intensity, 0.0),
Rumble::Right => g.rumble(0.0, intensity),
Rumble::None => Ok(()),
};
if let Err(e) = result {
println!("Rumble failed: {:?}", e);
}
}
timeout = (Instant::now() - timeout_start).as_millis() as f64 / 1000.0;
} else {
active_pattern = rumble_iter.next().unwrap();
println!(
"Rumbling {:?} for {:?} seconds",
active_pattern.1, active_pattern.0
);
timeout = 0.0;
timeout_start = Instant::now();
}
});
}

View File

@@ -1,14 +1,10 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, KeyboardInput, WindowEvent},
event_loop::EventLoop,
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
@@ -23,7 +19,7 @@ fn main() {
ElementState::Released,
VirtualKeyCode::{N, Y},
};
control_flow.set_wait();
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => {
@@ -45,15 +41,11 @@ fn main() {
// closing the window. How to close the window is detailed in the handler for
// the Y key.
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
..
} => {
}) => {
match virtual_code {
Y => {
if close_requested {
@@ -65,7 +57,7 @@ fn main() {
// event loop (i.e. if it's a multi-window application), you need to
// drop the window. That closes it, and results in `Destroyed` being
// sent.
control_flow.set_exit();
*control_flow = ControlFlow::Exit;
}
}
N => {

View File

@@ -1,99 +0,0 @@
#![allow(clippy::single_match)]
use log::LevelFilter;
use simple_logger::SimpleLogger;
use winit::{
dpi::PhysicalPosition,
event::{ElementState, Event, Ime, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new()
.with_level(LevelFilter::Trace)
.init()
.unwrap();
println!("IME position will system default");
println!("Click to set IME position to cursor's");
println!("Press F2 to toggle IME. See the documentation of `set_ime_allowed` for more info");
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
.build(&event_loop)
.unwrap();
let mut ime_allowed = true;
window.set_ime_allowed(ime_allowed);
let mut may_show_ime = false;
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
let mut ime_pos = PhysicalPosition::new(0.0, 0.0);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::WindowEvent {
event: WindowEvent::CursorMoved { position, .. },
..
} => {
cursor_position = position;
}
Event::WindowEvent {
event:
WindowEvent::MouseInput {
state: ElementState::Released,
..
},
..
} => {
println!(
"Setting ime position to {}, {}",
cursor_position.x, cursor_position.y
);
ime_pos = cursor_position;
if may_show_ime {
window.set_ime_position(ime_pos);
}
}
Event::WindowEvent {
event: WindowEvent::Ime(event),
..
} => {
println!("{:?}", event);
may_show_ime = event != Ime::Disabled;
if may_show_ime {
window.set_ime_position(ime_pos);
}
}
Event::WindowEvent {
event: WindowEvent::ReceivedCharacter(ch),
..
} => {
println!("ch: {:?}", ch);
}
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
..
} => {
println!("key: {:?}", input);
if input.state == ElementState::Pressed
&& input.virtual_keycode == Some(VirtualKeyCode::F2)
{
ime_allowed = !ime_allowed;
window.set_ime_allowed(ime_allowed);
println!("\nIME: {}\n", ime_allowed);
}
}
_ => (),
}
});
}

27
examples/min_max_size.rs Normal file
View File

@@ -0,0 +1,27 @@
use winit::{
dpi::LogicalSize,
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0)));
window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0)));
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
}
});
}

View File

@@ -1,10 +1,6 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{event_loop::EventLoop, window::WindowBuilder};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();

View File

@@ -1,62 +0,0 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Mouse Wheel events")
.build(&event_loop)
.unwrap();
println!(
r"
When using so called 'natural scrolling' (scrolling that acts like on a touch screen), this is what to expect:
Moving your finger downwards on a scroll wheel should make the window move down, and you should see a positive Y scroll value.
When moving fingers on a trackpad down and to the right, you should see positive X and Y deltas, and the window should move down and to the right.
With reverse scrolling, you should see the inverse behavior.
In both cases the example window should move like the content of a scroll area in any other application.
In other words, the deltas indicate the direction in which to move the content (in this case the window)."
);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseWheel { delta, .. } => match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => {
println!("mouse wheel Line Delta: ({},{})", x, y);
let pixels_per_line = 120.0;
let mut pos = window.outer_position().unwrap();
pos.x += (x * pixels_per_line) as i32;
pos.y += (y * pixels_per_line) as i32;
window.set_outer_position(pos)
}
winit::event::MouseScrollDelta::PixelDelta(p) => {
println!("mouse wheel Pixel Delta: ({},{})", p.x, p.y);
let mut pos = window.outer_position().unwrap();
pos.x += p.x as i32;
pos.y += p.y as i32;
window.set_outer_position(pos)
}
},
_ => (),
},
_ => (),
}
});
}

View File

@@ -1,30 +1,28 @@
#![allow(clippy::single_match)]
#[cfg(not(target_arch = "wasm32"))]
fn main() {
extern crate env_logger;
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::EventLoop,
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder},
event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, Fullscreen, WindowBuilder},
};
const WINDOW_COUNT: usize = 3;
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
const WINDOW_SIZE: (u32, u32) = (600, 400);
SimpleLogger::new().init().unwrap();
env_logger::init();
let event_loop = EventLoop::new();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
for _ in 0..WINDOW_COUNT {
let window = WindowBuilder::new()
.with_inner_size(WINDOW_SIZE)
.with_inner_size(WINDOW_SIZE.into())
.build(&event_loop)
.unwrap();
let mut video_modes: Vec<_> = window.current_monitor().unwrap().video_modes().collect();
let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect();
let mut video_mode_id = 0usize;
let (tx, rx) = mpsc::channel();
@@ -36,10 +34,10 @@ fn main() {
// We need to update our chosen video mode if the window
// was moved to an another monitor, so that the window
// appears on this monitor instead when we go fullscreen
let previous_video_mode = video_modes.get(video_mode_id).cloned();
video_modes = window.current_monitor().unwrap().video_modes().collect();
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
video_modes = window.current_monitor().video_modes().collect();
video_mode_id = video_mode_id.min(video_modes.len());
let video_mode = video_modes.get(video_mode_id);
let video_mode = video_modes.iter().nth(video_mode_id);
// Different monitors may support different video modes,
// and the index we chose previously may now point to a
@@ -47,23 +45,18 @@ fn main() {
if video_mode != previous_video_mode.as_ref() {
println!(
"Window moved to another monitor, picked video mode: {}",
video_modes.get(video_mode_id).unwrap()
video_modes.iter().nth(video_mode_id).unwrap()
);
}
}
#[allow(deprecated)]
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
} => {
}) => {
window.set_title(&format!("{:?}", key));
let state = !modifiers.shift();
let state = !modifiers.shift;
use VirtualKeyCode::*;
match key {
A => window.set_always_on_top(state),
@@ -79,30 +72,21 @@ fn main() {
Right => (video_modes.len() - 1).min(video_mode_id + 1),
_ => unreachable!(),
};
println!("Picking video mode: {}", video_modes[video_mode_id]);
println!(
"Picking video mode: {}",
video_modes.iter().nth(video_mode_id).unwrap()
);
}
F => window.set_fullscreen(match (state, modifiers.alt()) {
(true, false) => Some(Fullscreen::Borderless(None)),
(true, true) => {
Some(Fullscreen::Exclusive(video_modes[video_mode_id].clone()))
F => window.set_fullscreen(match (state, modifiers.alt) {
(true, false) => {
Some(Fullscreen::Borderless(window.current_monitor()))
}
(true, true) => Some(Fullscreen::Exclusive(
video_modes.iter().nth(video_mode_id).unwrap().clone(),
)),
(false, _) => None,
}),
L if state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) {
println!("error: {}", err);
}
}
G if state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Confined) {
println!("error: {}", err);
}
}
G | L if !state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
println!("error: {}", err);
}
}
G => window.set_cursor_grab(state).unwrap(),
H => window.set_cursor_visible(!state),
I => {
println!("Info:");
@@ -113,38 +97,31 @@ fn main() {
println!("-> fullscreen : {:?}", window.fullscreen());
}
L => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE),
true => Some(WINDOW_SIZE.into()),
false => None,
}),
M => window.set_maximized(state),
P => window.set_outer_position({
let mut position = window.outer_position().unwrap();
let sign = if state { 1 } else { -1 };
position.x += 10 * sign;
position.y += 10 * sign;
let sign = if state { 1.0 } else { -1.0 };
position.x += 10.0 * sign;
position.y += 10.0 * sign;
position
}),
Q => window.request_redraw(),
R => window.set_resizable(state),
S => window.set_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
),
false => WINDOW_SIZE,
}),
W => {
if let Size::Physical(size) = WINDOW_SIZE.into() {
window
.set_cursor_position(Position::Physical(
PhysicalPosition::new(
size.width as i32 / 2,
size.height as i32 / 2,
),
))
.unwrap()
S => window.set_inner_size(
match state {
true => (WINDOW_SIZE.0 + 100, WINDOW_SIZE.1 + 100),
false => WINDOW_SIZE,
}
}
.into(),
),
W => window
.set_cursor_position(
(WINDOW_SIZE.0 as i32 / 2, WINDOW_SIZE.1 as i32 / 2).into(),
)
.unwrap(),
Z => {
window.set_visible(false);
thread::sleep(Duration::from_secs(1));
@@ -159,34 +136,28 @@ fn main() {
});
}
event_loop.run(move |event, _event_loop, control_flow| {
match !window_senders.is_empty() {
true => control_flow.set_wait(),
false => control_flow.set_exit(),
*control_flow = match !window_senders.is_empty() {
true => ControlFlow::Wait,
false => ControlFlow::Exit,
};
match event {
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested
| WindowEvent::Destroyed
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
| WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
} => {
}) => {
window_senders.remove(&window_id);
}
_ => {
if let Some(tx) = window_senders.get(&window_id) {
if let Some(event) = event.to_static() {
tx.send(event).unwrap();
}
tx.send(event).unwrap();
}
}
},
_ => {}
_ => (),
}
})
}

View File

@@ -1,30 +1,21 @@
#![allow(clippy::single_match)]
use std::collections::HashMap;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::EventLoop,
event::{ElementState, Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::Window,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let mut windows = HashMap::new();
for _ in 0..3 {
let window = Window::new(&event_loop).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
}
println!("Press N to open a new window.");
event_loop.run(move |event, event_loop, control_flow| {
control_flow.set_wait();
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, window_id } => {
match event {
@@ -35,21 +26,14 @@ fn main() {
windows.remove(&window_id);
if windows.is_empty() {
control_flow.set_exit();
*control_flow = ControlFlow::Exit;
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::N),
..
},
is_synthetic: false,
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Pressed,
..
} => {
let window = Window::new(event_loop).unwrap();
println!("Opened a new window: {:?}", window.id());
}) => {
let window = Window::new(&event_loop).unwrap();
windows.insert(window.id(), window);
}
_ => (),

View File

@@ -1,14 +1,13 @@
#![allow(clippy::single_match)]
use instant::Instant;
use std::time::Duration;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, WindowEvent},
event_loop::EventLoop,
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
@@ -16,26 +15,21 @@ fn main() {
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseInput {
state: ElementState::Released,
..
} => {
window.request_redraw();
}
_ => (),
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
}
_ => (),
event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::EventsCleared => {
window.request_redraw();
*control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0))
}
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
println!("{:?}", event);
}
_ => (),
});
}

View File

@@ -1,48 +0,0 @@
#![allow(clippy::single_match)]
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use std::{thread, time};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
thread::spawn(move || loop {
thread::sleep(time::Duration::from_secs(1));
window.request_redraw();
});
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
control_flow.set_wait();
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
}
_ => (),
}
});
}
#[cfg(target_arch = "wasm32")]
fn main() {
unimplemented!() // `Window` can't be sent between threads
}

View File

@@ -1,43 +1,31 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::EventLoop,
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let mut resizable = false;
let window = WindowBuilder::new()
.with_title("Hit space to toggle resizability.")
.with_inner_size(LogicalSize::new(600.0, 300.0))
.with_min_inner_size(LogicalSize::new(400.0, 200.0))
.with_max_inner_size(LogicalSize::new(800.0, 400.0))
.with_inner_size((400, 200).into())
.with_resizable(resizable)
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
..
},
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
..
} => {
}) => {
resizable = !resizable;
println!("Resizable: {}", resizable);
window.set_resizable(resizable);

View File

@@ -1,17 +1,12 @@
#![allow(clippy::single_match)]
use instant::Instant;
use std::time::Duration;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::EventLoop,
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
@@ -26,16 +21,16 @@ fn main() {
match event {
Event::NewEvents(StartCause::Init) => {
control_flow.set_wait_until(Instant::now() + timer_length);
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length)
}
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
control_flow.set_wait_until(Instant::now() + timer_length);
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length);
println!("\nTimer\n");
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});

View File

@@ -1,14 +1,10 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
@@ -20,15 +16,14 @@ fn main() {
window.set_title("A fantastic window!");
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
println!("{:?}", event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
_ => (),
} => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
}
});
}

View File

@@ -1,18 +1,8 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::event_loop::EventLoop;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let monitor = match event_loop.primary_monitor() {
Some(monitor) => monitor,
None => {
println!("No primary monitor detected.");
return;
}
};
let monitor = event_loop.primary_monitor();
println!("Listing available video modes:");

View File

@@ -1,90 +0,0 @@
#![allow(clippy::single_match)]
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
pub fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
#[cfg(target_arch = "wasm32")]
let log_list = wasm::create_log_list(&window);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
#[cfg(target_arch = "wasm32")]
wasm::log_event(&log_list, &event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::MainEventsCleared => {
window.request_redraw();
}
_ => (),
}
});
}
#[cfg(target_arch = "wasm32")]
mod wasm {
use wasm_bindgen::prelude::*;
use winit::{event::Event, window::Window};
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
#[allow(clippy::main_recursion)]
super::main();
}
pub fn create_log_list(window: &Window) -> web_sys::Element {
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
canvas.style().set_css_text("background-color: crimson;");
body.append_child(&canvas).unwrap();
let log_header = document.create_element("h2").unwrap();
log_header.set_text_content(Some("Event Log"));
body.append_child(&log_header).unwrap();
let log_list = document.create_element("ul").unwrap();
body.append_child(&log_list).unwrap();
log_list
}
pub fn log_event(log_list: &web_sys::Element, event: &Event<()>) {
log::debug!("{:?}", event);
// Getting access to browser logs requires a lot of setup on mobile devices.
// So we implement this basic logging system into the page to give developers an easy alternative.
// As a bonus its also kind of handy on desktop.
if let Event::WindowEvent { event, .. } = &event {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let log = document.create_element("li").unwrap();
log.set_text_content(Some(&format!("{:?}", event)));
log_list
.insert_before(&log, log_list.first_child().as_ref())
.unwrap();
}
}
}

View File

@@ -0,0 +1,11 @@
[package]
name = "stdweb-gamepad"
version = "0.1.0"
authors = ["furiouzz <christophe.massolin@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
winit = { path = "../../../../", features = [ "stdweb" ] }
stdweb = "0.4.20"

View File

@@ -0,0 +1,80 @@
use winit::{
event::{device::GamepadEvent, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
use stdweb::js;
/**
* Build example (from examples/web/gamepad/stdweb):
* cargo web build
* Run example (from examples/web/gamepad/stdweb):
* cargo web start
* Development (from project root):
* npx nodemon --watch src --watch examples/web/gamepad/stdweb/src -e rs --exec 'cargo web check'
*/
pub fn main() {
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("Gamepad tests")
.build(&event_loop)
.unwrap();
let deadzone = 0.12;
event_loop.run(move |event, _, control_flow| match event {
Event::GamepadEvent(gamepad_handle, event) => match event {
GamepadEvent::Axis {
axis_id,
axis,
value,
stick,
} if value > deadzone => {
let string = format!("Axis {:#?} {:#?} {:#?} {:#?}", axis_id, axis, value, stick);
js! { console.log( @{string} ); }
}
GamepadEvent::Stick {
x_id,
y_id,
x_value,
y_value,
side,
} if (x_value.powi(2) + y_value.powi(2)).sqrt() > deadzone => {
let string = format!(
"Stick {:#?} {:#?} {:#?} {:#?} {:#?}",
x_id, y_id, x_value, y_value, side
);
js! { console.log( @{string} ); }
}
GamepadEvent::Button {
button_id,
button,
state,
} => {
let string = format!("Button {:#?} {:#?} {:#?}", button_id, button, state);
js! { console.log( @{string} ); }
}
GamepadEvent::Added => {
let string = format!("[{:?}] {:#?}", gamepad_handle, event);
js! { console.log( @{string} ); }
}
GamepadEvent::Removed => {
let string = format!("[{:?}] {:#?}", gamepad_handle, event);
js! { console.log( @{string} ); }
}
_ => {}
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
});
}

View File

@@ -0,0 +1,7 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
.DS_Store

View File

@@ -0,0 +1,20 @@
[package]
name = "websys-gamepad"
version = "0.0.1"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
winit = { path = "../../../../", features = [ "web-sys" ] }
wasm-bindgen = "0.2.45"
wasm-bindgen-test = "0.3.8"
web-sys = { version = "0.3.22", features = [ "console" ] }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = "0.1.6"

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Gamepad</title>
</head>
<body>
<canvas id="my_id"></canvas>
<script>
window.Module = {
canvas: document.getElementById('my_id')
}
</script>
<script type="module">
import example_gamepad from "../pkg/websys_examples.js"
example_gamepad()
.then((m) => console.log('Success', m))
.catch((e) => console.log('Ar error occured', e))
</script>
</body>
</html>

View File

@@ -0,0 +1,78 @@
mod utils;
use wasm_bindgen::prelude::*;
use winit::{
event::{device::GamepadEvent, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
/**
* Build example (from examples/gamepad/websys):
* wasm-pack build --target web
* Run web server (from examples/gamepad/websys):
* npx http-server
* Open your browser at http://localhost:8000/files/${EXAMPLE}.html
* Development (from project root):
* npx nodemon --watch src --watch examples/web/gamepad/websys/src -e rs --exec 'cd examples/web/gamepad/websys && wasm-pack build --target web'
*/
macro_rules! console_log {
($($t:tt)*) => (web_sys::console::log_1(&format_args!($($t)*).to_string().into()))
}
#[wasm_bindgen(start)]
pub fn example_gamepad() {
utils::set_panic_hook(); // needed for error stack trace
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("Gamepad tests")
.build(&event_loop)
.unwrap();
let deadzone = 0.12;
event_loop.run(move |event, _, control_flow| {
match event {
Event::GamepadEvent(gamepad_handle, event) => {
match event {
GamepadEvent::Axis {
axis_id,
axis,
value,
stick,
} if value > deadzone => {
console_log!("Axis {:#?} {:#?} {:#?} {:#?}", axis_id, axis, value, stick)
},
GamepadEvent::Stick {
x_id, y_id, x_value, y_value, side
} if (x_value.powi(2) + y_value.powi(2)).sqrt() > deadzone => {
console_log!("Stick {:#?} {:#?} {:#?} {:#?} {:#?}", x_id, y_id, x_value, y_value, side)
},
GamepadEvent::Button {
button_id,
button,
state,
} => {
console_log!("Button {:#?} {:#?} {:#?}", button_id, button, state)
},
GamepadEvent::Added => {
console_log!("[{:?}] {:#?}", gamepad_handle, event)
},
GamepadEvent::Removed => console_log!("[{:?}] {:#?}", gamepad_handle, event),
_ => {},
}
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}

View File

@@ -0,0 +1,10 @@
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
// #[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}

View File

@@ -1,35 +1,26 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
println!("{:?}", event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::MainEventsCleared => {
window.request_redraw();
}
_ => (),
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
}
});
}

View File

@@ -1,41 +1,37 @@
#![allow(clippy::single_match)]
// This example is used by developers to test various window functions.
use simple_logger::SimpleLogger;
use winit::{
dpi::{LogicalSize, PhysicalSize},
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{DeviceEventFilter, EventLoop},
event_loop::{ControlFlow, EventLoop},
window::{Fullscreen, WindowBuilder},
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(100.0, 100.0))
.with_inner_size(LogicalSize::from((100, 100)))
.build(&event_loop)
.unwrap();
eprintln!("debugging keys:");
eprintln!(" (E) Enter exclusive fullscreen");
eprintln!(" (F) Toggle borderless fullscreen");
eprintln!(" (P) Toggle borderless fullscreen on system's preffered monitor");
#[cfg(waiting_for_set_minimized)]
eprintln!(" (M) Toggle minimized");
eprintln!(" (Q) Quit event loop");
eprintln!(" (V) Toggle visibility");
eprintln!(" (X) Toggle maximized");
#[cfg(waiting_for_set_minimized)]
let mut minimized = false;
let mut maximized = false;
let mut visible = true;
event_loop.set_device_event_filter(DeviceEventFilter::Never);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
*control_flow = ControlFlow::Wait;
match event {
Event::DeviceEvent {
@@ -47,6 +43,7 @@ fn main() {
}),
..
} => match key {
#[cfg(waiting_for_set_minimized)]
VirtualKeyCode::M => {
if minimized {
minimized = !minimized;
@@ -62,69 +59,62 @@ fn main() {
_ => (),
},
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
},
..
},
event: WindowEvent::KeyboardInput { input, .. },
..
} => match key {
VirtualKeyCode::E => {
fn area(size: PhysicalSize<u32>) -> u32 {
size.width * size.height
}
} => match input {
KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
} => match key {
VirtualKeyCode::E => {
fn area(size: PhysicalSize) -> f64 {
size.width * size.height
}
let monitor = window.current_monitor().unwrap();
if let Some(mode) = monitor
.video_modes()
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
{
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
} else {
eprintln!("no video modes available");
}
}
VirtualKeyCode::F => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
let monitor = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
if let Some(mode) = monitor.video_modes().max_by(|a, b| {
area(a.size())
.partial_cmp(&area(b.size()))
.expect("NaN in video mode size")
}) {
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
} else {
eprintln!("no video modes available");
}
}
}
VirtualKeyCode::P => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
VirtualKeyCode::F => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
let monitor = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
}
}
VirtualKeyCode::M => {
minimized = !minimized;
window.set_minimized(minimized);
}
VirtualKeyCode::Q => {
control_flow.set_exit();
}
VirtualKeyCode::V => {
visible = !visible;
window.set_visible(visible);
}
VirtualKeyCode::X => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
#[cfg(waiting_for_set_minimized)]
VirtualKeyCode::M => {
minimized = !minimized;
window.set_minimized(minimized);
}
VirtualKeyCode::Q => {
*control_flow = ControlFlow::Exit;
}
VirtualKeyCode::V => {
visible = !visible;
window.set_visible(visible);
}
VirtualKeyCode::X => {
maximized = !maximized;
window.set_maximized(maximized);
}
_ => (),
},
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
_ => (),
}
});

View File

@@ -1,17 +1,12 @@
#![allow(clippy::single_match)]
extern crate image;
use std::path::Path;
use simple_logger::SimpleLogger;
use winit::{
event::Event,
event_loop::EventLoop,
event_loop::{ControlFlow, EventLoop},
window::{Icon, WindowBuilder},
};
fn main() {
SimpleLogger::new().init().unwrap();
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
// since it seems to work well enough in most cases. Be careful about going too high, or
@@ -31,12 +26,11 @@ fn main() {
.unwrap();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, .. } = event {
use winit::event::WindowEvent::*;
match event {
CloseRequested => control_flow.set_exit(),
CloseRequested => *control_flow = ControlFlow::Exit,
DroppedFile(path) => {
window.set_window_icon(Some(load_icon(&path)));
}
@@ -48,11 +42,13 @@ fn main() {
fn load_icon(path: &Path) -> Icon {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba8();
let image = image::open(path).expect("Failed to open icon path");
use image::{GenericImageView, Pixel};
let (width, height) = image.dimensions();
let rgba = image.into_raw();
let mut rgba = Vec::with_capacity((width * height) as usize * 4);
for (_, _, pixel) in image.pixels() {
rgba.extend_from_slice(&pixel.to_rgba().data);
}
(rgba, width, height)
};
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")

View File

@@ -1,5 +1,3 @@
#![allow(clippy::single_match)]
// Limit this example to only compatible platforms.
#[cfg(any(
target_os = "windows",
@@ -8,22 +6,18 @@
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "android",
target_os = "openbsd"
))]
fn main() {
use std::{thread::sleep, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::run_return::EventLoopExtRunReturn,
event_loop::{ControlFlow, EventLoop},
platform::desktop::EventLoopExtDesktop,
window::WindowBuilder,
};
let mut event_loop = EventLoop::new();
SimpleLogger::new().init().unwrap();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
@@ -33,8 +27,6 @@ fn main() {
while !quit {
event_loop.run_return(|event, _, control_flow| {
control_flow.set_wait();
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{:?}", event);
@@ -46,21 +38,21 @@ fn main() {
..
} => {
quit = true;
*control_flow = ControlFlow::Exit;
}
Event::MainEventsCleared => {
control_flow.set_exit();
Event::EventsCleared => {
*control_flow = ControlFlow::Exit;
}
_ => (),
_ => *control_flow = ControlFlow::Wait,
}
});
// Sleep for 1/60 second to simulate rendering
println!("rendering");
sleep(Duration::from_millis(16));
}
}
#[cfg(any(target_os = "ios", target_arch = "wasm32"))]
#[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))]
fn main() {
println!("This platform doesn't support run_return.");
}

View File

@@ -1,9 +0,0 @@
[package]
name = "run-wasm"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cargo-run-wasm = "0.1.0"

View File

@@ -1,3 +0,0 @@
fn main() {
cargo_run_wasm::run_wasm();
}

View File

@@ -1,570 +1,331 @@
//! UI scaling is important, so read the docs for this module if you don't want to be confused.
//! DPI is important, so read the docs for this module if you don't want to be confused.
//!
//! ## Why should I care about UI scaling?
//! Originally, `winit` dealt entirely in physical pixels (excluding unintentional inconsistencies), but now all
//! window-related functions both produce and consume logical pixels. Monitor-related functions still use physical
//! pixels, as do any context-related functions in `glutin`.
//!
//! Modern computer screens don't have a consistent relationship between resolution and size.
//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
//! normally being less than a quarter the size of their desktop counterparts. What's more, neither
//! desktop nor mobile screens are consistent resolutions within their own size classes - common
//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
//! and beyond.
//! If you've never heard of these terms before, then you're not alone, and this documentation will explain the
//! concepts.
//!
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen,
//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
//! problematic with text rendering, where quarter-sized text becomes a significant legibility
//! problem.
//! Modern screens have a defined physical resolution, most commonly 1920x1080. Indepedent of that is the amount of
//! space the screen occupies, which is to say, the height and width in millimeters. The relationship between these two
//! measurements is the *pixel density*. Mobile screens require a high pixel density, as they're held close to the
//! eyes. Larger displays also require a higher pixel density, hence the growing presence of 1440p and 4K displays.
//!
//! Failure to account for the scale factor can create a significantly degraded user experience.
//! Most notably, it can make users feel like they have bad eyesight, which will potentially cause
//! them to think about growing elderly, resulting in them having an existential crisis. Once users
//! enter that state, they will no longer be focused on your application.
//! So, this presents a problem. Let's say we want to render a square 100px button. It will occupy 100x100 of the
//! screen's pixels, which in many cases, seems perfectly fine. However, because this size doesn't account for the
//! screen's dimensions or pixel density, the button's size can vary quite a bit. On a 4K display, it would be unusably
//! small.
//!
//! ## How should I handle it?
//! That's a description of what happens when the button is 100x100 *physical* pixels. Instead, let's try using 100x100
//! *logical* pixels. To map logical pixels to physical pixels, we simply multiply by the DPI (dots per inch) factor.
//! On a "typical" desktop display, the DPI factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical
//! pixels. However, a 1440p display may have a DPI factor of 1.25, so the button is rendered as 125x125 physical pixels.
//! Ideally, the button now has approximately the same perceived size across varying displays.
//!
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is
//! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device
//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
//! Failure to account for the DPI factor can create a badly degraded user experience. Most notably, it can make users
//! feel like they have bad eyesight, which will potentially cause them to think about growing elderly, resulting in
//! them entering an existential panic. Once users enter that state, they will no longer be focused on your application.
//!
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's
//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather
//! than any DPI-dependent units.
//! There are two ways to get the DPI factor:
//! - You can track the [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) event of your
//! windows. This event is sent any time the DPI factor changes, either because the window moved to another monitor,
//! or because the user changed the configuration of their screen.
//! - You can also retrieve the DPI factor of a monitor by calling
//! [`MonitorHandle::hidpi_factor`](crate::monitor::MonitorHandle::hidpi_factor), or the
//! current DPI factor applied to a window by calling
//! [`Window::hidpi_factor`](crate::window::Window::hidpi_factor), which is roughly equivalent
//! to `window.current_monitor().hidpi_factor()`.
//!
//! ### Position and Size types
//! Depending on the platform, the window's actual DPI factor may only be known after
//! the event loop has started and your window has been drawn once. To properly handle these cases,
//! the most robust way is to monitor the [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged)
//! event and dynamically adapt your drawing logic to follow the DPI factor.
//!
//! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the
//! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels
//! divided by the scale factor.
//! All of Winit's functions return physical types, but can take either logical or physical
//! coordinates as input, allowing you to use the most convenient coordinate system for your
//! particular application.
//! Here's an overview of what sort of DPI factors you can expect, and where they come from:
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the display settings.
//! While users are free to select any option they want, they're only given a selection of "nice" DPI factors, i.e.
//! 1.0, 1.25, 1.5... on Windows 7, the DPI factor is global and changing it requires logging out.
//! - **macOS:** The buzzword is "retina displays", which have a DPI factor of 2.0. Otherwise, the DPI factor is 1.0.
//! Intermediate DPI factors are never used, thus 1440p displays/etc. aren't properly supported. It's possible for any
//! display to use that 2.0 DPI factor, given the use of the command line.
//! - **X11:** On X11, we calculate the DPI factor based on the millimeter dimensions provided by XRandR. This can
//! result in a wide range of possible values, including some interesting ones like 1.0833333333333333. This can be
//! overridden using the `WINIT_HIDPI_FACTOR` environment variable, though that's not recommended.
//! - **Wayland:** On Wayland, DPI factors are set per-screen by the server, and are always integers (most often 1 or 2).
//! - **iOS:** DPI factors are both constant and device-specific on iOS.
//! - **Android:** This feature isn't yet implemented on Android, so the DPI factor will always be returned as 1.0.
//! - **Web:** DPI factors are handled by the browser and will always be 1.0 for your application.
//!
//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
//! will truncate the fractional part of the float, rather than properly round to the nearest
//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
//! int, although rounding lessens the problem.
//! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This
//! may be surprising on X11, but is quite standard elsewhere. Physical size changes always produce a
//! [`Resized`](crate::event::WindowEvent::Resized) event, even on platforms where no resize actually occurs,
//! such as macOS and Wayland. As a result, it's not necessary to separately handle
//! [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) if you're only listening for size.
//!
//! ### Events
//! Your GPU has no awareness of the concept of logical pixels, and unless you like wasting pixel density, your
//! framebuffer's size should be in physical pixels.
//!
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your
//! application's UI elements and adjust how the platform changes the window's size to reflect the new
//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor
//! can be found by calling [`window.scale_factor()`].
//! `winit` will send [`Resized`](crate::event::WindowEvent::Resized) events whenever a window's logical size
//! changes, and [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events
//! whenever the DPI factor changes. Receiving either of these events means that the physical size of your window has
//! changed, and you should recompute it using the latest values you received for each. If the logical size and the
//! DPI factor change simultaneously, `winit` will send both events together; thus, it's recommended to buffer
//! these events and process them at the end of the queue.
//!
//! ## How is the scale factor calculated?
//!
//! Scale factor is calculated differently on different platforms:
//!
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
//! display settings. While users are free to select any option they want, they're only given a
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
//! global and changing it requires logging out. See [this article][windows_1] for technical
//! details.
//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
//! the specific value varies across devices.
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
//! currently uses a three-pronged approach:
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
//! + If not present, use the value set in `Xft.dpi` in Xresources.
//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
//!
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always
//! integers (most often 1 or 2).
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
//! information.
//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the
//! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information.
//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels.
//! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by
//! both the screen scaling and the browser zoom level and can go below `1.0`.
//!
//!
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged
//! [`window.scale_factor()`]: crate::window::Window::scale_factor
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
//! If you never received any [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events,
//! then your window's DPI factor is 1.
pub trait Pixel: Copy + Into<f64> {
fn from_f64(f: f64) -> Self;
fn cast<P: Pixel>(self) -> P {
P::from_f64(self.into())
}
}
impl Pixel for u8 {
fn from_f64(f: f64) -> Self {
f.round() as u8
}
}
impl Pixel for u16 {
fn from_f64(f: f64) -> Self {
f.round() as u16
}
}
impl Pixel for u32 {
fn from_f64(f: f64) -> Self {
f.round() as u32
}
}
impl Pixel for i8 {
fn from_f64(f: f64) -> Self {
f.round() as i8
}
}
impl Pixel for i16 {
fn from_f64(f: f64) -> Self {
f.round() as i16
}
}
impl Pixel for i32 {
fn from_f64(f: f64) -> Self {
f.round() as i32
}
}
impl Pixel for f32 {
fn from_f64(f: f64) -> Self {
f as f32
}
}
impl Pixel for f64 {
fn from_f64(f: f64) -> Self {
f
}
}
/// Checks that the scale factor is a normal positive `f64`.
/// Checks that the DPI factor is a normal positive `f64`.
///
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from
/// All functions that take a DPI factor assert that this will return `true`. If you're sourcing DPI factors from
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
/// otherwise, you risk panics.
#[inline]
pub fn validate_scale_factor(scale_factor: f64) -> bool {
scale_factor.is_sign_positive() && scale_factor.is_normal()
pub fn validate_hidpi_factor(dpi_factor: f64) -> bool {
dpi_factor.is_sign_positive() && dpi_factor.is_normal()
}
/// A position represented in logical pixels.
///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
/// implementation is provided which does the rounding for you.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalPosition<P> {
pub x: P,
pub y: P,
pub struct LogicalPosition {
pub x: f64,
pub y: f64,
}
impl<P> LogicalPosition<P> {
impl LogicalPosition {
#[inline]
pub const fn new(x: P, y: P) -> Self {
pub fn new(x: f64, y: f64) -> Self {
LogicalPosition { x, y }
}
}
impl<P: Pixel> LogicalPosition<P> {
#[inline]
pub fn from_physical<T: Into<PhysicalPosition<X>>, X: Pixel>(
physical: T,
scale_factor: f64,
) -> Self {
physical.into().to_logical(scale_factor)
pub fn from_physical<T: Into<PhysicalPosition>>(physical: T, dpi_factor: f64) -> Self {
physical.into().to_logical(dpi_factor)
}
#[inline]
pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<X> {
assert!(validate_scale_factor(scale_factor));
let x = self.x.into() * scale_factor;
let y = self.y.into() * scale_factor;
PhysicalPosition::new(x, y).cast()
pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition {
assert!(validate_hidpi_factor(dpi_factor));
let x = self.x * dpi_factor;
let y = self.y * dpi_factor;
PhysicalPosition::new(x, y)
}
}
impl From<(f64, f64)> for LogicalPosition {
#[inline]
pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> {
LogicalPosition {
x: self.x.cast(),
y: self.y.cast(),
}
fn from((x, y): (f64, f64)) -> Self {
Self::new(x, y)
}
}
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
fn from((x, y): (X, X)) -> LogicalPosition<P> {
LogicalPosition::new(x.cast(), y.cast())
impl From<(i32, i32)> for LogicalPosition {
#[inline]
fn from((x, y): (i32, i32)) -> Self {
Self::new(x as f64, y as f64)
}
}
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) {
fn from(p: LogicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
impl Into<(f64, f64)> for LogicalPosition {
#[inline]
fn into(self) -> (f64, f64) {
(self.x, self.y)
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
fn from([x, y]: [X; 2]) -> LogicalPosition<P> {
LogicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] {
fn from(p: LogicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
fn from(p: LogicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.y }
impl Into<(i32, i32)> for LogicalPosition {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (i32, i32) {
(self.x.round() as _, self.y.round() as _)
}
}
/// A position represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition<P> {
pub x: P,
pub y: P,
pub struct PhysicalPosition {
pub x: f64,
pub y: f64,
}
impl<P> PhysicalPosition<P> {
impl PhysicalPosition {
#[inline]
pub const fn new(x: P, y: P) -> Self {
pub fn new(x: f64, y: f64) -> Self {
PhysicalPosition { x, y }
}
}
impl<P: Pixel> PhysicalPosition<P> {
#[inline]
pub fn from_logical<T: Into<LogicalPosition<X>>, X: Pixel>(
logical: T,
scale_factor: f64,
) -> Self {
logical.into().to_physical(scale_factor)
pub fn from_logical<T: Into<LogicalPosition>>(logical: T, dpi_factor: f64) -> Self {
logical.into().to_physical(dpi_factor)
}
#[inline]
pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalPosition<X> {
assert!(validate_scale_factor(scale_factor));
let x = self.x.into() / scale_factor;
let y = self.y.into() / scale_factor;
LogicalPosition::new(x, y).cast()
pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition {
assert!(validate_hidpi_factor(dpi_factor));
let x = self.x / dpi_factor;
let y = self.y / dpi_factor;
LogicalPosition::new(x, y)
}
}
impl From<(f64, f64)> for PhysicalPosition {
#[inline]
pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> {
PhysicalPosition {
x: self.x.cast(),
y: self.y.cast(),
}
fn from((x, y): (f64, f64)) -> Self {
Self::new(x, y)
}
}
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
fn from((x, y): (X, X)) -> PhysicalPosition<P> {
PhysicalPosition::new(x.cast(), y.cast())
impl From<(i32, i32)> for PhysicalPosition {
#[inline]
fn from((x, y): (i32, i32)) -> Self {
Self::new(x as f64, y as f64)
}
}
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) {
fn from(p: PhysicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
impl Into<(f64, f64)> for PhysicalPosition {
#[inline]
fn into(self) -> (f64, f64) {
(self.x, self.y)
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
fn from([x, y]: [X; 2]) -> PhysicalPosition<P> {
PhysicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] {
fn from(p: PhysicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
fn from(p: PhysicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.y }
impl Into<(i32, i32)> for PhysicalPosition {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (i32, i32) {
(self.x.round() as _, self.y.round() as _)
}
}
/// A size represented in logical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
///
/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalSize<P> {
pub width: P,
pub height: P,
pub struct LogicalSize {
pub width: f64,
pub height: f64,
}
impl<P> LogicalSize<P> {
impl LogicalSize {
#[inline]
pub const fn new(width: P, height: P) -> Self {
pub fn new(width: f64, height: f64) -> Self {
LogicalSize { width, height }
}
}
impl<P: Pixel> LogicalSize<P> {
#[inline]
pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(
physical: T,
scale_factor: f64,
) -> Self {
physical.into().to_logical(scale_factor)
pub fn from_physical<T: Into<PhysicalSize>>(physical: T, dpi_factor: f64) -> Self {
physical.into().to_logical(dpi_factor)
}
#[inline]
pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalSize<X> {
assert!(validate_scale_factor(scale_factor));
let width = self.width.into() * scale_factor;
let height = self.height.into() * scale_factor;
PhysicalSize::new(width, height).cast()
pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize {
assert!(validate_hidpi_factor(dpi_factor));
let width = self.width * dpi_factor;
let height = self.height * dpi_factor;
PhysicalSize::new(width, height)
}
}
impl From<(f64, f64)> for LogicalSize {
#[inline]
pub fn cast<X: Pixel>(&self) -> LogicalSize<X> {
LogicalSize {
width: self.width.cast(),
height: self.height.cast(),
}
fn from((width, height): (f64, f64)) -> Self {
Self::new(width, height)
}
}
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
fn from((x, y): (X, X)) -> LogicalSize<P> {
LogicalSize::new(x.cast(), y.cast())
impl From<(u32, u32)> for LogicalSize {
#[inline]
fn from((width, height): (u32, u32)) -> Self {
Self::new(width as f64, height as f64)
}
}
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for (X, X) {
fn from(s: LogicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
impl Into<(f64, f64)> for LogicalSize {
#[inline]
fn into(self) -> (f64, f64) {
(self.width, self.height)
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
fn from([x, y]: [X; 2]) -> LogicalSize<P> {
LogicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for [X; 2] {
fn from(s: LogicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
fn from(s: LogicalSize<P>) -> Self {
mint::Vector2 {
x: s.width,
y: s.height,
}
impl Into<(u32, u32)> for LogicalSize {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (u32, u32) {
(self.width.round() as _, self.height.round() as _)
}
}
/// A size represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
///
/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalSize<P> {
pub width: P,
pub height: P,
pub struct PhysicalSize {
pub width: f64,
pub height: f64,
}
impl<P> PhysicalSize<P> {
impl PhysicalSize {
#[inline]
pub const fn new(width: P, height: P) -> Self {
pub fn new(width: f64, height: f64) -> Self {
PhysicalSize { width, height }
}
}
impl<P: Pixel> PhysicalSize<P> {
#[inline]
pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, scale_factor: f64) -> Self {
logical.into().to_physical(scale_factor)
pub fn from_logical<T: Into<LogicalSize>>(logical: T, dpi_factor: f64) -> Self {
logical.into().to_physical(dpi_factor)
}
#[inline]
pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalSize<X> {
assert!(validate_scale_factor(scale_factor));
let width = self.width.into() / scale_factor;
let height = self.height.into() / scale_factor;
LogicalSize::new(width, height).cast()
pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize {
assert!(validate_hidpi_factor(dpi_factor));
let width = self.width / dpi_factor;
let height = self.height / dpi_factor;
LogicalSize::new(width, height)
}
}
impl From<(f64, f64)> for PhysicalSize {
#[inline]
pub fn cast<X: Pixel>(&self) -> PhysicalSize<X> {
PhysicalSize {
width: self.width.cast(),
height: self.height.cast(),
}
fn from((width, height): (f64, f64)) -> Self {
Self::new(width, height)
}
}
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
fn from((x, y): (X, X)) -> PhysicalSize<P> {
PhysicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for (X, X) {
fn from(s: PhysicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
fn from([x, y]: [X; 2]) -> PhysicalSize<P> {
PhysicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for [X; 2] {
fn from(s: PhysicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
fn from(s: PhysicalSize<P>) -> Self {
mint::Vector2 {
x: s.width,
y: s.height,
}
}
}
/// A size that's either physical or logical.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Size {
Physical(PhysicalSize<u32>),
Logical(LogicalSize<f64>),
}
impl Size {
pub fn new<S: Into<Size>>(size: S) -> Size {
size.into()
}
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalSize<P> {
match *self {
Size::Physical(size) => size.to_logical(scale_factor),
Size::Logical(size) => size.cast(),
}
}
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalSize<P> {
match *self {
Size::Physical(size) => size.cast(),
Size::Logical(size) => size.to_physical(scale_factor),
}
}
}
impl<P: Pixel> From<PhysicalSize<P>> for Size {
impl From<(u32, u32)> for PhysicalSize {
#[inline]
fn from(size: PhysicalSize<P>) -> Size {
Size::Physical(size.cast())
fn from((width, height): (u32, u32)) -> Self {
Self::new(width as f64, height as f64)
}
}
impl<P: Pixel> From<LogicalSize<P>> for Size {
impl Into<(f64, f64)> for PhysicalSize {
#[inline]
fn from(size: LogicalSize<P>) -> Size {
Size::Logical(size.cast())
fn into(self) -> (f64, f64) {
(self.width, self.height)
}
}
/// A position that's either physical or logical.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Position {
Physical(PhysicalPosition<i32>),
Logical(LogicalPosition<f64>),
}
impl Position {
pub fn new<S: Into<Position>>(position: S) -> Position {
position.into()
}
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalPosition<P> {
match *self {
Position::Physical(position) => position.to_logical(scale_factor),
Position::Logical(position) => position.cast(),
}
}
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<P> {
match *self {
Position::Physical(position) => position.cast(),
Position::Logical(position) => position.to_physical(scale_factor),
}
}
}
impl<P: Pixel> From<PhysicalPosition<P>> for Position {
impl Into<(u32, u32)> for PhysicalSize {
/// Note that this rounds instead of truncating.
#[inline]
fn from(position: PhysicalPosition<P>) -> Position {
Position::Physical(position.cast())
}
}
impl<P: Pixel> From<LogicalPosition<P>> for Position {
#[inline]
fn from(position: LogicalPosition<P>) -> Position {
Position::Logical(position.cast())
fn into(self) -> (u32, u32) {
(self.width.round() as _, self.height.round() as _)
}
}

File diff suppressed because it is too large Load Diff

363
src/event/device.rs Normal file
View File

@@ -0,0 +1,363 @@
//! Raw hardware events that are not associated with any particular window.
//!
//! Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
//! game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because
//! window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs
//! may not match.
//!
//! All attached devices are guaranteed to emit an `Added` event upon the initialization of the event loop.
//!
//! Note that device events are always delivered regardless of window focus.
use crate::{
dpi::PhysicalPosition,
event::{AxisId, ButtonId, ElementState, KeyboardInput, MouseButton, ModifiersState},
event_loop::EventLoop,
platform_impl,
};
use std::{fmt, io};
/// A hint suggesting the type of button that was pressed.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum GamepadButton {
Start,
Select,
/// The north face button.
///
/// * Nintendo: X
/// * Playstation: Triangle
/// * XBox: Y
North,
/// The south face button.
///
/// * Nintendo: B
/// * Playstation: X
/// * XBox: A
South,
/// The east face button.
///
/// * Nintendo: A
/// * Playstation: Circle
/// * XBox: B
East,
/// The west face button.
///
/// * Nintendo: Y
/// * Playstation: Square
/// * XBox: X
West,
LeftStick,
RightStick,
LeftTrigger,
RightTrigger,
LeftShoulder,
RightShoulder,
DPadUp,
DPadDown,
DPadLeft,
DPadRight,
}
/// A hint suggesting the type of axis that moved.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum GamepadAxis {
LeftStickX,
LeftStickY,
RightStickX,
RightStickY,
LeftTrigger,
RightTrigger,
}
/// A given joystick's side on the gamepad.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Side {
Left,
Right,
}
/// Raw mouse events.
///
/// See the module-level docs for more information.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum MouseEvent {
/// A mouse device has been added.
Added,
/// A mouse device has been removed.
Removed,
/// A mouse button has been pressed or released.
Button {
state: ElementState,
button: MouseButton,
},
/// Relative change in physical position of a pointing device.
///
/// This represents raw, unfiltered physical motion, NOT the position of the mouse. Accordingly,
/// the values provided here are the change in position of the mouse since the previous
/// `MovedRelative` event.
MovedRelative(f64, f64),
/// Change in absolute position of a pointing device.
///
/// The `PhysicalPosition` value is the new position of the cursor relative to the desktop. This
/// generally doesn't get output by standard mouse devices, but can get output from tablet devices.
MovedAbsolute(PhysicalPosition),
/// Change in rotation of mouse wheel.
Wheel(f64, f64),
}
/// Raw keyboard events.
///
/// See the module-level docs for more information.
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
pub enum KeyboardEvent {
/// A keyboard device has been added.
Added,
/// A keyboard device has been removed.
Removed,
/// A key has been pressed or released.
Input(KeyboardInput),
ModifiersChanged(ModifiersState),
}
/// Raw HID event.
///
/// See the module-level docs for more information.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum HidEvent {
/// A Human Interface Device device has been added.
Added,
/// A Human Interface Device device has been removed.
Removed,
/// A raw data packet has been received from the Human Interface Device.
Data(Box<[u8]>),
}
/// Gamepad/joystick events.
///
/// These can be generated by any of a variety of game controllers, including (but not limited to)
/// gamepads, joysicks, and HOTAS devices.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub enum GamepadEvent {
/// A gamepad/joystick device has been added.
Added,
/// A gamepad/joystick device has been removed.
Removed,
/// An analog axis value on the gamepad/joystick has changed.
///
/// Winit does NOT provide [deadzone filtering](https://www.quora.com/What-does-the-term-joystick-deadzone-mean),
/// and such filtering may have to be provided by API users for joystick axes.
Axis {
axis_id: AxisId,
/// A hint regarding the physical axis that moved.
///
/// On traditional gamepads (such as an X360 controller) this can be assumed to have a
/// non-`None` value; however, other joystick devices with more varied layouts generally won't
/// provide a value here.
///
/// TODO: DISCUSS CONTROLLER MAPPING ONCE WE FIGURE OUT WHAT WE'RE DOING THERE.
axis: Option<GamepadAxis>,
value: f64,
/// Whether or not this axis has also produced a [`GamepadEvent::Stick`] event.
stick: bool,
},
/// A two-axis joystick's value has changed.
///
/// This is mainly provided to assist with deadzone calculation, as proper deadzones should be
/// calculated via the combined distance of each joystick axis from the center of the joystick,
/// rather than per-axis.
///
/// Note that this is only guaranteed to be emitted for traditionally laid out gamepads. More
/// complex joysticks generally don't report specifics of their layout to the operating system,
/// preventing Winit from automatically aggregating their axis input into two-axis stick events.
Stick {
/// The X axis' ID.
x_id: AxisId,
/// The Y axis' ID.
y_id: AxisId,
x_value: f64,
y_value: f64,
/// Which joystick side produced this event.
side: Side,
},
Button {
button_id: ButtonId,
/// A hint regarding the location of the button.
///
/// The caveats on the `Axis.hint` field also apply here.
button: Option<GamepadButton>,
state: ElementState,
},
}
/// Error reported if a rumble attempt unexpectedly failed.
#[derive(Debug)]
pub enum RumbleError {
/// The device is no longer connected.
DeviceNotConnected,
/// An unknown OS error has occured.
OsError(io::Error),
}
/// A typed identifier for a mouse device.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MouseId(pub(crate) platform_impl::MouseId);
/// A typed identifier for a keyboard device.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct KeyboardId(pub(crate) platform_impl::KeyboardId);
/// A typed if for a Human Interface Device.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HidId(pub(crate) platform_impl::HidId);
/// A handle to a gamepad/joystick device.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GamepadHandle(pub(crate) platform_impl::GamepadHandle);
impl MouseId {
/// Returns a dummy `MouseId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `MouseId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
MouseId(platform_impl::MouseId::dummy())
}
/// Enumerate all attached mouse devices.
pub fn enumerate<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
platform_impl::MouseId::enumerate(&event_loop.event_loop)
}
/// Check to see if this mouse device is still connected.
pub fn is_connected(&self) -> bool {
self.0.is_connected()
}
}
impl KeyboardId {
/// Returns a dummy `KeyboardId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `KeyboardId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
KeyboardId(platform_impl::KeyboardId::dummy())
}
/// Enumerate all attached keyboard devices.
pub fn enumerate<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
platform_impl::KeyboardId::enumerate(&event_loop.event_loop)
}
/// Check to see if this keyboard device is still connected.
pub fn is_connected(&self) -> bool {
self.0.is_connected()
}
}
impl HidId {
/// Returns a dummy `HidId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `HidId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
HidId(platform_impl::HidId::dummy())
}
/// Enumerate all attached keyboard devices.
pub fn enumerate<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
platform_impl::HidId::enumerate(&event_loop.event_loop)
}
/// Check to see if this keyboard device is still connected.
pub fn is_connected(&self) -> bool {
self.0.is_connected()
}
}
impl GamepadHandle {
/// Returns a dummy `GamepadHandle`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `GamepadHandle`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
GamepadHandle(platform_impl::GamepadHandle::dummy())
}
/// Enumerate all attached gamepad/joystick devices.
pub fn enumerate<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
platform_impl::GamepadHandle::enumerate(&event_loop.event_loop)
}
/// Check to see if this gamepad/joystick device is still connected.
pub fn is_connected(&self) -> bool {
self.0.is_connected()
}
/// Attempts to set the rumble values for an attached controller. Input values are automatically
/// bound to a [`0.0`, `1.0`] range.
///
/// Certain gamepads assign different usages to the left and right motors - for example, X360
/// controllers treat the left motor as a low-frequency rumble and the right motor as a
/// high-frequency rumble. However, this cannot necessarily be assumed for all gamepad devices.
///
/// Note that, if the given gamepad does not support rumble, no error value gets thrown.
pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> {
self.0.rumble(left_speed, right_speed)
}
/// Gets the port number assigned to the gamepad.
pub fn port(&self) -> Option<u8> {
self.0.port()
}
/// Gets the controller's battery level.
///
/// If the controller doesn't report a battery level, this returns `None`.
pub fn battery_level(&self) -> Option<BatteryLevel> {
self.0.battery_level()
}
}
/// TODO: IS THIS THE RIGHT ABSTRACTION FOR ALL PLATFORMS?
/// This is exposed in its current form because it's what Microsoft does for XInput, and that's my
/// (@Osspial's) main point of reference. If you're implementing this on a different platform and
/// that platform exposes battery level differently, please bring it up in the tracking issue!
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum BatteryLevel {
Empty,
Low,
Medium,
Full,
}
impl fmt::Debug for MouseId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}
impl fmt::Debug for KeyboardId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}
impl fmt::Debug for HidId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}
impl fmt::Debug for GamepadHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}

View File

@@ -1,117 +1,48 @@
//! The [`EventLoop`] struct and assorted supporting types, including
//! [`ControlFlow`].
//! The `EventLoop` struct and assorted supporting types, including `ControlFlow`.
//!
//! If you want to send custom events to the event loop, use
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
//! [`send_event`](`EventLoopProxy::send_event`) method.
//! If you want to send custom events to the event loop, use [`EventLoop::create_proxy()`][create_proxy]
//! to acquire an [`EventLoopProxy`][event_loop_proxy] and call its [`send_event`][send_event] method.
//!
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
use std::marker::PhantomData;
//!
//! [create_proxy]: crate::event_loop::EventLoop::create_proxy
//! [event_loop_proxy]: crate::event_loop::EventLoopProxy
//! [send_event]: crate::event_loop::EventLoopProxy::send_event
use instant::Instant;
use std::ops::Deref;
use std::{error, fmt};
use instant::Instant;
use once_cell::sync::OnceCell;
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
///
/// An `EventLoop` can be seen more or less as a "context". Calling [`EventLoop::new`]
/// An `EventLoop` can be seen more or less as a "context". Calling `EventLoop::new()`
/// initializes everything that will be required to create windows. For example on Linux creating
/// an event loop opens a connection to the X or Wayland server.
///
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
/// To wake up an `EventLoop` from a another thread, see the `EventLoopProxy` docs.
///
/// Note that this cannot be shared across threads (due to platform-dependant logic
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access, the
/// [`Window`] created from this _can_ be sent to an other thread, and the
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
/// Note that the `EventLoop` cannot be shared across threads (due to platform-dependant logic
/// forbidding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the
/// `Window` created from this `EventLoop` _can_ be sent to an other thread, and the
/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread.
///
/// [`Window`]: crate::window::Window
pub struct EventLoop<T: 'static> {
pub(crate) event_loop: platform_impl::EventLoop<T>,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
}
/// Target that associates windows with an [`EventLoop`].
/// Target that associates windows with an `EventLoop`.
///
/// This type exists to allow you to create new windows while Winit executes
/// 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`.
pub struct EventLoopWindowTarget<T: 'static> {
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
}
/// Object that allows building the event loop.
///
/// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported.
#[derive(Default)]
pub struct EventLoopBuilder<T: 'static> {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
_p: PhantomData<T>,
}
impl EventLoopBuilder<()> {
/// Start building a new event loop.
#[inline]
pub fn new() -> Self {
Self::with_user_event()
}
}
impl<T> EventLoopBuilder<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
#[inline]
pub fn with_user_event() -> Self {
Self {
platform_specific: Default::default(),
_p: PhantomData,
}
}
/// Builds a new event loop.
///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
/// and only once per application.***
///
/// Attempting to create the event loop on a different thread, or multiple event loops in
/// the same application, will panic. This restriction isn't
/// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
/// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed
/// in the relevant [`platform`] module if the target platform supports creating an event loop on
/// any thread.
///
/// Calling this function will result in display backend initialisation.
///
/// ## Platform-specific
///
/// - **Linux:** Backend type can be controlled using an environment variable
/// `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
/// If it is not set, winit will try to connect to a Wayland connection, and if that fails,
/// will fall back on X11. If this variable is set with any other value, winit will panic.
///
/// [`platform`]: crate::platform
#[inline]
pub fn build(&mut self) -> EventLoop<T> {
static EVENT_LOOP_CREATED: OnceCell<()> = OnceCell::new();
if EVENT_LOOP_CREATED.set(()).is_err() {
panic!("Creating EventLoop multiple times is not supported.");
}
// Certain platforms accept a mutable reference in their API.
#[allow(clippy::unnecessary_mut_passed)]
EventLoop {
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific),
_marker: PhantomData,
}
}
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
}
impl<T> fmt::Debug for EventLoop<T> {
@@ -126,133 +57,77 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
}
}
/// Set by the user callback given to the [`EventLoop::run`] method.
/// Set by the user callback given to the `EventLoop::run` method.
///
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted.
///
/// Defaults to [`Poll`].
/// Indicates the desired behavior of the event loop after [`Event::EventsCleared`][events_cleared]
/// is emitted. Defaults to `Poll`.
///
/// ## Persistency
///
/// Almost every change is persistent between multiple calls to the event loop closure within a
/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset.
/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will
/// reset the control flow to [`Poll`].
/// given run loop. The only exception to this is `Exit` which, once set, cannot be unset. Changes
/// are **not** persistent between multiple calls to `run_return` - issuing a new call will reset
/// the control flow to `Poll`.
///
/// [`ExitWithCode`]: Self::ExitWithCode
/// [`Poll`]: Self::Poll
/// [events_cleared]: crate::event::Event::EventsCleared
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
///
/// ## Platform-specific
///
/// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes
/// the events in the queue may be sent before the next `requestAnimationFrame` callback, for
/// example when the scaling of the page has changed. This should be treated as an implementation
/// detail which should not be relied on.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the given time is reached.
///
/// Useful for implementing efficient timers. Applications which want to render at the display's
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API
/// to reduce odds of missed frames.
///
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
/// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set,
/// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
/// result in the `control_flow` parameter being reset to `ExitWithCode`.
///
/// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this
/// with exit code 0.
///
/// ## Platform-specific
///
/// - **Android / iOS / WASM:** The supplied exit code is unused.
/// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used,
/// which can cause surprises with negative exit values (`-42` would end up as `214`). See
/// [`std::process::exit`].
///
/// [`LoopDestroyed`]: Event::LoopDestroyed
/// [`Exit`]: ControlFlow::Exit
ExitWithCode(i32),
}
impl ControlFlow {
/// Alias for [`ExitWithCode`]`(0)`.
///
/// [`ExitWithCode`]: Self::ExitWithCode
#[allow(non_upper_case_globals)]
pub const Exit: Self = Self::ExitWithCode(0);
/// Sets this to [`Poll`].
///
/// [`Poll`]: Self::Poll
pub fn set_poll(&mut self) {
*self = Self::Poll;
}
/// Sets this to [`Wait`].
///
/// [`Wait`]: Self::Wait
pub fn set_wait(&mut self) {
*self = Self::Wait;
}
/// Sets this to [`WaitUntil`]`(instant)`.
///
/// [`WaitUntil`]: Self::WaitUntil
pub fn set_wait_until(&mut self, instant: Instant) {
*self = Self::WaitUntil(instant);
}
/// Sets this to [`ExitWithCode`]`(code)`.
///
/// [`ExitWithCode`]: Self::ExitWithCode
pub fn set_exit_with_code(&mut self, code: i32) {
*self = Self::ExitWithCode(code);
}
/// Sets this to [`Exit`].
///
/// [`Exit`]: Self::Exit
pub fn set_exit(&mut self) {
*self = Self::Exit;
}
/// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set,
/// `control_flow` cannot be changed from `Exit`, and any future attempts to do so will result
/// in the `control_flow` parameter being reset to `Exit`.
Exit,
}
impl Default for ControlFlow {
#[inline(always)]
fn default() -> Self {
Self::Poll
fn default() -> ControlFlow {
ControlFlow::Poll
}
}
impl EventLoop<()> {
/// Alias for [`EventLoopBuilder::new().build()`].
/// Builds a new event loop with a `()` as the user event type.
///
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
#[inline]
/// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.***
/// Attempting to create the event loop on a different thread will panic. This restriction isn't
/// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
/// porting to platforms that require it. `EventLoopExt::new_any_thread` functions are exposed
/// in the relevant `platform` module if the target platform supports creating an event loop on
/// any thread.
///
/// Usage will result in display backend initialisation, this can be controlled on linux
/// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
/// If it is not set, winit will try to connect to a wayland connection, and if it fails will
/// fallback on x11. If this variable is set with any other value, winit will panic.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
pub fn new() -> EventLoop<()> {
EventLoopBuilder::new().build()
}
}
impl Default for EventLoop<()> {
fn default() -> Self {
Self::new()
EventLoop::<()>::with_user_event()
}
}
impl<T> EventLoop<T> {
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
/// Builds a new event loop.
///
/// All caveats documented in [`EventLoop::new`] apply to this function.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
pub fn with_user_event() -> EventLoop<T> {
EventLoopBuilder::<T>::with_user_event().build()
EventLoop {
event_loop: platform_impl::EventLoop::new(),
_marker: ::std::marker::PhantomData,
}
}
/// Hijacks the calling thread and initializes the winit event loop with the provided
@@ -264,26 +139,38 @@ impl<T> EventLoop<T> {
///
/// Any values not passed to this function will *not* be dropped.
///
/// ## Platform-specific
///
/// - **X11 / Wayland:** The program terminates with exit code 1 if the display server
/// disconnects.
///
/// [`ControlFlow`]: crate::event_loop::ControlFlow
#[inline]
pub fn run<F>(self, event_handler: F) -> !
where
F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
F: 'static + FnMut(Event<T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
{
self.event_loop.run(event_handler)
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events to the main event loop.
/// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
event_loop_proxy: self.event_loop.create_proxy(),
}
}
/// Returns the list of all the monitors available on the system.
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.event_loop
.available_monitors()
.into_iter()
.map(|inner| MonitorHandle { inner })
}
/// Returns the primary monitor of the system.
#[inline]
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle {
inner: self.event_loop.primary_monitor(),
}
}
}
impl<T> Deref for EventLoop<T> {
@@ -293,59 +180,7 @@ impl<T> Deref for EventLoop<T> {
}
}
impl<T> EventLoopWindowTarget<T> {
/// Returns the list of all the monitors available on the system.
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.p
.available_monitors()
.into_iter()
.map(|inner| MonitorHandle { inner })
}
/// Returns the primary monitor of the system.
///
/// Returns `None` if it can't identify any monitor as a primary one.
///
/// ## Platform-specific
///
/// **Wayland:** Always returns `None`.
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
self.p.primary_monitor()
}
/// Change [`DeviceEvent`] filter mode.
///
/// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit
/// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
/// this filter at runtime to explicitly capture them again.
///
/// ## Platform-specific
///
/// - **Wayland / Windows / macOS / iOS / Android / Web:** Unsupported.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
self.p.set_device_event_filter(_filter);
}
}
unsafe impl<T> HasRawDisplayHandle for EventLoopWindowTarget<T> {
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> RawDisplayHandle {
self.p.raw_display_handle()
}
}
/// Used to send custom events to [`EventLoop`].
/// Used to send custom events to `EventLoop`.
pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>,
}
@@ -359,14 +194,12 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
}
impl<T: 'static> EventLoopProxy<T> {
/// Send an event to the [`EventLoop`] from which this proxy was created. This emits a
/// Send an event to the `EventLoop` from which this proxy was created. This emits a
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
/// function.
///
/// Returns an `Err` if the associated [`EventLoop`] no longer exists.
///
/// [`UserEvent(event)`]: Event::UserEvent
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
/// Returns an `Err` if the associated `EventLoop` no longer exists.
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.event_loop_proxy.send_event(event)
}
}
@@ -377,34 +210,19 @@ impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
}
}
/// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that
/// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that
/// no longer exists.
///
/// Contains the original event given to [`EventLoopProxy::send_event`].
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopClosed<T>(pub T);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct EventLoopClosed;
impl<T> fmt::Display for EventLoopClosed<T> {
impl fmt::Display for EventLoopClosed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Tried to wake up a closed `EventLoop`")
write!(f, "{}", error::Error::description(self))
}
}
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
/// Filter controlling the propagation of device events.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum DeviceEventFilter {
/// Always filter out device events.
Always,
/// Filter out device events while the window is not focused.
Unfocused,
/// Report all device events regardless of window focus.
Never,
}
impl Default for DeviceEventFilter {
fn default() -> Self {
Self::Unfocused
impl error::Error for EventLoopClosed {
fn description(&self) -> &str {
"Tried to wake up a closed `EventLoop`"
}
}

View File

@@ -1,5 +1,4 @@
use crate::platform_impl::PlatformIcon;
use std::{error::Error, fmt, io, mem};
use std::{error::Error, fmt, mem};
#[repr(C)]
#[derive(Debug)]
@@ -12,8 +11,8 @@ pub(crate) struct Pixel {
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
#[derive(Debug)]
/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// An error produced when using `Icon::from_rgba` with invalid arguments.
pub enum BadIcon {
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
/// safely interpreted as 32bpp RGBA pixels.
@@ -26,106 +25,72 @@ pub enum BadIcon {
width_x_height: usize,
pixel_count: usize,
},
/// Produced when underlying OS functionality failed to create the icon
OsError(io::Error),
}
impl fmt::Display for BadIcon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
let msg = match self {
&BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!(
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
byte_count,
),
BadIcon::DimensionsVsPixelCount {
&BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
} => write!(f,
} => format!(
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
width, height, pixel_count, width_x_height,
),
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e),
}
};
write!(f, "{}", msg)
}
}
impl Error for BadIcon {
fn source(&self) -> Option<&(dyn Error + 'static)> {
fn description(&self) -> &str {
"A valid icon cannot be created from these arguments"
}
fn cause(&self) -> Option<&dyn Error> {
Some(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RgbaIcon {
/// An icon used for the window titlebar, taskbar, etc.
pub struct Icon {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u32,
pub(crate) height: u32,
}
/// For platforms which don't have window icons (e.g. web)
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct NoIcon;
#[allow(dead_code)] // These are not used on every platform
mod constructors {
use super::*;
impl RgbaIcon {
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadIcon::ByteCountNotDivisibleBy4 {
byte_count: rgba.len(),
});
}
let pixel_count = rgba.len() / PIXEL_SIZE;
if pixel_count != (width * height) as usize {
Err(BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height: (width * height) as usize,
pixel_count,
})
} else {
Ok(RgbaIcon {
rgba,
width,
height,
})
}
}
}
impl NoIcon {
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
// Create the rgba icon anyway to validate the input
let _ = RgbaIcon::from_rgba(rgba, width, height)?;
Ok(NoIcon)
}
}
}
/// An icon used for the window titlebar, taskbar, etc.
#[derive(Clone)]
pub struct Icon {
pub(crate) inner: PlatformIcon,
}
impl fmt::Debug for Icon {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&self.inner, formatter)
}
}
impl Icon {
/// Creates an icon from 32bpp RGBA data.
/// Creates an `Icon` from 32bpp RGBA data.
///
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
Ok(Icon {
inner: PlatformIcon::from_rgba(rgba, width, height)?,
})
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadIcon::ByteCountNotDivisibleBy4 {
byte_count: rgba.len(),
});
}
let pixel_count = rgba.len() / PIXEL_SIZE;
if pixel_count != (width * height) as usize {
Err(BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height: (width * height) as usize,
pixel_count,
})
} else {
Ok(Icon {
rgba,
width,
height,
})
}
}
}

View File

@@ -1,6 +1,6 @@
//! Winit is a cross-platform window creation and event loop management library.
//! Winit allows you to build a window on as many platforms as possible.
//!
//! # Building windows
//! # Building a window
//!
//! Before you can build a [`Window`], you first need to build an [`EventLoop`]. This is done with the
//! [`EventLoop::new()`] function.
@@ -15,36 +15,31 @@
//! - Calling [`Window::new(&event_loop)`][window_new].
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
//!
//! The first method is the simplest, and will give you default values for everything. The second
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
//! The first way is the simplest way and will give you default values for everything.
//!
//! The second way allows you to customize the way your [`Window`] will look and behave by modifying
//! the fields of the [`WindowBuilder`] object before you create the [`Window`].
//!
//! # Event handling
//!
//! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can
//! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the
//! window or a key getting pressed while the window is focused. Devices can generate
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired.
//! generate a [`WindowEvent`] when certain things happen, like whenever the user moves their mouse
//! or presses a key inside the [`Window`]. Devices can generate a [`DeviceEvent`] directly as well,
//! which contains unfiltered event data that isn't specific to a certain window. Some user
//! activity, like mouse movement, can generate both a [`WindowEvent`] *and* a [`DeviceEvent`]. You
//! can also create and handle your own custom [`UserEvent`]s, if desired.
//!
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until the `control_flow` argument given to the closure is set to
//! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which
//! point [`Event`]`::`[`LoopDestroyed`] is emitted and the entire program terminates.
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
//! most other platforms. However, this model can be re-implemented to an extent with
//! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why
//! it's discouraged, beyond compatibility reasons.
//! Events can be retreived by using an [`EventLoop`]. A [`Window`] will send its events to the
//! [`EventLoop`] object it was created with.
//!
//! You do this by calling [`event_loop.run(...)`][event_loop_run]. This function will run forever
//! unless `control_flow` is set to [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`]
//! is emitted and the entire program terminates.
//!
//! ```no_run
//! use winit::{
//! event::{Event, WindowEvent},
//! event_loop::EventLoop,
//! event_loop::{ControlFlow, EventLoop},
//! window::WindowBuilder,
//! };
//!
@@ -52,71 +47,57 @@
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
//!
//! event_loop.run(move |event, _, control_flow| {
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! control_flow.set_poll();
//!
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
//! // This is ideal for non-game applications that only update in response to user
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! control_flow.set_wait();
//!
//! match event {
//! Event::EventsCleared => {
//! // Application update code.
//!
//! // Queue a RedrawRequested event.
//! window.request_redraw();
//! },
//! Event::WindowEvent {
//! event: WindowEvent::RedrawRequested,
//! ..
//! } => {
//! // Redraw the application.
//! //
//! // It's preferrable to render in this event rather than in EventsCleared, since
//! // rendering in here allows the program to gracefully handle redraws requested
//! // by the OS.
//! },
//! Event::WindowEvent {
//! event: WindowEvent::CloseRequested,
//! ..
//! } => {
//! println!("The close button was pressed; stopping");
//! control_flow.set_exit();
//! *control_flow = ControlFlow::Exit
//! },
//! Event::MainEventsCleared => {
//! // Application update code.
//!
//! // Queue a RedrawRequested event.
//! //
//! // You only need to call this if you've determined that you need to redraw, in
//! // applications which do not always need to. Applications that redraw continuously
//! // can just render here instead.
//! window.request_redraw();
//! },
//! Event::RedrawRequested(_) => {
//! // Redraw the application.
//! //
//! // It's preferable for applications that do not render continuously to render in
//! // this event rather than in MainEventsCleared, since rendering in here allows
//! // the program to gracefully handle redraws requested by the OS.
//! },
//! _ => ()
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! _ => *control_flow = ControlFlow::Poll,
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
//! // This is ideal for non-game applications that only update in response to user
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! // _ => *control_flow = ControlFlow::Wait,
//! }
//! });
//! ```
//!
//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`]
//! dispatched the event.
//! If you use multiple [`Window`]s, [`Event`]`::`[`WindowEvent`] has a member named `window_id`. You can
//! compare it with the value returned by the [`id()`][window_id_fn] method of [`Window`] in order to know which
//! [`Window`] has received the event.
//!
//! # Drawing on the window
//!
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//!
//! Note that many platforms will display garbage data in the window's client area if the
//! application doesn't render anything to the window by the time the desktop compositor is ready to
//! display the window to the user. If you notice this happening, you should create the window with
//! [`visible` set to `false`](crate::window::WindowBuilder::with_visible) and explicitly make the
//! window visible only once you're ready to render into it.
//! Winit doesn't provide any function that allows drawing on a [`Window`]. However it allows you to
//! retrieve the raw handle of the window (see the [`platform`] module), which in turn allows you
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the [`Window`].
//!
//! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return
//! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [event_loop_run]: event_loop::EventLoop::run
//! [`ControlFlow`]: event_loop::ControlFlow
//! [`Exit`]: event_loop::ControlFlow::Exit
//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
//! [`WindowBuilder`]: window::WindowBuilder
//! [window_new]: window::Window::new
//! [window_builder_new]: window::WindowBuilder::new
@@ -128,26 +109,26 @@
//! [`UserEvent`]: event::Event::UserEvent
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
//! [`platform`]: platform
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
#![deny(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)]
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
#![allow(clippy::missing_safety_doc)]
#![deny(intra_doc_link_resolution_failure)]
#[allow(unused_imports)]
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
#[macro_use]
#[cfg(any(target_os = "ios", target_os = "windows"))]
extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
#[cfg(all(target_arch = "wasm32", feature = "std_web"))]
extern crate std_web as stdweb;
pub mod dpi;
#[macro_use]
@@ -157,6 +138,7 @@ pub mod event_loop;
mod icon;
pub mod monitor;
mod platform_impl;
mod util;
pub mod window;
pub mod platform;

View File

@@ -1,10 +1,14 @@
//! Types useful for interacting with a user's monitors.
//!
//! If you want to get basic information about a monitor, you can use the
//! [`MonitorHandle`] type. This is retrieved from one of the following
//! methods, which return an iterator of [`MonitorHandle`]:
//! - [`EventLoopWindowTarget::available_monitors`](crate::event_loop::EventLoopWindowTarget::available_monitors).
//! - [`Window::available_monitors`](crate::window::Window::available_monitors).
//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle]
//! type. This is retreived from one of the following methods, which return an iterator of
//! [`MonitorHandle`][monitor_handle]:
//! - [`EventLoop::available_monitors`][loop_get]
//! - [`Window::available_monitors`][window_get].
//!
//! [monitor_handle]: crate::monitor::MonitorHandle
//! [loop_get]: crate::event_loop::EventLoop::available_monitors
//! [window_get]: crate::window::Window::available_monitors
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
platform_impl,
@@ -12,7 +16,10 @@ use crate::{
/// Describes a fullscreen video mode of a monitor.
///
/// Can be acquired with [`MonitorHandle::video_modes`].
/// Can be acquired with:
/// - [`MonitorHandle::video_modes`][monitor_get].
///
/// [monitor_get]: crate::monitor::MonitorHandle::video_modes
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) video_mode: platform_impl::VideoMode,
@@ -39,8 +46,8 @@ impl Ord for VideoMode {
self.monitor().cmp(&other.monitor()).then(
size.cmp(&other_size)
.then(
self.refresh_rate_millihertz()
.cmp(&other.refresh_rate_millihertz())
self.refresh_rate()
.cmp(&other.refresh_rate())
.then(self.bit_depth().cmp(&other.bit_depth())),
)
.reverse(),
@@ -51,7 +58,7 @@ impl Ord for VideoMode {
impl VideoMode {
/// Returns the resolution of this video mode.
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
self.video_mode.size()
}
@@ -68,10 +75,12 @@ impl VideoMode {
self.video_mode.bit_depth()
}
/// Returns the refresh rate of this video mode in mHz.
/// Returns the refresh rate of this video mode. **Note**: the returned
/// refresh rate is an integer approximation, and you shouldn't rely on this
/// value to be exact.
#[inline]
pub fn refresh_rate_millihertz(&self) -> u32 {
self.video_mode.refresh_rate_millihertz()
pub fn refresh_rate(&self) -> u16 {
self.video_mode.refresh_rate()
}
/// Returns the monitor that this video mode is valid for. Each monitor has
@@ -86,10 +95,10 @@ impl std::fmt::Display for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{} @ {} mHz ({} bpp)",
"{}x{} @ {} Hz ({} bpp)",
self.size().width,
self.size().height,
self.refresh_rate_millihertz(),
self.refresh_rate(),
self.bit_depth()
)
}
@@ -124,7 +133,7 @@ impl MonitorHandle {
///
/// - **Web:** Always returns (0,0)
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
self.inner.size()
}
@@ -135,31 +144,22 @@ impl MonitorHandle {
///
/// - **Web:** Always returns (0,0)
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
pub fn position(&self) -> PhysicalPosition {
self.inner.position()
}
/// The monitor refresh rate used by the system.
///
/// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to
/// enter fullscreen should be used instead.
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
self.inner.refresh_rate_millihertz()
}
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](crate::dpi) module for more information.
///
/// ## Platform-specific
///
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
/// - **Web:** Always returns 1.0
#[inline]
pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor()
pub fn hidpi_factor(&self) -> f64 {
self.inner.hidpi_factor()
}
/// Returns all fullscreen video modes supported by this monitor.

View File

@@ -1,40 +1,33 @@
#![cfg(any(target_os = "android"))]
use crate::{
event_loop::{EventLoop, EventLoopWindowTarget},
window::{Window, WindowBuilder},
};
use ndk::configuration::Configuration;
use ndk_glue::Rect;
use crate::{EventLoop, Window, WindowBuilder};
use std::os::raw::c_void;
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {}
/// Additional methods on `EventLoop` that are specific to Android.
pub trait EventLoopExtAndroid {
/// Makes it possible for glutin to register a callback when a suspend event happens on Android
fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>);
}
impl<T> EventLoopExtAndroid for EventLoop<T> {}
impl EventLoopExtAndroid for EventLoop {
fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>) {
self.event_loop.set_suspend_callback(cb);
}
}
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Android.
pub trait EventLoopWindowTargetExtAndroid {}
/// Additional methods on [`Window`] that are specific to Android.
/// Additional methods on `Window` that are specific to Android.
pub trait WindowExtAndroid {
fn content_rect(&self) -> Rect;
fn config(&self) -> Configuration;
fn native_window(&self) -> *const c_void;
}
impl WindowExtAndroid for Window {
fn content_rect(&self) -> Rect {
self.window.content_rect()
}
fn config(&self) -> Configuration {
self.window.config()
#[inline]
fn native_window(&self) -> *const c_void {
self.window.native_window()
}
}
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 {}
impl WindowBuilderExtAndroid for WindowBuilder {}

45
src/platform/desktop.rs Normal file
View File

@@ -0,0 +1,45 @@
#![cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"
))]
use crate::{
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
};
/// Additional methods on `EventLoop` that are specific to desktop platforms.
pub trait EventLoopExtDesktop {
/// A type provided by the user that can be passed through `Event::UserEvent`.
type UserEvent;
/// Initializes the `winit` event loop.
///
/// Unlike `run`, this function accepts non-`'static` (i.e. non-`move`) closures and returns
/// control flow to the caller when `control_flow` is set to `ControlFlow::Exit`.
///
/// # Caveats
/// Despite its apperance at first glance, this is *not* a perfect replacement for
/// `poll_events`. For example, this function will not return on Windows or macOS while a
/// window is getting resized, resulting in all application logic outside of the
/// `event_handler` closure not running until the resize operation ends. Other OS operations
/// may also result in such freezes. This behavior is caused by fundamental limitations in the
/// underyling OS APIs, which cannot be hidden by Winit without severe stability reprecussions.
///
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
fn run_return<F>(&mut self, event_handler: F)
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
}
impl<T> EventLoopExtDesktop for EventLoop<T> {
type UserEvent = T;
fn run_return<F>(&mut self, event_handler: F)
where
F: FnMut(Event<T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
{
self.event_loop.run_return(event_handler)
}
}

View File

@@ -43,14 +43,14 @@ pub trait WindowExtIOS {
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn ui_view(&self) -> *mut c_void;
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandle::scale_factor()`].
/// this to [`MonitorHandle::hidpi_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
fn set_scale_factor(&self, scale_factor: f64);
fn set_hidpi_factor(&self, hidpi_factor: f64);
/// Sets the valid orientations for the [`Window`].
///
@@ -113,8 +113,8 @@ impl WindowExtIOS for Window {
}
#[inline]
fn set_scale_factor(&self, scale_factor: f64) {
self.window.set_scale_factor(scale_factor)
fn set_hidpi_factor(&self, hidpi_factor: f64) {
self.window.set_hidpi_factor(hidpi_factor)
}
#[inline]
@@ -148,14 +148,14 @@ pub trait WindowBuilderExtIOS {
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandle::scale_factor()`].
/// this to [`MonitorHandle::hidpi_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
fn with_scale_factor(self, scale_factor: f64) -> WindowBuilder;
fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder;
/// Sets the valid orientations for the [`Window`].
///
@@ -204,8 +204,8 @@ impl WindowBuilderExtIOS for WindowBuilder {
}
#[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder {
self.platform_specific.scale_factor = Some(scale_factor);
fn with_hidpi_factor(mut self, hidpi_factor: f64) -> WindowBuilder {
self.platform_specific.hidpi_factor = Some(hidpi_factor);
self
}

View File

@@ -4,23 +4,46 @@ use std::os::raw::c_void;
use crate::{
dpi::LogicalSize,
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
/// Additional methods on [`Window`] that are specific to MacOS.
/// Corresponds to `NSRequestUserAttentionType`.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RequestUserAttentionType {
/// Corresponds to `NSCriticalRequest`.
///
/// Dock icon will bounce until the application is focused.
Critical,
/// Corresponds to `NSInformationalRequest`.
///
/// Dock icon will bounce once.
Informational,
}
impl Default for RequestUserAttentionType {
fn default() -> Self {
RequestUserAttentionType::Critical
}
}
/// Additional methods on `Window` that are specific to MacOS.
pub trait WindowExtMacOS {
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
/// The pointer will become invalid when the `Window` is destroyed.
fn ns_window(&self) -> *mut c_void;
/// Returns a pointer to the cocoa `NSView` that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
/// The pointer will become invalid when the `Window` is destroyed.
fn ns_view(&self) -> *mut c_void;
/// Request user attention, causing the application's dock icon to bounce.
/// Note that this has no effect if the application is already focused.
fn request_user_attention(&self, request_type: RequestUserAttentionType);
/// Returns whether or not the window is in simple fullscreen mode.
fn simple_fullscreen(&self) -> bool;
@@ -32,12 +55,6 @@ pub trait WindowExtMacOS {
/// And allows the user to have a fullscreen window without using another
/// space or taking control over the entire monitor.
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
/// Returns whether or not the window has shadow.
fn has_shadow(&self) -> bool;
/// Sets whether or not the window has shadow.
fn set_has_shadow(&self, has_shadow: bool);
}
impl WindowExtMacOS for Window {
@@ -51,6 +68,11 @@ impl WindowExtMacOS for Window {
self.window.ns_view()
}
#[inline]
fn request_user_attention(&self, request_type: RequestUserAttentionType) {
self.window.request_user_attention(request_type)
}
#[inline]
fn simple_fullscreen(&self) -> bool {
self.window.simple_fullscreen()
@@ -60,20 +82,10 @@ impl WindowExtMacOS for Window {
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window.set_simple_fullscreen(fullscreen)
}
#[inline]
fn has_shadow(&self) -> bool {
self.window.has_shadow()
}
#[inline]
fn set_has_shadow(&self, has_shadow: bool) {
self.window.set_has_shadow(has_shadow)
}
}
/// Corresponds to `NSApplicationActivationPolicy`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ActivationPolicy {
/// Corresponds to `NSApplicationActivationPolicyRegular`.
Regular,
@@ -89,15 +101,19 @@ impl Default for ActivationPolicy {
}
}
/// Additional methods on [`WindowBuilder`] that are specific to MacOS.
/// Additional methods on `WindowBuilder` that are specific to MacOS.
///
/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method:
/// - `with_titlebar_transparent`
/// - `with_title_hidden`
/// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view`
/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method
/// on the base `WindowBuilder`:
///
/// - `with_titlebar_transparent`
/// - `with_title_hidden`
/// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view`
pub trait WindowBuilderExtMacOS {
/// Sets the activation policy for the window being built.
fn with_activation_policy(self, activation_policy: ActivationPolicy) -> WindowBuilder;
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
fn with_movable_by_window_background(self, movable_by_window_background: bool)
-> WindowBuilder;
@@ -112,12 +128,17 @@ pub trait WindowBuilderExtMacOS {
/// Makes the window content appear behind the titlebar.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
/// Build window with `resizeIncrements` property. Values must not be 0.
fn with_resize_increments(self, increments: LogicalSize<f64>) -> WindowBuilder;
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
}
impl WindowBuilderExtMacOS for WindowBuilder {
#[inline]
fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder {
self.platform_specific.activation_policy = activation_policy;
self
}
#[inline]
fn with_movable_by_window_background(
mut self,
@@ -158,8 +179,8 @@ impl WindowBuilderExtMacOS for WindowBuilder {
}
#[inline]
fn with_resize_increments(mut self, increments: LogicalSize<f64>) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments);
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into());
self
}
@@ -168,75 +189,9 @@ impl WindowBuilderExtMacOS for WindowBuilder {
self.platform_specific.disallow_hidpi = disallow_hidpi;
self
}
#[inline]
fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder {
self.platform_specific.has_shadow = has_shadow;
self
}
}
pub trait EventLoopBuilderExtMacOS {
/// Sets the activation policy for the application.
///
/// It is set to [`ActivationPolicy::Regular`] by default.
///
/// # Example
///
/// Set the activation policy to "accessory".
///
/// ```
/// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "macos")]
/// use winit::platform::macos::{EventLoopBuilderExtMacOS, ActivationPolicy};
///
/// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "macos")]
/// builder.with_activation_policy(ActivationPolicy::Accessory);
/// # if false { // We can't test this part
/// let event_loop = builder.build();
/// # }
/// ```
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self;
/// Used to control whether a default menubar menu is created.
///
/// Menu creation is enabled by default.
///
/// # Example
///
/// Disable creating a default menubar.
///
/// ```
/// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "macos")]
/// use winit::platform::macos::EventLoopBuilderExtMacOS;
///
/// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "macos")]
/// builder.with_default_menu(false);
/// # if false { // We can't test this part
/// let event_loop = builder.build();
/// # }
/// ```
fn with_default_menu(&mut self, enable: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
#[inline]
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = activation_policy;
self
}
#[inline]
fn with_default_menu(&mut self, enable: bool) -> &mut Self {
self.platform_specific.default_menu = enable;
self
}
}
/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
/// Additional methods on `MonitorHandle` that are specific to MacOS.
pub trait MonitorHandleExtMacOS {
/// Returns the identifier of the monitor for Cocoa.
fn native_id(&self) -> u32;
@@ -254,21 +209,3 @@ impl MonitorHandleExtMacOS for MonitorHandle {
self.inner.ns_screen().map(|s| s as *mut c_void)
}
}
/// Additional methods on [`EventLoopWindowTarget`] that are specific to macOS.
pub trait EventLoopWindowTargetExtMacOS {
/// Hide the entire application. In most applications this is typically triggered with Command-H.
fn hide_application(&self);
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
fn hide_other_applications(&self);
}
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
fn hide_application(&self) {
self.p.hide_application()
}
fn hide_other_applications(&self) {
self.p.hide_other_applications()
}
}

View File

@@ -11,7 +11,7 @@
//!
//! And the following platform-specific module:
//!
//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`)
//! - `desktop` (available on `windows`, `unix`, and `macos`)
//!
//! However only the module corresponding to the platform you're compiling to will be available.
@@ -19,7 +19,7 @@ pub mod android;
pub mod ios;
pub mod macos;
pub mod unix;
pub mod web;
pub mod windows;
pub mod run_return;
pub mod desktop;
pub mod web;

View File

@@ -1,64 +0,0 @@
#![cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "android",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use crate::{
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
};
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunReturn {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
type UserEvent;
/// Initializes the `winit` event loop.
///
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures
/// and returns control flow to the caller when `control_flow` is set to [`ControlFlow::Exit`].
///
/// # Caveats
///
/// Despite its appearance at first glance, this is *not* a perfect replacement for
/// `poll_events`. For example, this function will not return on Windows or macOS while a
/// window is getting resized, resulting in all application logic outside of the
/// `event_handler` closure not running until the resize operation ends. Other OS operations
/// may also result in such freezes. This behavior is caused by fundamental limitations in the
/// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions.
///
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
///
/// ## Platform-specific
///
/// - **X11 / Wayland:** This function returns `1` upon disconnection from
/// the display server.
fn run_return<F>(&mut self, event_handler: F) -> i32
where
F: FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
);
}
impl<T> EventLoopExtRunReturn for EventLoop<T> {
type UserEvent = T;
fn run_return<F>(&mut self, event_handler: F) -> i32
where
F: FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
),
{
self.event_loop.run_return(event_handler)
}
}

View File

@@ -1,309 +1,362 @@
#![cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
use std::os::raw;
#[cfg(feature = "x11")]
use std::{ptr, sync::Arc};
use std::{os::raw, ptr, sync::Arc};
use smithay_client_toolkit::window::{ButtonState, Theme};
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
dpi::LogicalSize,
event_loop::{EventLoop, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
#[cfg(feature = "x11")]
use crate::dpi::Size;
#[cfg(feature = "x11")]
use crate::platform_impl::{x11::ffi::XVisualInfo, x11::XConnection, XLIB_ERROR_HOOKS};
use crate::platform_impl::{
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
x11::{ffi::XVisualInfo, XConnection},
EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
};
// TODO: stupid hack so that glutin can do its work
#[doc(hidden)]
#[cfg(feature = "x11")]
pub use crate::platform_impl::x11;
#[cfg(feature = "x11")]
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
#[cfg(feature = "wayland")]
pub use crate::window::Theme;
/// Theme for wayland client side decorations
///
/// Colors must be in ARGB8888 format
pub struct WaylandTheme {
/// Primary color when the window is focused
pub primary_active: [u8; 4],
/// Primary color when the window is unfocused
pub primary_inactive: [u8; 4],
/// Secondary color when the window is focused
pub secondary_active: [u8; 4],
/// Secondary color when the window is unfocused
pub secondary_inactive: [u8; 4],
/// Close button color when hovered over
pub close_button_hovered: [u8; 4],
/// Close button color
pub close_button: [u8; 4],
/// Close button color when hovered over
pub maximize_button_hovered: [u8; 4],
/// Maximize button color
pub maximize_button: [u8; 4],
/// Minimize button color when hovered over
pub minimize_button_hovered: [u8; 4],
/// Minimize button color
pub minimize_button: [u8; 4],
}
/// 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
/// indicator whether the error was handled by the callback.
///
/// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent
#[cfg(feature = "x11")]
pub type XlibErrorHook =
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
struct WaylandThemeObject(WaylandTheme);
/// Hook to winit's xlib error handling callback.
///
/// This method is provided as a safe way to handle the errors comming from X11 when using xlib
/// in external crates, like glutin for GLX access. Trying to handle errors by speculating with
/// `XSetErrorHandler` is [`unsafe`].
///
/// [`unsafe`]: https://www.remlab.net/op/xlib.shtml
#[inline]
#[cfg(feature = "x11")]
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook.
unsafe {
XLIB_ERROR_HOOKS.lock().push(hook);
impl Theme for WaylandThemeObject {
fn get_primary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.primary_active
} else {
self.0.primary_inactive
}
}
// Used for division line
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.secondary_active
} else {
self.0.secondary_inactive
}
}
fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.close_button_hovered,
_ => self.0.close_button,
}
}
fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.maximize_button_hovered,
_ => self.0.maximize_button,
}
}
fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.minimize_button_hovered,
_ => self.0.minimize_button,
}
}
}
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Unix.
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
pub trait EventLoopWindowTargetExtUnix {
/// True if the [`EventLoopWindowTarget`] uses Wayland.
#[cfg(feature = "wayland")]
/// True if the `EventLoopWindowTarget` uses Wayland.
fn is_wayland(&self) -> bool;
/// True if the [`EventLoopWindowTarget`] uses X11.
#[cfg(feature = "x11")]
///
/// True if the `EventLoopWindowTarget` uses X11.
fn is_x11(&self) -> bool;
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this
/// [`EventLoopWindowTarget`].
/// `EventLoopWindowTarget`.
///
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
/// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
///
/// [`EventLoop`]: crate::event_loop::EventLoop
#[cfg(feature = "wayland")]
/// The pointer will become invalid when the winit `EventLoop` is destroyed.
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
#[inline]
#[cfg(feature = "wayland")]
fn is_wayland(&self) -> bool {
self.p.is_wayland()
}
#[inline]
#[cfg(feature = "x11")]
fn is_x11(&self) -> bool {
!self.p.is_wayland()
}
#[inline]
#[cfg(feature = "x11")]
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.p {
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.p {
LinuxEventLoopWindowTarget::Wayland(ref p) => {
Some(p.display().get_display_ptr() as *mut _)
}
#[cfg(feature = "x11")]
_ => None,
}
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to Unix.
pub trait EventLoopBuilderExtUnix {
/// Force using X11.
#[cfg(feature = "x11")]
fn with_x11(&mut self) -> &mut Self;
/// Force using Wayland.
#[cfg(feature = "wayland")]
fn with_wayland(&mut self) -> &mut Self;
/// Whether to allow the event loop to be created off of the main thread.
/// Additional methods on `EventLoop` that are specific to Unix.
pub trait EventLoopExtUnix {
/// Builds a new `EventLoop` that is forced to use X11.
///
/// By default, the window is only allowed to be created on the main
/// thread, to make platform compatibility easier.
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
/// # Panics
///
/// If called outside the main thread. To initialize an X11 event loop outside
/// the main thread, use [`new_x11_any_thread`](#tymethod.new_x11_any_thread).
fn new_x11() -> Result<Self, XNotSupported>
where
Self: Sized;
/// Builds a new `EventLoop` that is forced to use Wayland.
///
/// # Panics
///
/// If called outside the main thread. To initialize a Wayland event loop outside
/// the main thread, use [`new_wayland_any_thread`](#tymethod.new_wayland_any_thread).
fn new_wayland() -> Self
where
Self: Sized;
/// Builds a new `EventLoop` on any thread.
///
/// This method bypasses the cross-platform compatibility requirement
/// that `EventLoop` be created on the main thread.
fn new_any_thread() -> Self
where
Self: Sized;
/// Builds a new X11 `EventLoop` on any thread.
///
/// This method bypasses the cross-platform compatibility requirement
/// that `EventLoop` be created on the main thread.
fn new_x11_any_thread() -> Result<Self, XNotSupported>
where
Self: Sized;
/// Builds a new Wayland `EventLoop` on any thread.
///
/// This method bypasses the cross-platform compatibility requirement
/// that `EventLoop` be created on the main thread.
fn new_wayland_any_thread() -> Self
where
Self: Sized;
}
impl<T> EventLoopBuilderExtUnix for EventLoopBuilder<T> {
#[inline]
#[cfg(feature = "x11")]
fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::X);
self
}
#[inline]
#[cfg(feature = "wayland")]
fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::Wayland);
self
}
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
fn wrap_ev<T>(event_loop: LinuxEventLoop<T>) -> EventLoop<T> {
EventLoop {
event_loop,
_marker: std::marker::PhantomData,
}
}
/// Additional methods on [`Window`] that are specific to Unix.
impl<T> EventLoopExtUnix for EventLoop<T> {
#[inline]
fn new_any_thread() -> Self {
wrap_ev(LinuxEventLoop::new_any_thread())
}
#[inline]
fn new_x11_any_thread() -> Result<Self, XNotSupported> {
LinuxEventLoop::new_x11_any_thread().map(wrap_ev)
}
#[inline]
fn new_wayland_any_thread() -> Self {
wrap_ev(
LinuxEventLoop::new_wayland_any_thread()
// TODO: propagate
.expect("failed to open Wayland connection"),
)
}
#[inline]
fn new_x11() -> Result<Self, XNotSupported> {
LinuxEventLoop::new_x11().map(wrap_ev)
}
#[inline]
fn new_wayland() -> Self {
wrap_ev(
LinuxEventLoop::new_wayland()
// TODO: propagate
.expect("failed to open Wayland connection"),
)
}
}
/// Additional methods on `Window` that are specific to Unix.
pub trait WindowExtUnix {
/// Returns the ID of the [`Window`] xlib object that is used by this window.
/// Returns the ID of the `Window` xlib object that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
#[cfg(feature = "x11")]
fn xlib_window(&self) -> Option<raw::c_ulong>;
/// Returns a pointer to the `Display` object of xlib that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "x11")]
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn xlib_display(&self) -> Option<*mut raw::c_void>;
#[cfg(feature = "x11")]
fn xlib_screen_id(&self) -> Option<raw::c_int>;
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Set window urgency hint (`XUrgencyHint`). Only relevant on X.
fn set_urgent(&self, is_urgent: bool);
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "x11")]
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "wayland")]
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "wayland")]
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn wayland_display(&self) -> Option<*mut raw::c_void>;
/// Updates [`Theme`] of window decorations.
///
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
/// Possible values for env variable are: "dark" and light"
#[cfg(feature = "wayland")]
fn wayland_set_csd_theme(&self, config: Theme);
/// Sets the color theme of the client side window decorations on wayland
fn set_wayland_theme(&self, theme: WaylandTheme);
/// Check if the window is ready for drawing
///
/// It is a remnant of a previous implementation detail for the
/// wayland backend, and is no longer relevant.
///
/// Always return `true`.
/// Always return true.
#[deprecated]
fn is_ready(&self) -> bool;
}
impl WindowExtUnix for Window {
#[inline]
#[cfg(feature = "x11")]
fn xlib_window(&self) -> Option<raw::c_ulong> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_window()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_display()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_xconnection()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn set_urgent(&self, is_urgent: bool) {
if let LinuxWindow::X(ref w) = self.window {
w.set_urgent(is_urgent);
}
}
#[inline]
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _),
#[cfg(feature = "x11")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _),
#[cfg(feature = "x11")]
LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _),
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_set_csd_theme(&self, theme: Theme) {
#[allow(clippy::single_match)]
fn set_wayland_theme(&self, theme: WaylandTheme) {
match self.window {
LinuxWindow::Wayland(ref w) => w.set_csd_theme(theme),
#[cfg(feature = "x11")]
_ => (),
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)),
_ => {}
}
}
@@ -313,141 +366,87 @@ impl WindowExtUnix for Window {
}
}
/// Additional methods on [`WindowBuilder`] that are specific to Unix.
/// Additional methods on `WindowBuilder` that are specific to Unix.
pub trait WindowBuilderExtUnix {
#[cfg(feature = "x11")]
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
#[cfg(feature = "x11")]
fn with_x11_screen(self, screen_id: i32) -> Self;
/// Build window with the given `general` and `instance` names.
///
/// On Wayland, the `general` name sets an application ID, which should match the `.desktop`
/// file destributed with your program. The `instance` is a `no-op`.
///
/// On X11, the `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
fn with_class(self, class: String, instance: String) -> Self;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
fn with_gtk_theme_variant(self, variant: String) -> Self;
/// Build window with resize increment hint. Only implemented on X11.
fn with_resize_increments(self, increments: LogicalSize) -> Self;
/// Build window with base size hint. Only implemented on X11.
fn with_base_size(self, base_size: LogicalSize) -> Self;
/// Build window with a given application ID. It should match the `.desktop` file distributed with
/// your program. Only relevant on Wayland.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
#[cfg(feature = "x11")]
fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
#[cfg(feature = "x11")]
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
#[cfg(feature = "x11")]
fn with_gtk_theme_variant(self, variant: String) -> Self;
/// Build window with certain decoration [`Theme`]
///
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
/// Possible values for env variable are: "dark" and light"
#[cfg(feature = "wayland")]
fn with_wayland_csd_theme(self, theme: Theme) -> Self;
/// Build window with resize increment hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder;
/// # use winit::platform::unix::WindowBuilderExtUnix;
/// // Specify the size in logical dimensions like this:
/// WindowBuilder::new().with_resize_increments(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// WindowBuilder::new().with_resize_increments(PhysicalSize::new(400, 200));
/// ```
#[cfg(feature = "x11")]
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
/// Build window with base size hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder;
/// # use winit::platform::unix::WindowBuilderExtUnix;
/// // Specify the size in logical dimensions like this:
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
/// ```
#[cfg(feature = "x11")]
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
fn with_app_id(self, app_id: String) -> Self;
}
impl WindowBuilderExtUnix for WindowBuilder {
#[inline]
#[cfg(feature = "x11")]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
{
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
}
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.platform_specific.screen_id = Some(screen_id);
self
}
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
fn with_class(mut self, instance: String, class: String) -> Self {
self.platform_specific.class = Some((instance, class));
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.override_redirect = override_redirect;
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11_window_types = x11_window_types;
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_gtk_theme_variant(mut self, variant: String) -> Self {
self.platform_specific.gtk_theme_variant = Some(variant);
self
}
#[inline]
#[cfg(feature = "wayland")]
fn with_wayland_csd_theme(mut self, theme: Theme) -> Self {
self.platform_specific.csd_theme = Some(theme);
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self {
fn with_resize_increments(mut self, increments: LogicalSize) -> Self {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
fn with_base_size(mut self, base_size: LogicalSize) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self
}
#[inline]
fn with_app_id(mut self, app_id: String) -> Self {
self.platform_specific.app_id = Some(app_id);
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Linux.

View File

@@ -1,91 +1,22 @@
#![cfg(target_arch = "wasm32")]
//! The web target does not automatically insert the canvas element object into the web page, to
//! allow end users to determine how the page should be laid out. Use the [`WindowExtWebSys`] trait
//! to retrieve the canvas from the Window. Alternatively, use the [`WindowBuilderExtWebSys`] trait
//! to provide your own canvas.
//! allow end users to determine how the page should be laid out. Use the `WindowExtStdweb` or
//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the
//! Window.
use crate::event::Event;
use crate::event_loop::ControlFlow;
use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget;
use crate::window::WindowBuilder;
#[cfg(feature = "stdweb")]
use stdweb::web::html_element::CanvasElement;
#[cfg(feature = "stdweb")]
pub trait WindowExtStdweb {
fn canvas(&self) -> CanvasElement;
}
#[cfg(feature = "web-sys")]
use web_sys::HtmlCanvasElement;
#[cfg(feature = "web-sys")]
pub trait WindowExtWebSys {
fn canvas(&self) -> HtmlCanvasElement;
/// Whether the browser reports the preferred color scheme to be "dark".
fn is_dark_mode(&self) -> bool;
}
pub trait WindowBuilderExtWebSys {
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
/// Whether `event.preventDefault` should be automatically called to prevent event propagation
/// when appropriate.
///
/// For example, mouse wheel events are only handled by the canvas by default. This avoids
/// the default behavior of scrolling the page.
fn with_prevent_default(self, prevent_default: bool) -> Self;
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
/// canvas keyboard events.
fn with_focusable(self, focusable: bool) -> Self;
}
impl WindowBuilderExtWebSys for WindowBuilder {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.canvas = canvas;
self
}
fn with_prevent_default(mut self, prevent_default: bool) -> Self {
self.platform_specific.prevent_default = prevent_default;
self
}
fn with_focusable(mut self, focusable: bool) -> Self {
self.platform_specific.focusable = focusable;
self
}
}
/// Additional methods on `EventLoop` that are specific to the web.
pub trait EventLoopExtWebSys {
/// A type provided by the user that can be passed through `Event::UserEvent`.
type UserEvent;
/// Initializes the winit event loop.
///
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
/// satisfy its `!` return type.
fn spawn<F>(self, event_handler: F)
where
F: 'static
+ FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
);
}
impl<T> EventLoopExtWebSys for EventLoop<T> {
type UserEvent = T;
fn spawn<F>(self, event_handler: F)
where
F: 'static
+ FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
),
{
self.event_loop.spawn(event_handler)
}
}

View File

@@ -1,252 +1,117 @@
#![cfg(target_os = "windows")]
use std::{ffi::c_void, path::Path};
use std::os::raw::c_void;
use libc;
use winapi::shared::windef::HWND;
use crate::{
dpi::PhysicalSize,
event::DeviceId,
event_loop::EventLoopBuilder,
event::device::{GamepadHandle, KeyboardId, MouseId},
event_loop::EventLoop,
monitor::MonitorHandle,
platform_impl::{Parent, WinIcon},
window::{BadIcon, Icon, Theme, Window, WindowBuilder},
platform_impl::EventLoop as WindowsEventLoop,
window::{Icon, Window, WindowBuilder},
};
/// Window Handle type used by Win32 API
pub type HWND = isize;
/// Menu Handle type used by Win32 API
pub type HMENU = isize;
/// Monitor Handle type used by Win32 API
pub type HMONITOR = isize;
/// Instance Handle type used by Win32 API
pub type HINSTANCE = isize;
/// Additional methods on `EventLoop` that are specific to Windows.
pub trait EventLoopBuilderExtWindows {
/// Whether to allow the event loop to be created off of the main thread.
///
/// By default, the window is only allowed to be created on the main
/// thread, to make platform compatibility easier.
pub trait EventLoopExtWindows {
/// Creates an event loop off of the main thread.
///
/// # `Window` caveats
///
/// Note that any `Window` created on the new thread will be destroyed when the thread
/// terminates. Attempting to use a `Window` after its parent thread terminates has
/// unspecified, although explicitly not undefined, behavior.
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
/// Whether to enable process-wide DPI awareness.
///
/// By default, `winit` will attempt to enable process-wide DPI awareness. If
/// that's undesirable, you can disable it with this function.
///
/// # Example
///
/// Disable process-wide DPI awareness.
///
/// ```
/// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "windows")]
/// use winit::platform::windows::EventLoopBuilderExtWindows;
///
/// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "windows")]
/// builder.with_dpi_aware(false);
/// # if false { // We can't test this part
/// let event_loop = builder.build();
/// # }
/// ```
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self;
/// A callback to be executed before dispatching a win32 message to the window procedure.
/// Return true to disable winit's internal message dispatching.
///
/// # Example
///
/// ```
/// # use windows_sys::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG};
/// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "windows")]
/// use winit::platform::windows::EventLoopBuilderExtWindows;
///
/// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "windows")]
/// builder.with_msg_hook(|msg|{
/// let msg = msg as *const MSG;
/// # let accels: Vec<ACCEL> = Vec::new();
/// let translated = unsafe {
/// TranslateAcceleratorW(
/// (*msg).hwnd,
/// CreateAcceleratorTableW(accels.as_ptr() as _, 1),
/// msg,
/// ) == 1
/// };
/// translated
/// });
/// ```
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
fn new_any_thread() -> Self
where
F: FnMut(*const c_void) -> bool + 'static;
Self: Sized;
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's
/// undesirable, you can create an `EventLoop` using this function instead.
fn new_dpi_unaware() -> Self
where
Self: Sized;
/// Creates a DPI-unaware event loop off of the main thread.
///
/// The `Window` caveats in [`new_any_thread`](EventLoopExtWindows::new_any_thread) also apply here.
fn new_dpi_unaware_any_thread() -> Self
where
Self: Sized;
}
impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
impl<T> EventLoopExtWindows for EventLoop<T> {
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
fn new_any_thread() -> Self {
EventLoop {
event_loop: WindowsEventLoop::new_any_thread(),
_marker: ::std::marker::PhantomData,
}
}
#[inline]
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self {
self.platform_specific.dpi_aware = dpi_aware;
self
fn new_dpi_unaware() -> Self {
EventLoop {
event_loop: WindowsEventLoop::new_dpi_unaware(),
_marker: ::std::marker::PhantomData,
}
}
#[inline]
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
where
F: FnMut(*const c_void) -> bool + 'static,
{
self.platform_specific.msg_hook = Some(Box::new(callback));
self
fn new_dpi_unaware_any_thread() -> Self {
EventLoop {
event_loop: WindowsEventLoop::new_dpi_unaware_any_thread(),
_marker: ::std::marker::PhantomData,
}
}
}
/// Additional methods on `Window` that are specific to Windows.
pub trait WindowExtWindows {
/// Returns the HINSTANCE of the window
fn hinstance(&self) -> HINSTANCE;
fn hinstance(&self) -> *mut libc::c_void;
/// Returns the native handle that is used by this window.
///
/// The pointer will become invalid when the native window was destroyed.
fn hwnd(&self) -> HWND;
/// Enables or disables mouse and keyboard input to the specified window.
///
/// A window must be enabled before it can be activated.
/// If an application has create a modal dialog box by disabling its owner window
/// (as described in [`WindowBuilderExtWindows::with_owner_window`]), the application must enable
/// the owner window before destroying the dialog box.
/// Otherwise, another window will receive the keyboard focus and be activated.
///
/// If a child window is disabled, it is ignored when the system tries to determine which
/// window should receive mouse messages.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks>
/// and <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#disabled-windows>
fn set_enable(&self, enabled: bool);
fn hwnd(&self) -> *mut libc::c_void;
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
/// Returns the current window theme.
fn theme(&self) -> Theme;
/// Whether to show or hide the window icon in the taskbar.
fn set_skip_taskbar(&self, skip: bool);
}
impl WindowExtWindows for Window {
#[inline]
fn hinstance(&self) -> HINSTANCE {
self.window.hinstance()
fn hinstance(&self) -> *mut libc::c_void {
self.window.hinstance() as *mut _
}
#[inline]
fn hwnd(&self) -> HWND {
self.window.hwnd()
}
#[inline]
fn set_enable(&self, enabled: bool) {
self.window.set_enable(enabled)
fn hwnd(&self) -> *mut libc::c_void {
self.window.hwnd() as *mut _
}
#[inline]
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
self.window.set_taskbar_icon(taskbar_icon)
}
#[inline]
fn theme(&self) -> Theme {
self.window.theme()
}
#[inline]
fn set_skip_taskbar(&self, skip: bool) {
self.window.set_skip_taskbar(skip)
}
}
/// Additional methods on `WindowBuilder` that are specific to Windows.
pub trait WindowBuilderExtWindows {
/// Sets a parent to the window to be created.
///
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
/// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable)
/// on the owner window to create a modal dialog box.
///
/// From MSDN:
/// - An owned window is always above its owner in the z-order.
/// - The system automatically destroys an owned window when its owner is destroyed.
/// - An owned window is hidden when its owner is minimized.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
fn with_owner_window(self, parent: HWND) -> WindowBuilder;
/// Sets a menu on the window to be created.
///
/// Parent and menu are mutually exclusive; a child window cannot have a menu!
///
/// The menu must have been manually created beforehand with [`CreateMenu`] or similar.
///
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
///
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu
fn with_menu(self, menu: HMENU) -> WindowBuilder;
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
/// Enables or disables drag and drop support (enabled by default). Will interfere with other crates
/// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of
/// `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still attempt to initialize
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future.
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions.
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
fn with_drag_and_drop(self, flag: bool) -> WindowBuilder;
/// Forces a theme or uses the system settings if `None` was provided.
fn with_theme(self, theme: Option<Theme>) -> WindowBuilder;
/// Whether show or hide the window icon in the taskbar.
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
}
impl WindowBuilderExtWindows for WindowBuilder {
#[inline]
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
self.platform_specific.parent = Parent::ChildOf(parent);
self
}
#[inline]
fn with_owner_window(mut self, parent: HWND) -> WindowBuilder {
self.platform_specific.parent = Parent::OwnedBy(parent);
self
}
#[inline]
fn with_menu(mut self, menu: HMENU) -> WindowBuilder {
self.platform_specific.menu = Some(menu);
self.platform_specific.parent = Some(parent);
self
}
@@ -261,24 +126,6 @@ impl WindowBuilderExtWindows for WindowBuilder {
self.platform_specific.no_redirection_bitmap = flag;
self
}
#[inline]
fn with_drag_and_drop(mut self, flag: bool) -> WindowBuilder {
self.platform_specific.drag_and_drop = flag;
self
}
#[inline]
fn with_theme(mut self, theme: Option<Theme>) -> WindowBuilder {
self.platform_specific.preferred_theme = theme;
self
}
#[inline]
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
self.platform_specific.skip_taskbar = skip;
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Windows.
@@ -287,7 +134,7 @@ pub trait MonitorHandleExtWindows {
fn native_id(&self) -> String;
/// Returns the handle of the monitor - `HMONITOR`.
fn hmonitor(&self) -> HMONITOR;
fn hmonitor(&self) -> *mut c_void;
}
impl MonitorHandleExtWindows for MonitorHandle {
@@ -297,59 +144,54 @@ impl MonitorHandleExtWindows for MonitorHandle {
}
#[inline]
fn hmonitor(&self) -> HMONITOR {
self.inner.hmonitor()
fn hmonitor(&self) -> *mut c_void {
self.inner.hmonitor() as *mut _
}
}
/// Additional methods on `DeviceId` that are specific to Windows.
pub trait DeviceIdExtWindows {
/// Additional methods on device types that are specific to Windows.
pub trait DeviceExtWindows {
/// Returns an identifier that persistently refers to this specific device.
///
/// Will return `None` if the device is no longer available.
fn persistent_identifier(&self) -> Option<String>;
/// Returns the handle of the device - `HANDLE`.
fn handle(&self) -> *mut c_void;
}
impl DeviceIdExtWindows for DeviceId {
impl DeviceExtWindows for MouseId {
#[inline]
fn persistent_identifier(&self) -> Option<String> {
self.0.persistent_identifier()
}
}
/// Additional methods on `Icon` that are specific to Windows.
pub trait IconExtWindows: Sized {
/// Create an icon from a file path.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
-> Result<Self, BadIcon>;
/// Create an icon from a resource embedded in this executable or library.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
}
impl IconExtWindows for Icon {
fn from_path<P: AsRef<Path>>(
path: P,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
let win_icon = WinIcon::from_path(path, size)?;
Ok(Icon { inner: win_icon })
}
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon })
#[inline]
fn handle(&self) -> *mut c_void {
self.0.handle() as _
}
}
impl DeviceExtWindows for KeyboardId {
#[inline]
fn persistent_identifier(&self) -> Option<String> {
self.0.persistent_identifier()
}
#[inline]
fn handle(&self) -> *mut c_void {
self.0.handle() as _
}
}
impl DeviceExtWindows for GamepadHandle {
#[inline]
fn persistent_identifier(&self) -> Option<String> {
self.0.persistent_identifier()
}
#[inline]
fn handle(&self) -> *mut c_void {
self.0.handle() as _
}
}

View File

@@ -0,0 +1,122 @@
#![allow(dead_code)]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
use libc;
use std::os::raw;
#[link(name = "android")]
#[link(name = "EGL")]
#[link(name = "GLESv2")]
extern "C" {}
/**
** asset_manager.h
**/
pub type AAssetManager = raw::c_void;
/**
** native_window.h
**/
pub type ANativeWindow = raw::c_void;
extern "C" {
pub fn ANativeWindow_getHeight(window: *const ANativeWindow) -> libc::int32_t;
pub fn ANativeWindow_getWidth(window: *const ANativeWindow) -> libc::int32_t;
}
/**
** native_activity.h
**/
pub type JavaVM = ();
pub type JNIEnv = ();
pub type jobject = *const libc::c_void;
pub type AInputQueue = (); // FIXME: wrong
pub type ARect = (); // FIXME: wrong
#[repr(C)]
pub struct ANativeActivity {
pub callbacks: *mut ANativeActivityCallbacks,
pub vm: *mut JavaVM,
pub env: *mut JNIEnv,
pub clazz: jobject,
pub internalDataPath: *const libc::c_char,
pub externalDataPath: *const libc::c_char,
pub sdkVersion: libc::int32_t,
pub instance: *mut libc::c_void,
pub assetManager: *mut AAssetManager,
pub obbPath: *const libc::c_char,
}
#[repr(C)]
pub struct ANativeActivityCallbacks {
pub onStart: extern "C" fn(*mut ANativeActivity),
pub onResume: extern "C" fn(*mut ANativeActivity),
pub onSaveInstanceState: extern "C" fn(*mut ANativeActivity, *mut libc::size_t),
pub onPause: extern "C" fn(*mut ANativeActivity),
pub onStop: extern "C" fn(*mut ANativeActivity),
pub onDestroy: extern "C" fn(*mut ANativeActivity),
pub onWindowFocusChanged: extern "C" fn(*mut ANativeActivity, libc::c_int),
pub onNativeWindowCreated: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
pub onNativeWindowResized: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
pub onNativeWindowRedrawNeeded: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
pub onNativeWindowDestroyed: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
pub onInputQueueCreated: extern "C" fn(*mut ANativeActivity, *mut AInputQueue),
pub onInputQueueDestroyed: extern "C" fn(*mut ANativeActivity, *mut AInputQueue),
pub onContentRectChanged: extern "C" fn(*mut ANativeActivity, *const ARect),
pub onConfigurationChanged: extern "C" fn(*mut ANativeActivity),
pub onLowMemory: extern "C" fn(*mut ANativeActivity),
}
/**
** looper.h
**/
pub type ALooper = ();
#[link(name = "android")]
extern "C" {
pub fn ALooper_forThread() -> *const ALooper;
pub fn ALooper_acquire(looper: *const ALooper);
pub fn ALooper_release(looper: *const ALooper);
pub fn ALooper_prepare(opts: libc::c_int) -> *const ALooper;
pub fn ALooper_pollOnce(
timeoutMillis: libc::c_int,
outFd: *mut libc::c_int,
outEvents: *mut libc::c_int,
outData: *mut *mut libc::c_void,
) -> libc::c_int;
pub fn ALooper_pollAll(
timeoutMillis: libc::c_int,
outFd: *mut libc::c_int,
outEvents: *mut libc::c_int,
outData: *mut *mut libc::c_void,
) -> libc::c_int;
pub fn ALooper_wake(looper: *const ALooper);
pub fn ALooper_addFd(
looper: *const ALooper,
fd: libc::c_int,
ident: libc::c_int,
events: libc::c_int,
callback: ALooper_callbackFunc,
data: *mut libc::c_void,
) -> libc::c_int;
pub fn ALooper_removeFd(looper: *const ALooper, fd: libc::c_int) -> libc::c_int;
}
pub const ALOOPER_PREPARE_ALLOW_NON_CALLBACKS: libc::c_int = 1 << 0;
pub const ALOOPER_POLL_WAKE: libc::c_int = -1;
pub const ALOOPER_POLL_CALLBACK: libc::c_int = -2;
pub const ALOOPER_POLL_TIMEOUT: libc::c_int = -3;
pub const ALOOPER_POLL_ERROR: libc::c_int = -4;
pub const ALOOPER_EVENT_INPUT: libc::c_int = 1 << 0;
pub const ALOOPER_EVENT_OUTPUT: libc::c_int = 1 << 1;
pub const ALOOPER_EVENT_ERROR: libc::c_int = 1 << 2;
pub const ALOOPER_EVENT_HANGUP: libc::c_int = 1 << 3;
pub const ALOOPER_EVENT_INVALID: libc::c_int = 1 << 4;
pub type ALooper_callbackFunc =
extern "C" fn(libc::c_int, libc::c_int, *mut libc::c_void) -> libc::c_int;

File diff suppressed because it is too large Load Diff

View File

@@ -10,19 +10,17 @@ use std::{
};
use objc::runtime::{BOOL, YES};
use once_cell::sync::Lazy;
use crate::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
event_loop::ControlFlow,
platform_impl::platform::{
event_loop::{EventHandler, EventProxy, EventWrapper, Never},
event_loop::{EventHandler, Never},
ffi::{
id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer,
CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger,
NSOperatingSystemVersion, NSUInteger,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, NSInteger, NSOperatingSystemVersion,
NSUInteger,
},
},
window::WindowId as RootWindowId,
@@ -47,13 +45,21 @@ enum UserCallbackTransitionResult<'a> {
processing_redraws: bool,
},
ReentrancyPrevented {
queued_events: &'a mut Vec<EventWrapper>,
queued_events: &'a mut Vec<Event<Never>>,
},
}
impl Event<'static, Never> {
impl Event<Never> {
fn is_redraw(&self) -> bool {
matches!(self, Event::RedrawRequested(_))
if let Event::WindowEvent {
window_id: _,
event: WindowEvent::RedrawRequested,
} = self
{
true
} else {
false
}
}
}
@@ -63,12 +69,12 @@ impl Event<'static, Never> {
enum AppStateImpl {
NotLaunched {
queued_windows: Vec<id>,
queued_events: Vec<EventWrapper>,
queued_events: Vec<Event<Never>>,
queued_gpu_redraws: HashSet<id>,
},
Launching {
queued_windows: Vec<id>,
queued_events: Vec<EventWrapper>,
queued_events: Vec<Event<Never>>,
queued_event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<id>,
},
@@ -79,7 +85,7 @@ enum AppStateImpl {
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<EventWrapper>,
queued_events: Vec<Event<Never>>,
queued_gpu_redraws: HashSet<id>,
},
ProcessingRedraws {
@@ -116,7 +122,7 @@ impl Drop for AppState {
} => {
for &mut window in queued_windows {
unsafe {
let _: () = msg_send![window, release];
let () = msg_send![window, release];
}
}
}
@@ -197,10 +203,10 @@ impl AppState {
}
fn has_launched(&self) -> bool {
!matches!(
self.state(),
AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. }
)
match self.state() {
&AppStateImpl::NotLaunched { .. } | &AppStateImpl::Launching { .. } => false,
_ => true,
}
}
fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
@@ -220,7 +226,7 @@ impl AppState {
});
}
fn did_finish_launching_transition(&mut self) -> (Vec<id>, Vec<EventWrapper>) {
fn did_finish_launching_transition(&mut self) -> (Vec<id>, Vec<Event<Never>>) {
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching {
queued_windows,
@@ -243,7 +249,7 @@ impl AppState {
(windows, events)
}
fn wakeup_transition(&mut self) -> Option<EventWrapper> {
fn wakeup_transition(&mut self) -> Option<Event<Never>> {
// before `AppState::did_finish_launching` is called, pretend there is no running
// event loop.
if !self.has_launched() {
@@ -256,10 +262,7 @@ impl AppState {
AppStateImpl::PollFinished {
waiting_event_handler,
},
) => (
waiting_event_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)),
),
) => (waiting_event_handler, Event::NewEvents(StartCause::Poll)),
(
ControlFlow::Wait,
AppStateImpl::Waiting {
@@ -268,10 +271,10 @@ impl AppState {
},
) => (
waiting_event_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: None,
})),
}),
),
(
ControlFlow::WaitUntil(requested_resume),
@@ -281,19 +284,19 @@ impl AppState {
},
) => {
let event = if Instant::now() >= requested_resume {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached {
Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume,
}))
})
} else {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(requested_resume),
}))
})
};
(waiting_event_handler, event)
}
(ControlFlow::ExitWithCode(_), _) => bug!("unexpected `ControlFlow` `Exit`"),
(ControlFlow::Exit, _) => bug!("unexpected `ControlFlow` `Exit`"),
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
};
@@ -448,7 +451,7 @@ impl AppState {
});
self.waker.start()
}
(_, ControlFlow::ExitWithCode(_)) => {
(_, ControlFlow::Exit) => {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically
warn!("`ControlFlow::Exit` ignored on iOS");
@@ -525,9 +528,7 @@ pub unsafe fn queue_gl_or_metal_redraw(window: id) {
| &mut AppStateImpl::InUserCallback {
ref mut queued_gpu_redraws,
..
} => {
let _ = queued_gpu_redraws.insert(window);
}
} => drop(queued_gpu_redraws.insert(window)),
s @ &mut AppStateImpl::ProcessingRedraws { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
@@ -535,7 +536,6 @@ pub unsafe fn queue_gl_or_metal_redraw(window: id) {
panic!("Attempt to create a `Window` after the app has terminated")
}
}
drop(this);
}
@@ -548,7 +548,7 @@ pub unsafe fn will_launch(queued_event_handler: Box<dyn EventHandler>) {
pub unsafe fn did_finish_launching() {
let mut this = AppState::get_mut();
let windows = match this.state_mut() {
AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows),
AppStateImpl::Launching { queued_windows, .. } => mem::replace(queued_windows, Vec::new()),
s => bug!("unexpected state {:?}", s),
};
@@ -578,23 +578,20 @@ pub unsafe fn did_finish_launching() {
// ```
let screen: id = msg_send![window, screen];
let _: id = msg_send![screen, retain];
let _: () = msg_send![window, setScreen:0 as id];
let _: () = msg_send![window, setScreen: screen];
let _: () = msg_send![screen, release];
let () = msg_send![window, setScreen:0 as id];
let () = msg_send![window, setScreen: screen];
let () = msg_send![screen, release];
let controller: id = msg_send![window, rootViewController];
let _: () = msg_send![window, setRootViewController:ptr::null::<()>()];
let _: () = msg_send![window, setRootViewController: controller];
let _: () = msg_send![window, makeKeyAndVisible];
let () = msg_send![window, setRootViewController:ptr::null::<()>()];
let () = msg_send![window, setRootViewController: controller];
let () = msg_send![window, makeKeyAndVisible];
}
let _: () = msg_send![window, release];
let () = msg_send![window, release];
}
let (windows, events) = AppState::get_mut().did_finish_launching_transition();
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init,
)))
.chain(events);
let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events);
handle_nonuser_events(events);
// the above window dance hack, could possibly trigger new windows to be created.
@@ -603,9 +600,9 @@ pub unsafe fn did_finish_launching() {
let count: NSUInteger = msg_send![window, retainCount];
// make sure the window is still referenced
if count > 1 {
let _: () = msg_send![window, makeKeyAndVisible];
let () = msg_send![window, makeKeyAndVisible];
}
let _: () = msg_send![window, release];
let () = msg_send![window, release];
}
}
@@ -623,12 +620,12 @@ pub unsafe fn handle_wakeup_transition() {
}
// requires main thread
pub unsafe fn handle_nonuser_event(event: EventWrapper) {
pub unsafe fn handle_nonuser_event(event: Event<Never>) {
handle_nonuser_events(std::iter::once(event))
}
// requires main thread
pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = Event<Never>>>(events: I) {
let mut this = AppState::get_mut();
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
@@ -645,23 +642,16 @@ pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events
let mut control_flow = this.control_flow;
drop(this);
for wrapper in events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
log::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
}
for event in events {
if !processing_redraws && event.is_redraw() {
log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
log::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
}
loop {
@@ -670,7 +660,7 @@ pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
queued_gpu_redraws: _,
} => mem::take(queued_events),
} => mem::replace(queued_events, Vec::new()),
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() {
@@ -702,23 +692,16 @@ pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
log::warn!(
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
}
for event in queued_events {
if !processing_redraws && event.is_redraw() {
log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
log::warn!(
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
}
}
}
@@ -751,7 +734,7 @@ unsafe fn handle_user_events() {
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
queued_gpu_redraws: _,
} => mem::take(queued_events),
} => mem::replace(queued_events, Vec::new()),
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() {
@@ -772,15 +755,8 @@ unsafe fn handle_user_events() {
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
}
}
for event in queued_events {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
event_handler.handle_user_events(&mut control_flow);
}
@@ -793,25 +769,23 @@ pub unsafe fn handle_main_events_cleared() {
return;
}
match this.state_mut() {
AppStateImpl::ProcessingEvents { .. } => {}
&mut AppStateImpl::ProcessingEvents { .. } => {}
_ => bug!("`ProcessingRedraws` happened unexpectedly"),
};
drop(this);
// User events are always sent out at the end of the "MainEventLoop"
handle_user_events();
handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));
handle_nonuser_event(Event::EventsCleared);
let mut this = AppState::get_mut();
let mut redraw_events: Vec<EventWrapper> = this
let redraw_events = this
.main_events_cleared_transition()
.into_iter()
.map(|window| {
EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))
})
.collect();
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
.map(|window| Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::RedrawRequested,
});
drop(this);
handle_nonuser_events(redraw_events);
@@ -832,66 +806,6 @@ pub unsafe fn terminated() {
event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow)
}
fn handle_event_proxy(
event_handler: &mut Box<dyn EventHandler>,
control_flow: ControlFlow,
proxy: EventProxy,
) {
match proxy {
EventProxy::DpiChangedProxy {
suggested_size,
scale_factor,
window_id,
} => handle_hidpi_proxy(
event_handler,
control_flow,
suggested_size,
scale_factor,
window_id,
),
}
}
fn handle_hidpi_proxy(
event_handler: &mut Box<dyn EventHandler>,
mut control_flow: ControlFlow,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
window_id: id,
) {
let mut size = suggested_size.to_physical(scale_factor);
let new_inner_size = &mut size;
let event = Event::WindowEvent {
window_id: RootWindowId(window_id.into()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
},
};
event_handler.handle_nonuser_event(event, &mut control_flow);
let (view, screen_frame) = get_view_and_screen_frame(window_id);
let physical_size = *new_inner_size;
let logical_size = physical_size.to_logical(scale_factor);
let size = CGSize::new(logical_size);
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
unsafe {
let _: () = msg_send![view, setFrame: new_frame];
}
}
fn get_view_and_screen_frame(window_id: id) -> (id, CGRect) {
unsafe {
let view_controller: id = msg_send![window_id, rootViewController];
let view: id = msg_send![view_controller, view];
let bounds: CGRect = msg_send![window_id, bounds];
let screen: id = msg_send![window_id, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![window_id, convertRect:bounds toCoordinateSpace:screen_space];
(view, screen_frame)
}
}
struct EventLoopWaker {
timer: CFRunLoopTimerRef,
}
@@ -990,20 +904,20 @@ macro_rules! os_capabilities {
}
os_capabilities! {
/// <https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc>
/// https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
#[allow(unused)] // error message unused
safe_area_err_msg: "-[UIView safeAreaInsets]",
safe_area: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc>
/// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc
home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
home_indicator_hidden: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc>
/// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc
defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
defer_system_gestures: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc>
/// https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc
maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
maximum_frames_per_second: 10-3,
/// <https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc>
/// https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc
#[allow(unused)] // error message unused
force_touch_err_msg: "-[UITouch force]",
force_touch: 9-0,
@@ -1016,27 +930,29 @@ impl NSOperatingSystemVersion {
}
pub fn os_capabilities() -> OSCapabilities {
static OS_CAPABILITIES: Lazy<OSCapabilities> = Lazy::new(|| {
let version: NSOperatingSystemVersion = unsafe {
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
let atleast_ios_8: BOOL = msg_send![
process_info,
respondsToSelector: sel!(operatingSystemVersion)
];
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
// Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
// debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
// has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(
atleast_ios_8 == YES,
"`winit` requires iOS version 8 or greater"
);
msg_send![process_info, operatingSystemVersion]
lazy_static! {
static ref OS_CAPABILITIES: OSCapabilities = {
let version: NSOperatingSystemVersion = unsafe {
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
let atleast_ios_8: BOOL = msg_send![
process_info,
respondsToSelector: sel!(operatingSystemVersion)
];
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
// Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
// debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
// has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(
atleast_ios_8 == YES,
"`winit` requires iOS version 8 or greater"
);
msg_send![process_info, operatingSystemVersion]
};
version.into()
};
version.into()
});
}
OS_CAPABILITIES.clone()
}

View File

@@ -7,15 +7,11 @@ use std::{
sync::mpsc::{self, Receiver, Sender},
};
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
use crate::{
dpi::LogicalSize,
event::Event,
event_loop::{
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
},
monitor::MonitorHandle as RootMonitorHandle,
platform::ios::Idiom,
};
@@ -27,59 +23,22 @@ use crate::platform_impl::platform::{
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSStringRust, UIApplicationMain,
UIUserInterfaceIdiom,
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSString, UIApplicationMain, UIUserInterfaceIdiom,
},
monitor, view, MonitorHandle,
};
#[derive(Debug)]
pub enum EventWrapper {
StaticEvent(Event<'static, Never>),
EventProxy(EventProxy),
}
#[derive(Debug, PartialEq)]
pub enum EventProxy {
DpiChangedProxy {
window_id: id,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
}
pub struct EventLoopWindowTarget<T: 'static> {
receiver: Receiver<T>,
sender_to_clone: Sender<T>,
}
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
// guaranteed to be on main thread
unsafe { monitor::uiscreens() }
}
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
// guaranteed to be on main thread
let monitor = unsafe { monitor::main_uiscreen() };
Some(RootMonitorHandle { inner: monitor })
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
}
}
pub struct EventLoop<T: 'static> {
window_target: RootEventLoopWindowTarget<T>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
pub fn new() -> EventLoop<T> {
static mut SINGLETON_INIT: bool = false;
unsafe {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
@@ -110,7 +69,7 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(self, event_handler: F) -> !
where
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
unsafe {
let application: *mut c_void = msg_send![class!(UIApplication), sharedApplication];
@@ -130,7 +89,7 @@ impl<T: 'static> EventLoop<T> {
0,
ptr::null(),
nil,
NSStringRust::alloc(nil).init_str("AppDelegate"),
NSString::alloc(nil).init_str("AppDelegate"),
);
unreachable!()
}
@@ -140,6 +99,16 @@ impl<T: 'static> EventLoop<T> {
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
// guaranteed to be on main thread
unsafe { monitor::uiscreens() }
}
pub fn primary_monitor(&self) -> MonitorHandle {
// guaranteed to be on main thread
unsafe { monitor::main_uiscreen() }
}
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
&self.window_target
}
@@ -196,10 +165,8 @@ impl<T> EventLoopProxy<T> {
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender
.send(event)
.map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.sender.send(event).map_err(|_| EventLoopClosed)?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
@@ -230,10 +197,10 @@ fn setup_control_flow_observers() {
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
// priority to be 0, in order to send EventsCleared before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
// conservative, the main symptom would be non-redraw events coming in after `EventsCleared`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
@@ -308,7 +275,7 @@ fn setup_control_flow_observers() {
pub enum Never {}
pub trait EventHandler: Debug {
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow);
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
}
@@ -327,10 +294,10 @@ impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
impl<F, T> EventHandler for EventLoopHandler<F, T>
where
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
T: 'static,
{
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
(self.f)(
event.map_nonuser_event().unwrap(),
&self.event_loop,

View File

@@ -4,10 +4,7 @@ use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
use objc::{runtime::Object, Encode, Encoding};
use crate::{
dpi::LogicalSize,
platform::ios::{Idiom, ScreenEdge, ValidOrientations},
};
use crate::platform::ios::{Idiom, ScreenEdge, ValidOrientations};
pub type id = *mut Object;
pub const nil: id = 0 as id;
@@ -29,41 +26,26 @@ pub struct NSOperatingSystemVersion {
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone)]
pub struct CGPoint {
pub x: CGFloat,
pub y: CGFloat,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone)]
pub struct CGSize {
pub width: CGFloat,
pub height: CGFloat,
}
impl CGSize {
pub fn new(size: LogicalSize<f64>) -> CGSize {
CGSize {
width: size.width as _,
height: size.height as _,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
}
impl CGRect {
pub fn new(origin: CGPoint, size: CGSize) -> CGRect {
CGRect { origin, size }
}
}
unsafe impl Encode for CGRect {
fn encode() -> Encoding {
unsafe {
@@ -88,7 +70,7 @@ pub enum UITouchPhase {
Cancelled,
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIForceTouchCapability {
@@ -97,7 +79,7 @@ pub enum UIForceTouchCapability {
Available,
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchType {
@@ -144,9 +126,10 @@ impl From<Idiom> for UIUserInterfaceIdiom {
}
}
}
impl From<UIUserInterfaceIdiom> for Idiom {
fn from(ui_idiom: UIUserInterfaceIdiom) -> Idiom {
match ui_idiom {
impl Into<Idiom> for UIUserInterfaceIdiom {
fn into(self) -> Idiom {
match self {
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
UIUserInterfaceIdiom::Phone => Idiom::Phone,
UIUserInterfaceIdiom::Pad => Idiom::Pad,
@@ -229,9 +212,9 @@ impl From<ScreenEdge> for UIRectEdge {
}
}
impl From<UIRectEdge> for ScreenEdge {
fn from(ui_rect_edge: UIRectEdge) -> ScreenEdge {
let bits: u8 = ui_rect_edge.0.try_into().expect("invalid `UIRectEdge`");
impl Into<ScreenEdge> for UIRectEdge {
fn into(self) -> ScreenEdge {
let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`");
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
}
}
@@ -358,10 +341,7 @@ pub struct CFRunLoopSourceContext {
pub perform: Option<extern "C" fn(*mut c_void)>,
}
// This is named NSStringRust rather than NSString because the "Debug View Heirarchy" feature of
// Xcode requires a non-ambiguous reference to NSString for unclear reasons. This makes Xcode happy
// so please test if you change the name back to NSString.
pub trait NSStringRust: Sized {
pub trait NSString: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class!(NSString), alloc]
}
@@ -372,7 +352,7 @@ pub trait NSStringRust: Sized {
unsafe fn UTF8String(self) -> *const c_char;
}
impl NSStringRust for id {
impl NSString for id {
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
msg_send![self, initWithUTF8String: c_string as id]
}

View File

@@ -56,15 +56,13 @@
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
#![cfg(target_os = "ios")]
#![allow(clippy::let_unit_value)]
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
// window size/position.
macro_rules! assert_main_thread {
($($t:tt)*) => {
let is_main_thread: ::objc::runtime::BOOL = msg_send!(class!(NSThread), isMainThread);
if is_main_thread == ::objc::runtime::NO {
if !msg_send![class!(NSThread), isMainThread] {
panic!($($t)*);
}
};
@@ -79,23 +77,19 @@ mod window;
use std::fmt;
pub(crate) use self::{
event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
},
pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
monitor::{MonitorHandle, VideoMode},
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId {
uiscreen: ffi::id,
}
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub unsafe fn dummy() -> Self {
DeviceId {
uiscreen: std::ptr::null_mut(),
}
@@ -109,7 +103,9 @@ unsafe impl Sync for DeviceId {}
pub enum OsError {}
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "os error")
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
_ => unreachable!(),
}
}
}

View File

@@ -17,62 +17,63 @@ use crate::{
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) screen_mode: NativeDisplayMode,
pub(crate) refresh_rate: u16,
pub(crate) screen_mode: id,
pub(crate) monitor: MonitorHandle,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NativeDisplayMode(pub id);
unsafe impl Send for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.0, release];
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
let _: id = msg_send![self.0, retain];
}
NativeDisplayMode(self.0)
}
}
impl Clone for VideoMode {
fn clone(&self) -> VideoMode {
VideoMode {
size: self.size,
bit_depth: self.bit_depth,
refresh_rate_millihertz: self.refresh_rate_millihertz,
screen_mode: self.screen_mode.clone(),
refresh_rate: self.refresh_rate,
screen_mode: unsafe { msg_send![self.screen_mode, retain] },
monitor: self.monitor.clone(),
}
}
}
impl Drop for VideoMode {
fn drop(&mut self) {
unsafe {
assert_main_thread!("`VideoMode` can only be dropped on the main thread on iOS");
let () = msg_send![self.screen_mode, release];
}
}
}
impl VideoMode {
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
let refresh_rate_millihertz = refresh_rate_millihertz(uiscreen);
let os_capabilities = app_state::os_capabilities();
let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second {
msg_send![uiscreen, maximumFramesPerSecond]
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
//
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
// supported, they are all guaranteed to have 60hz refresh rates. This does not
// correctly handle external displays. ProMotion displays support 120fps, but they were
// introduced at the same time as the `maximumFramesPerSecond` API.
//
// FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`.
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
60
};
let size: CGSize = msg_send![screen_mode, size];
let screen_mode: id = msg_send![screen_mode, retain];
let screen_mode = NativeDisplayMode(screen_mode);
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate_millihertz,
screen_mode,
refresh_rate: refresh_rate as u16,
screen_mode: msg_send![screen_mode, retain],
monitor: MonitorHandle::retained_new(uiscreen),
}
}
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
@@ -80,8 +81,8 @@ impl VideoMode {
self.bit_depth
}
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> RootMonitorHandle {
@@ -99,7 +100,7 @@ pub struct Inner {
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.uiscreen, release];
let () = msg_send![self.uiscreen, release];
}
}
}
@@ -152,21 +153,19 @@ impl Drop for MonitorHandle {
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Do this using the proper fmt API
#[derive(Debug)]
#[allow(dead_code)]
struct MonitorHandle {
name: Option<String>,
size: PhysicalSize<u32>,
position: PhysicalPosition<i32>,
scale_factor: f64,
size: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorHandle {
name: self.name(),
size: self.size(),
position: self.position(),
scale_factor: self.scale_factor(),
hidpi_factor: self.hidpi_factor(),
};
monitor_id_proxy.fmt(f)
@@ -177,7 +176,7 @@ impl MonitorHandle {
pub fn retained_new(uiscreen: id) -> MonitorHandle {
unsafe {
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS");
let _: () = msg_send![uiscreen, retain];
let () = msg_send![uiscreen, retain];
}
MonitorHandle {
inner: Inner { uiscreen },
@@ -202,31 +201,27 @@ impl Inner {
}
}
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
unsafe {
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
(bounds.size.width as f64, bounds.size.height as f64).into()
}
}
pub fn position(&self) -> PhysicalPosition<i32> {
pub fn position(&self) -> PhysicalPosition {
unsafe {
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
}
pub fn scale_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
unsafe {
let scale: CGFloat = msg_send![self.ui_screen(), nativeScale];
scale as f64
}
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(refresh_rate_millihertz(self.uiscreen))
}
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let mut modes = BTreeSet::new();
unsafe {
@@ -245,30 +240,6 @@ impl Inner {
}
}
fn refresh_rate_millihertz(uiscreen: id) -> u32 {
let refresh_rate_millihertz: NSInteger = unsafe {
let os_capabilities = app_state::os_capabilities();
if os_capabilities.maximum_frames_per_second {
msg_send![uiscreen, maximumFramesPerSecond]
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
//
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
// supported, they are all guaranteed to have 60hz refresh rates. This does not
// correctly handle external displays. ProMotion displays support 120fps, but they were
// introduced at the same time as the `maximumFramesPerSecond` API.
//
// FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`.
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
60
}
};
refresh_rate_millihertz as u32 * 1000
}
// MonitorHandleExtIOS
impl Inner {
pub fn ui_screen(&self) -> id {

View File

@@ -6,12 +6,11 @@ use objc::{
};
use crate::{
dpi::PhysicalPosition,
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::MonitorHandleExtIOS,
platform_impl::platform::{
app_state::{self, OSCapabilities},
event_loop::{self, EventProxy, EventWrapper},
event_loop,
ffi::{
id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask,
UIRectEdge, UITouchPhase, UITouchType,
@@ -103,63 +102,48 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
unsafe {
let window: id = msg_send![object, window];
assert!(!window.is_null());
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
RootWindowId(window.into()),
)))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::RedrawEventsCleared,
))),
);
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::RedrawRequested,
});
let superclass: &'static Class = msg_send![object, superclass];
let _: () = msg_send![super(object, superclass), drawRect: rect];
let () = msg_send![super(object, superclass), drawRect: rect];
}
}
extern "C" fn layout_subviews(object: &Object, _: Sel) {
unsafe {
let superclass: &'static Class = msg_send![object, superclass];
let _: () = msg_send![super(object, superclass), layoutSubviews];
let () = msg_send![super(object, superclass), layoutSubviews];
let window: id = msg_send![object, window];
assert!(!window.is_null());
let window_bounds: CGRect = msg_send![window, bounds];
let bounds: CGRect = msg_send![window, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![object, convertRect:window_bounds toCoordinateSpace:screen_space];
let scale_factor: CGFloat = msg_send![screen, scale];
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
}
.to_physical(scale_factor as f64);
// If the app is started in landscape, the view frame and window bounds can be mismatched.
// The view frame will be in portrait and the window bounds in landscape. So apply the
// window bounds to the view frame to make it consistent.
let view_frame: CGRect = msg_send![object, frame];
if view_frame != window_bounds {
let _: () = msg_send![object, setFrame: window_bounds];
}
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
}));
});
}
}
extern "C" fn set_content_scale_factor(
object: &mut Object,
_: Sel,
untrusted_scale_factor: CGFloat,
untrusted_hidpi_factor: CGFloat,
) {
unsafe {
let superclass: &'static Class = msg_send![object, superclass];
let _: () = msg_send![
let () = msg_send![
super(object, superclass),
setContentScaleFactor: untrusted_scale_factor
setContentScaleFactor: untrusted_hidpi_factor
];
let window: id = msg_send![object, window];
@@ -172,15 +156,14 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
// `setContentScaleFactor` may be called with a value of 0, which means "reset the
// content scale factor to a device-specific default value", so we can't use the
// parameter here. We can query the actual factor using the getter
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
let hidpi_factor: CGFloat = msg_send![object, contentScaleFactor];
assert!(
!scale_factor.is_nan()
&& scale_factor.is_finite()
&& scale_factor.is_sign_positive()
&& scale_factor > 0.0,
"invalid scale_factor set on UIView",
!hidpi_factor.is_nan()
&& hidpi_factor.is_finite()
&& hidpi_factor.is_sign_positive()
&& hidpi_factor > 0.0,
"invalid hidpi_factor set on UIView",
);
let scale_factor = scale_factor as f64;
let bounds: CGRect = msg_send![object, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
@@ -191,17 +174,14 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window_id: window,
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
})
.chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
})),
);
}
}
@@ -219,7 +199,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
if touch == nil {
break;
}
let logical_location: CGPoint = msg_send![touch, locationInView: nil];
let location: CGPoint = msg_send![touch, locationInView: nil];
let touch_type: UITouchType = msg_send![touch, type];
let force = if os_supports_force {
let trait_collection: id = msg_send![object, traitCollection];
@@ -258,23 +238,16 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
let physical_location = {
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
PhysicalPosition::from_logical::<(f64, f64), f64>(
(logical_location.x as _, logical_location.y as _),
scale_factor,
)
};
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
touch_events.push(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }),
id: touch_id,
location: physical_location,
location: (location.x as f64, location.y as f64).into(),
force,
phase,
}),
}));
});
}
app_state::handle_nonuser_events(touch_events);
}
@@ -340,7 +313,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
prefers_status_bar_hidden: BOOL,
setPrefersStatusBarHidden: |object| {
unsafe {
let _: () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
}
},
prefersStatusBarHidden,
@@ -353,7 +326,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
OSCapabilities::home_indicator_hidden_err_msg;
|object| {
unsafe {
let _: () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
}
},
prefersHomeIndicatorAutoHidden,
@@ -363,7 +336,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
supported_orientations: UIInterfaceOrientationMask,
setSupportedInterfaceOrientations: |object| {
unsafe {
let _: () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
}
},
supportedInterfaceOrientations,
@@ -376,7 +349,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
OSCapabilities::defer_system_gestures_err_msg;
|object| {
unsafe {
let _: () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
},
preferredScreenEdgesDeferringSystemGestures,
@@ -394,21 +367,21 @@ unsafe fn get_window_class() -> &'static Class {
extern "C" fn become_key_window(object: &Object, _: Sel) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(true),
}));
let _: () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
});
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
}
}
extern "C" fn resign_key_window(object: &Object, _: Sel) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(false),
}));
let _: () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
});
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
}
}
@@ -429,7 +402,7 @@ unsafe fn get_window_class() -> &'static Class {
}
// requires main thread
pub(crate) unsafe fn create_view(
pub unsafe fn create_view(
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
@@ -440,16 +413,16 @@ pub(crate) unsafe fn create_view(
assert!(!view.is_null(), "Failed to create `UIView` instance");
let view: id = msg_send![view, initWithFrame: frame];
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
let _: () = msg_send![view, setMultipleTouchEnabled: YES];
if let Some(scale_factor) = platform_attributes.scale_factor {
let _: () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat];
let () = msg_send![view, setMultipleTouchEnabled: YES];
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
let () = msg_send![view, setContentScaleFactor: hidpi_factor as CGFloat];
}
view
}
// requires main thread
pub(crate) unsafe fn create_view_controller(
pub unsafe fn create_view_controller(
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: id,
@@ -484,28 +457,28 @@ pub(crate) unsafe fn create_view_controller(
let edges: UIRectEdge = platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into();
let _: () = msg_send![
let () = msg_send![
view_controller,
setPrefersStatusBarHidden: status_bar_hidden
];
let _: () = msg_send![
let () = msg_send![
view_controller,
setSupportedInterfaceOrientations: supported_orientations
];
let _: () = msg_send![
let () = msg_send![
view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
let _: () = msg_send![
let () = msg_send![
view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
let _: () = msg_send![view_controller, setView: view];
let () = msg_send![view_controller, setView: view];
view_controller
}
// requires main thread
pub(crate) unsafe fn create_window(
pub unsafe fn create_window(
window_attributes: &WindowAttributes,
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
@@ -520,23 +493,15 @@ pub(crate) unsafe fn create_window(
!window.is_null(),
"Failed to initialize `UIWindow` instance"
);
let _: () = msg_send![window, setRootViewController: view_controller];
let () = msg_send![window, setRootViewController: view_controller];
match window_attributes.fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let uiscreen = video_mode.monitor().ui_screen() as id;
let _: () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
}
Some(Fullscreen::Borderless(ref monitor)) => {
let uiscreen: id = match &monitor {
Some(monitor) => monitor.ui_screen() as id,
None => {
let uiscreen: id = msg_send![window, screen];
uiscreen
}
};
msg_send![window, setScreen: uiscreen]
msg_send![window, setScreen:monitor.ui_screen()]
}
None => (),
}
@@ -553,11 +518,11 @@ pub fn create_delegate_class() {
}
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
unsafe { app_state::handle_nonuser_event(Event::Resumed) }
}
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
unsafe { app_state::handle_nonuser_event(Event::Suspended) }
}
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
@@ -576,10 +541,10 @@ pub fn create_delegate_class() {
}
let is_winit_window: BOOL = msg_send![window, isKindOfClass: class!(WinitUIWindow)];
if is_winit_window == YES {
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
events.push(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Destroyed,
}));
});
}
}
app_state::handle_nonuser_events(events);

View File

@@ -1,31 +1,27 @@
use raw_window_handle::{ios::IOSHandle, RawWindowHandle};
use std::{
collections::VecDeque,
ops::{Deref, DerefMut},
};
use objc::runtime::{Class, Object, BOOL, NO, YES};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
use crate::{
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
dpi::{self, LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon,
monitor::MonitorHandle as RootMonitorHandle,
platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
platform_impl::platform::{
app_state,
event_loop::{self, EventProxy, EventWrapper},
app_state, event_loop,
ffi::{
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
UIRectEdge, UIScreenOverscanCompensation,
},
monitor, view, EventLoopWindowTarget, MonitorHandle,
},
window::{
CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes,
WindowId as RootWindowId,
},
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
};
pub struct Inner {
@@ -38,9 +34,9 @@ pub struct Inner {
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.view, release];
let _: () = msg_send![self.view_controller, release];
let _: () = msg_send![self.window, release];
let () = msg_send![self.view, release];
let () = msg_send![self.view_controller, release];
let () = msg_send![self.window, release];
}
}
}
@@ -53,19 +49,14 @@ impl Inner {
pub fn set_visible(&self, visible: bool) {
match visible {
true => unsafe {
let _: () = msg_send![self.window, setHidden: NO];
let () = msg_send![self.window, setHidden: NO];
},
false => unsafe {
let _: () = msg_send![self.window, setHidden: YES];
let () = msg_send![self.window, setHidden: YES];
},
}
}
pub fn is_visible(&self) -> Option<bool> {
warn!("`Window::is_visible` is ignored on iOS");
None
}
pub fn request_redraw(&self) {
unsafe {
if self.gl_or_metal_backed {
@@ -79,39 +70,33 @@ impl Inner {
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(self.window);
} else {
let _: () = msg_send![self.view, setNeedsDisplay];
let () = msg_send![self.view, setNeedsDisplay];
}
}
}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
unsafe {
let safe_area = self.safe_area_screen_space();
let position = LogicalPosition {
x: safe_area.origin.x as f64,
y: safe_area.origin.y as f64,
};
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
Ok(LogicalPosition {
x: safe_area.origin.x as _,
y: safe_area.origin.y as _,
})
}
}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
unsafe {
let screen_frame = self.screen_frame();
let position = LogicalPosition {
x: screen_frame.origin.x as f64,
y: screen_frame.origin.y as f64,
};
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
Ok(LogicalPosition {
x: screen_frame.origin.x as _,
y: screen_frame.origin.y as _,
})
}
}
pub fn set_outer_position(&self, physical_position: Position) {
pub fn set_outer_position(&self, position: LogicalPosition) {
unsafe {
let scale_factor = self.scale_factor();
let position = physical_position.to_logical::<f64>(scale_factor);
let screen_frame = self.screen_frame();
let new_screen_frame = CGRect {
origin: CGPoint {
@@ -120,44 +105,40 @@ impl Inner {
},
size: screen_frame.size,
};
let bounds = self.rect_from_screen_space(new_screen_frame);
let _: () = msg_send![self.window, setBounds: bounds];
let bounds = self.from_screen_space(new_screen_frame);
let () = msg_send![self.window, setBounds: bounds];
}
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
pub fn inner_size(&self) -> LogicalSize {
unsafe {
let scale_factor = self.scale_factor();
let safe_area = self.safe_area_screen_space();
let size = LogicalSize {
width: safe_area.size.width as f64,
height: safe_area.size.height as f64,
};
size.to_physical(scale_factor)
LogicalSize {
width: safe_area.size.width as _,
height: safe_area.size.height as _,
}
}
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
pub fn outer_size(&self) -> LogicalSize {
unsafe {
let scale_factor = self.scale_factor();
let screen_frame = self.screen_frame();
let size = LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
size.to_physical(scale_factor)
LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
}
}
}
pub fn set_inner_size(&self, _size: Size) {
warn!("not clear what `Window::set_inner_size` means on iOS");
pub fn set_inner_size(&self, _size: LogicalSize) {
unimplemented!("not clear what `Window::set_inner_size` means on iOS");
}
pub fn set_min_inner_size(&self, _dimensions: Option<Size>) {
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
warn!("`Window::set_min_inner_size` is ignored on iOS")
}
pub fn set_max_inner_size(&self, _dimensions: Option<Size>) {
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
warn!("`Window::set_max_inner_size` is ignored on iOS")
}
@@ -165,12 +146,7 @@ impl Inner {
warn!("`Window::set_resizable` is ignored on iOS")
}
pub fn is_resizable(&self) -> bool {
warn!("`Window::is_resizable` is ignored on iOS");
false
}
pub fn scale_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
unsafe {
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
hidpi as _
@@ -181,11 +157,11 @@ impl Inner {
debug!("`Window::set_cursor_icon` ignored on iOS")
}
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), ExternalError> {
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
@@ -193,39 +169,19 @@ impl Inner {
debug!("`Window::set_cursor_visible` is ignored on iOS")
}
pub fn drag_window(&self) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_minimized(&self, _minimized: bool) {
warn!("`Window::set_minimized` is ignored on iOS")
}
pub fn set_maximized(&self, _maximized: bool) {
warn!("`Window::set_maximized` is ignored on iOS")
}
pub fn is_maximized(&self) -> bool {
warn!("`Window::is_maximized` is ignored on iOS");
false
}
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
unsafe {
let uiscreen = match monitor {
Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
let _: () =
msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
uiscreen
}
Some(Fullscreen::Borderless(monitor)) => monitor
.unwrap_or_else(|| self.current_monitor_inner())
.ui_screen() as id,
Some(Fullscreen::Borderless(monitor)) => monitor.ui_screen() as id,
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
@@ -235,16 +191,16 @@ impl Inner {
// this is pretty slow on iOS, so avoid doing it if we can
let current: id = msg_send![self.window, screen];
if uiscreen != current {
let _: () = msg_send![self.window, setScreen: uiscreen];
let () = msg_send![self.window, setScreen: uiscreen];
}
let bounds: CGRect = msg_send![uiscreen, bounds];
let _: () = msg_send![self.window, setFrame: bounds];
let () = msg_send![self.window, setFrame: bounds];
// For external displays, we must disable overscan compensation or
// the displayed image will have giant black bars surrounding it on
// each side
let _: () = msg_send![
let () = msg_send![
uiscreen,
setOverscanCompensation: UIScreenOverscanCompensation::None
];
@@ -253,7 +209,7 @@ impl Inner {
pub fn fullscreen(&self) -> Option<Fullscreen> {
unsafe {
let monitor = self.current_monitor_inner();
let monitor = self.current_monitor();
let uiscreen = monitor.inner.ui_screen();
let screen_space_bounds = self.screen_frame();
let screen_bounds: CGRect = msg_send![uiscreen, bounds];
@@ -264,7 +220,7 @@ impl Inner {
&& screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height
{
Some(Fullscreen::Borderless(Some(monitor)))
Some(Fullscreen::Borderless(monitor))
} else {
None
}
@@ -275,11 +231,6 @@ impl Inner {
warn!("`Window::set_decorations` is ignored on iOS")
}
pub fn is_decorated(&self) -> bool {
warn!("`Window::is_decorated` is ignored on iOS");
true
}
pub fn set_always_on_top(&self, _always_on_top: bool) {
warn!("`Window::set_always_on_top` is ignored on iOS")
}
@@ -288,24 +239,11 @@ impl Inner {
warn!("`Window::set_window_icon` is ignored on iOS")
}
pub fn set_ime_position(&self, _position: Position) {
pub fn set_ime_position(&self, _position: LogicalPosition) {
warn!("`Window::set_ime_position` is ignored on iOS")
}
pub fn set_ime_allowed(&self, _allowed: bool) {
warn!("`Window::set_ime_allowed` is ignored on iOS")
}
pub fn focus_window(&self) {
warn!("`Window::set_focus` is ignored on iOS")
}
pub fn request_user_attention(&self, _request_type: Option<UserAttentionType>) {
warn!("`Window::request_user_attention` is ignored on iOS")
}
// Allow directly accessing the current monitor internally without unwrapping.
fn current_monitor_inner(&self) -> RootMonitorHandle {
pub fn current_monitor(&self) -> RootMonitorHandle {
unsafe {
let uiscreen: id = msg_send![self.window, screen];
RootMonitorHandle {
@@ -314,17 +252,12 @@ impl Inner {
}
}
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
Some(self.current_monitor_inner())
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
unsafe { monitor::uiscreens() }
}
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
let monitor = unsafe { monitor::main_uiscreen() };
Some(RootMonitorHandle { inner: monitor })
pub fn primary_monitor(&self) -> MonitorHandle {
unsafe { monitor::main_uiscreen() }
}
pub fn id(&self) -> WindowId {
@@ -332,15 +265,13 @@ impl Inner {
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = UiKitWindowHandle::empty();
window_handle.ui_window = self.window as _;
window_handle.ui_view = self.view as _;
window_handle.ui_view_controller = self.view_controller as _;
RawWindowHandle::UiKit(window_handle)
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
let handle = IOSHandle {
ui_window: self.window as _,
ui_view: self.view as _,
ui_view_controller: self.view_controller as _,
..IOSHandle::empty()
};
RawWindowHandle::IOS(handle)
}
}
@@ -380,15 +311,15 @@ impl DerefMut for Window {
}
impl Window {
pub(crate) fn new<T>(
pub fn new<T>(
_event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> {
if window_attributes.min_inner_size.is_some() {
if let Some(_) = window_attributes.min_inner_size {
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
}
if window_attributes.max_inner_size.is_some() {
if let Some(_) = window_attributes.max_inner_size {
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
}
if window_attributes.always_on_top {
@@ -401,30 +332,24 @@ impl Window {
Some(Fullscreen::Exclusive(ref video_mode)) => {
video_mode.video_mode.monitor.ui_screen() as id
}
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.inner.ui_screen(),
Some(Fullscreen::Borderless(None)) | None => {
monitor::main_uiscreen().ui_screen() as id
}
Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id,
None => monitor::main_uiscreen().ui_screen(),
};
let screen_bounds: CGRect = msg_send![screen, bounds];
let frame = match window_attributes.inner_size {
Some(dim) => {
let scale_factor = msg_send![screen, scale];
let size = dim.to_logical::<f64>(scale_factor);
CGRect {
origin: screen_bounds.origin,
size: CGSize {
width: size.width as _,
height: size.height as _,
},
}
}
Some(dim) => CGRect {
origin: screen_bounds.origin,
size: CGSize {
width: dim.width as _,
height: dim.height as _,
},
},
None => screen_bounds,
};
let view = view::create_view(&window_attributes, &platform_attributes, frame);
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
let gl_or_metal_backed = {
let view_class: id = msg_send![view, class];
@@ -454,11 +379,10 @@ impl Window {
};
app_state::set_key_window(window);
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// Like the Windows and macOS backends, we send a `HiDpiFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let scale_factor: CGFloat = msg_send![view, contentScaleFactor];
let scale_factor = scale_factor as f64;
if scale_factor != 1.0 {
let hidpi_factor: CGFloat = msg_send![view, contentScaleFactor];
if hidpi_factor != 1.0 {
let bounds: CGRect = msg_send![view, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
@@ -469,17 +393,14 @@ impl Window {
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window_id: window,
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
})
.chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
})),
);
}
@@ -500,14 +421,14 @@ impl Inner {
self.view
}
pub fn set_scale_factor(&self, scale_factor: f64) {
pub fn set_hidpi_factor(&self, hidpi_factor: f64) {
unsafe {
assert!(
dpi::validate_scale_factor(scale_factor),
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
dpi::validate_hidpi_factor(hidpi_factor),
"`WindowExtIOS::set_hidpi_factor` received an invalid hidpi factor"
);
let scale_factor = scale_factor as CGFloat;
let _: () = msg_send![self.view, setContentScaleFactor: scale_factor];
let hidpi_factor = hidpi_factor as CGFloat;
let () = msg_send![self.view, setContentScaleFactor: hidpi_factor];
}
}
@@ -528,7 +449,7 @@ impl Inner {
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
unsafe {
let prefers_home_indicator_hidden = if hidden { YES } else { NO };
let _: () = msg_send![
let () = msg_send![
self.view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
@@ -538,7 +459,7 @@ impl Inner {
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into();
unsafe {
let _: () = msg_send![
let () = msg_send![
self.view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
@@ -548,7 +469,7 @@ impl Inner {
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
unsafe {
let status_bar_hidden = if hidden { YES } else { NO };
let _: () = msg_send![
let () = msg_send![
self.view_controller,
setPrefersStatusBarHidden: status_bar_hidden
];
@@ -559,11 +480,11 @@ impl Inner {
impl Inner {
// requires main thread
unsafe fn screen_frame(&self) -> CGRect {
self.rect_to_screen_space(msg_send![self.window, bounds])
self.to_screen_space(msg_send![self.window, bounds])
}
// requires main thread
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect {
let screen: id = msg_send![self.window, screen];
if !screen.is_null() {
let screen_space: id = msg_send![screen, coordinateSpace];
@@ -574,7 +495,7 @@ impl Inner {
}
// requires main thread
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect {
let screen: id = msg_send![self.window, screen];
if !screen.is_null() {
let screen_space: id = msg_send![screen, coordinateSpace];
@@ -599,9 +520,9 @@ impl Inner {
height: bounds.size.height - safe_area.top - safe_area.bottom,
},
};
self.rect_to_screen_space(safe_bounds)
self.to_screen_space(safe_bounds)
} else {
let screen_frame = self.rect_to_screen_space(bounds);
let screen_frame = self.to_screen_space(bounds);
let status_bar_frame: CGRect = {
let app: id = msg_send![class!(UIApplication), sharedApplication];
assert!(
@@ -638,27 +559,13 @@ pub struct WindowId {
}
impl WindowId {
pub const unsafe fn dummy() -> Self {
pub unsafe fn dummy() -> Self {
WindowId {
window: std::ptr::null_mut(),
}
}
}
impl From<WindowId> for u64 {
fn from(window_id: WindowId) -> Self {
window_id.window as u64
}
}
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self {
window: raw_id as _,
}
}
}
unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
@@ -687,7 +594,7 @@ impl From<id> for WindowId {
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
pub scale_factor: Option<f64>,
pub hidpi_factor: Option<f64>,
pub valid_orientations: ValidOrientations,
pub prefers_home_indicator_hidden: bool,
pub prefers_status_bar_hidden: bool,
@@ -698,7 +605,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> PlatformSpecificWindowBuilderAttributes {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
scale_factor: None,
hidpi_factor: None,
valid_orientations: Default::default(),
prefers_home_indicator_hidden: false,
prefers_status_bar_hidden: false,

File diff suppressed because it is too large Load Diff

View File

@@ -1,166 +0,0 @@
//! SCTK environment setup.
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6::ZxdgShellV6;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
use sctk::reexports::client::protocol::wl_shell::WlShell;
use sctk::reexports::client::protocol::wl_subcompositor::WlSubcompositor;
use sctk::reexports::client::{Attached, DispatchData};
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::environment::{Environment, SimpleGlobal};
use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener};
use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener};
use sctk::shell::{Shell, ShellHandler, ShellHandling};
use sctk::shm::ShmHandler;
/// Set of extra features that are supported by the compositor.
#[derive(Debug, Clone, Copy)]
pub struct WindowingFeatures {
pointer_constraints: bool,
xdg_activation: bool,
}
impl WindowingFeatures {
/// Create `WindowingFeatures` based on the presented interfaces.
pub fn new(env: &Environment<WinitEnv>) -> Self {
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>().is_some();
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
Self {
pointer_constraints,
xdg_activation,
}
}
pub fn pointer_constraints(&self) -> bool {
self.pointer_constraints
}
pub fn xdg_activation(&self) -> bool {
self.xdg_activation
}
}
sctk::environment!(WinitEnv,
singles = [
WlShm => shm,
WlCompositor => compositor,
WlSubcompositor => subcompositor,
WlShell => shell,
XdgWmBase => shell,
ZxdgShellV6 => shell,
ZxdgDecorationManagerV1 => decoration_manager,
ZwpRelativePointerManagerV1 => relative_pointer_manager,
ZwpPointerConstraintsV1 => pointer_constraints,
ZwpTextInputManagerV3 => text_input_manager,
XdgActivationV1 => xdg_activation,
],
multis = [
WlSeat => seats,
WlOutput => outputs,
]
);
/// The environment that we utilize.
pub struct WinitEnv {
seats: SeatHandler,
outputs: OutputHandler,
shm: ShmHandler,
compositor: SimpleGlobal<WlCompositor>,
subcompositor: SimpleGlobal<WlSubcompositor>,
shell: ShellHandler,
relative_pointer_manager: SimpleGlobal<ZwpRelativePointerManagerV1>,
pointer_constraints: SimpleGlobal<ZwpPointerConstraintsV1>,
text_input_manager: SimpleGlobal<ZwpTextInputManagerV3>,
decoration_manager: SimpleGlobal<ZxdgDecorationManagerV1>,
xdg_activation: SimpleGlobal<XdgActivationV1>,
}
impl WinitEnv {
pub fn new() -> Self {
// Output tracking for available_monitors, etc.
let outputs = OutputHandler::new();
// Keyboard/Pointer/Touch input.
let seats = SeatHandler::new();
// Essential globals.
let shm = ShmHandler::new();
let compositor = SimpleGlobal::new();
let subcompositor = SimpleGlobal::new();
// Gracefully handle shell picking, since SCTK automatically supports multiple
// backends.
let shell = ShellHandler::new();
// Server side decorations.
let decoration_manager = SimpleGlobal::new();
// Device events for pointer.
let relative_pointer_manager = SimpleGlobal::new();
// Pointer grab functionality.
let pointer_constraints = SimpleGlobal::new();
// IME handling.
let text_input_manager = SimpleGlobal::new();
// Surface activation.
let xdg_activation = SimpleGlobal::new();
Self {
seats,
outputs,
shm,
compositor,
subcompositor,
shell,
decoration_manager,
relative_pointer_manager,
pointer_constraints,
text_input_manager,
xdg_activation,
}
}
}
impl ShellHandling for WinitEnv {
fn get_shell(&self) -> Option<Shell> {
self.shell.get_shell()
}
}
impl SeatHandling for WinitEnv {
fn listen<F: FnMut(Attached<WlSeat>, &SeatData, DispatchData<'_>) + 'static>(
&mut self,
f: F,
) -> SeatListener {
self.seats.listen(f)
}
}
impl OutputHandling for WinitEnv {
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData<'_>) + 'static>(
&mut self,
f: F,
) -> OutputStatusListener {
self.outputs.listen(f)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,551 +0,0 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::io::Result as IOResult;
use std::process;
use std::rc::Rc;
use std::time::{Duration, Instant};
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::client::Display;
use sctk::reexports::calloop;
use sctk::environment::Environment;
use sctk::seat::pointer::{ThemeManager, ThemeSpec};
use sctk::WaylandSource;
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
use crate::platform_impl::platform::sticky_exit_callback;
use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
use super::env::{WindowingFeatures, WinitEnv};
use super::output::OutputManager;
use super::seat::SeatManager;
use super::window::shim::{self, WindowUpdate};
use super::{DeviceId, WindowId};
mod proxy;
mod sink;
mod state;
pub use proxy::EventLoopProxy;
pub use sink::EventSink;
pub use state::WinitState;
type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>;
pub struct EventLoopWindowTarget<T> {
/// Wayland display.
pub display: Display,
/// Environment to handle object creation, etc.
pub env: Environment<WinitEnv>,
/// Event loop handle.
pub event_loop_handle: calloop::LoopHandle<'static, WinitState>,
/// Output manager.
pub output_manager: OutputManager,
/// State that we share across callbacks.
pub state: RefCell<WinitState>,
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// A proxy to wake up event loop.
pub event_loop_awakener: calloop::ping::Ping,
/// The available windowing features.
pub windowing_features: WindowingFeatures,
/// Theme manager to manage cursors.
///
/// It's being shared between all windows to avoid loading
/// multiple similar themes.
pub theme_manager: ThemeManager,
_marker: std::marker::PhantomData<T>,
}
impl<T> EventLoopWindowTarget<T> {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = WaylandDisplayHandle::empty();
display_handle.display = self.display.get_display_ptr() as *mut _;
RawDisplayHandle::Wayland(display_handle)
}
}
pub struct EventLoop<T: 'static> {
/// Event loop.
event_loop: calloop::EventLoop<'static, WinitState>,
/// Wayland display.
display: Display,
/// Pending user events.
pending_user_events: Rc<RefCell<Vec<T>>>,
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// Window target.
window_target: RootEventLoopWindowTarget<T>,
/// Output manager.
_seat_manager: SeatManager,
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> Result<EventLoop<T>, Box<dyn Error>> {
// Connect to wayland server and setup event queue.
let display = Display::connect_to_env()?;
let mut event_queue = display.create_event_queue();
let display_proxy = display.attach(event_queue.token());
// Setup environment.
let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?;
// Create event loop.
let event_loop = calloop::EventLoop::<'static, WinitState>::try_new()?;
// Build windowing features.
let windowing_features = WindowingFeatures::new(&env);
// Create a theme manager.
let compositor = env.require_global::<WlCompositor>();
let shm = env.require_global::<WlShm>();
let theme_manager = ThemeManager::init(ThemeSpec::System, compositor, shm);
// Setup theme seat and output managers.
let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone());
let output_manager = OutputManager::new(&env);
// A source of events that we plug into our event loop.
let wayland_source = WaylandSource::new(event_queue);
let wayland_dispatcher =
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
queue.dispatch_pending(winit_state, |event, object, _| {
panic!(
"[calloop] Encountered an orphan event: {}@{} : {}",
event.interface,
object.as_ref().id(),
event.name
);
})
});
let _wayland_source_dispatcher = event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone())?;
// A source of user events.
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel();
// User events channel.
event_loop
.handle()
.insert_source(user_events_channel, move |event, _, _| {
if let calloop::channel::Event::Msg(msg) = event {
pending_user_events_clone.borrow_mut().push(msg);
}
})?;
// An event's loop awakener to wake up for window events from winit's windows.
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
// Handler of window requests.
event_loop.handle().insert_source(
event_loop_awakener_source,
move |_, _, winit_state| {
shim::handle_window_requests(winit_state);
},
)?;
let event_loop_handle = event_loop.handle();
let window_map = HashMap::new();
let event_sink = EventSink::new();
let window_updates = HashMap::new();
// Create event loop window target.
let event_loop_window_target = EventLoopWindowTarget {
display: display.clone(),
env,
state: RefCell::new(WinitState {
window_map,
event_sink,
window_updates,
}),
event_loop_handle,
output_manager,
event_loop_awakener,
wayland_dispatcher: wayland_dispatcher.clone(),
windowing_features,
theme_manager,
_marker: std::marker::PhantomData,
};
// Create event loop itself.
let event_loop = Self {
event_loop,
display,
pending_user_events,
wayland_dispatcher,
_seat_manager: seat_manager,
user_events_sender,
window_target: RootEventLoopWindowTarget {
p: PlatformEventLoopWindowTarget::Wayland(event_loop_window_target),
_marker: std::marker::PhantomData,
},
};
Ok(event_loop)
}
pub fn run<F>(mut self, callback: F) -> !
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
{
let exit_code = self.run_return(callback);
process::exit(exit_code);
}
pub fn run_return<F>(&mut self, mut callback: F) -> i32
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
let mut control_flow = ControlFlow::Poll;
let pending_user_events = self.pending_user_events.clone();
callback(
Event::NewEvents(StartCause::Init),
&self.window_target,
&mut control_flow,
);
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
// applications don't themselves have a formal suspend/resume lifecycle.
callback(Event::Resumed, &self.window_target, &mut control_flow);
let mut window_updates: Vec<(WindowId, WindowUpdate)> = Vec::new();
let mut event_sink_back_buffer = Vec::new();
// NOTE We break on errors from dispatches, since if we've got protocol error
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
// really an option. Instead we inform that the event loop got destroyed. We may
// communicate an error that something was terminated, but winit doesn't provide us
// with an API to do that via some event.
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
let exit_code = loop {
// Send pending events to the server.
let _ = self.display.flush();
// During the run of the user callback, some other code monitoring and reading the
// Wayland socket may have been run (mesa for example does this with vsync), if that
// is the case, some events may have been enqueued in our event queue.
//
// If some messages are there, the event loop needs to behave as if it was instantly
// woken up by messages arriving from the Wayland socket, to avoid delaying the
// dispatch of these events until we're woken up again.
let instant_wakeup = {
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
let queue = wayland_source.queue();
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.state.get_mut()
}
#[cfg(feature = "x11")]
_ => unreachable!(),
};
match queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
Ok(dispatched) => dispatched > 0,
Err(error) => break error.raw_os_error().unwrap_or(1),
}
};
match control_flow {
ControlFlow::ExitWithCode(code) => break code,
ControlFlow::Poll => {
// Non-blocking dispatch.
let timeout = Duration::from_millis(0);
if let Err(error) = self.loop_dispatch(Some(timeout)) {
break error.raw_os_error().unwrap_or(1);
}
callback(
Event::NewEvents(StartCause::Poll),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::Wait => {
let timeout = if instant_wakeup {
Some(Duration::from_millis(0))
} else {
None
};
if let Err(error) = self.loop_dispatch(timeout) {
break error.raw_os_error().unwrap_or(1);
}
callback(
Event::NewEvents(StartCause::WaitCancelled {
start: Instant::now(),
requested_resume: None,
}),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::WaitUntil(deadline) => {
let start = Instant::now();
// Compute the amount of time we'll block for.
let duration = if deadline > start && !instant_wakeup {
deadline - start
} else {
Duration::from_millis(0)
};
if let Err(error) = self.loop_dispatch(Some(duration)) {
break error.raw_os_error().unwrap_or(1);
}
let now = Instant::now();
if now < deadline {
callback(
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
}),
&self.window_target,
&mut control_flow,
)
} else {
callback(
Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
}),
&self.window_target,
&mut control_flow,
)
}
}
}
// Handle pending user events. We don't need back buffer, since we can't dispatch
// user events indirectly via callback to the user.
for user_event in pending_user_events.borrow_mut().drain(..) {
sticky_exit_callback(
Event::UserEvent(user_event),
&self.window_target,
&mut control_flow,
&mut callback,
);
}
// Process 'new' pending updates.
self.with_state(|state| {
window_updates.clear();
window_updates.extend(
state
.window_updates
.iter_mut()
.map(|(wid, window_update)| (*wid, window_update.take())),
);
});
for (window_id, window_update) in window_updates.iter_mut() {
if let Some(scale_factor) = window_update.scale_factor.map(|f| f as f64) {
let mut physical_size = self.with_state(|state| {
let window_handle = state.window_map.get(window_id).unwrap();
let mut size = window_handle.size.lock().unwrap();
// Update the new logical size if it was changed.
let window_size = window_update.size.unwrap_or(*size);
*size = window_size;
window_size.to_physical(scale_factor)
});
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size: &mut physical_size,
},
},
&self.window_target,
&mut control_flow,
&mut callback,
);
// We don't update size on a window handle since we'll do that later
// when handling size update.
let new_logical_size = physical_size.to_logical(scale_factor);
window_update.size = Some(new_logical_size);
}
if let Some(size) = window_update.size.take() {
let physical_size = self.with_state(|state| {
let window_handle = state.window_map.get_mut(window_id).unwrap();
let mut window_size = window_handle.size.lock().unwrap();
// Always issue resize event on scale factor change.
let physical_size =
if window_update.scale_factor.is_none() && *window_size == size {
// The size hasn't changed, don't inform downstream about that.
None
} else {
*window_size = size;
let scale_factor =
sctk::get_surface_scale_factor(window_handle.window.surface());
let physical_size = size.to_physical(scale_factor as f64);
Some(physical_size)
};
// We still perform all of those resize related logic even if the size
// hasn't changed, since GNOME relies on `set_geometry` calls after
// configures.
window_handle.window.resize(size.width, size.height);
window_handle.window.refresh();
// Mark that refresh isn't required, since we've done it right now.
window_update.refresh_frame = false;
physical_size
});
if let Some(physical_size) = physical_size {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
if window_update.close_window {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
// The purpose of the back buffer and that swap is to not hold borrow_mut when
// we're doing callback to the user, since we can double borrow if the user decides
// to create a window in one of those callbacks.
self.with_state(|state| {
std::mem::swap(
&mut event_sink_back_buffer,
&mut state.event_sink.window_events,
)
});
// Handle pending window events.
for event in event_sink_back_buffer.drain(..) {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
// Send events cleared.
sticky_exit_callback(
Event::MainEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);
// Handle RedrawRequested events.
for (window_id, window_update) in window_updates.iter() {
// Handle refresh of the frame.
if window_update.refresh_frame {
self.with_state(|state| {
let window_handle = state.window_map.get_mut(window_id).unwrap();
window_handle.window.refresh();
if !window_update.redraw_requested {
window_handle.window.surface().commit();
}
});
}
// Handle redraw request.
if window_update.redraw_requested {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(*window_id)),
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
// Send RedrawEventCleared.
sticky_exit_callback(
Event::RedrawEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);
};
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
exit_code
}
#[inline]
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.user_events_sender.clone())
}
#[inline]
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
&self.window_target
}
fn with_state<U, F: FnOnce(&mut WinitState) -> U>(&mut self, f: F) -> U {
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
f(state)
}
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
self.event_loop
.dispatch(timeout, state)
.map_err(|error| error.into())
}
}

View File

@@ -1,32 +0,0 @@
//! An event loop proxy.
use std::sync::mpsc::SendError;
use sctk::reexports::calloop::channel::Sender;
use crate::event_loop::EventLoopClosed;
/// A handle that can be sent across the threads and used to wake up the `EventLoop`.
pub struct EventLoopProxy<T: 'static> {
user_events_sender: Sender<T>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_events_sender: self.user_events_sender.clone(),
}
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn new(user_events_sender: Sender<T>) -> Self {
Self { user_events_sender }
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_events_sender
.send(event)
.map_err(|SendError(error)| EventLoopClosed(error))
}
}

View File

@@ -1,36 +0,0 @@
//! An event loop's sink to deliver events from the Wayland event callbacks.
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
use crate::platform_impl::platform::DeviceId as PlatformDeviceId;
use crate::window::WindowId as RootWindowId;
use super::{DeviceId, WindowId};
/// An event loop's sink to deliver events from the Wayland event callbacks
/// to the winit's user.
#[derive(Default)]
pub struct EventSink {
pub window_events: Vec<Event<'static, ()>>,
}
impl EventSink {
pub fn new() -> Self {
Default::default()
}
/// Add new device event to a queue.
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
self.window_events.push(Event::DeviceEvent {
event,
device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)),
});
}
/// Add new window event to a queue.
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
self.window_events.push(Event::WindowEvent {
event,
window_id: RootWindowId(window_id),
});
}
}

View File

@@ -1,25 +0,0 @@
//! A state that we pass around in a dispatch.
use std::collections::HashMap;
use super::EventSink;
use crate::platform_impl::wayland::window::shim::{WindowHandle, WindowUpdate};
use crate::platform_impl::wayland::WindowId;
/// Wrapper to carry winit's state.
pub struct WinitState {
/// A sink for window and device events that is being filled during dispatching
/// event loop and forwarded downstream afterwards.
pub event_sink: EventSink,
/// Window updates, which are coming from SCTK or the compositor, which require
/// calling back to the winit's downstream. They are handled right in the event loop,
/// unlike the ones coming from buffers on the `WindowHandle`'s.
pub window_updates: HashMap<WindowId, WindowUpdate>,
/// Window map containing all SCTK windows. Since those windows aren't allowed
/// to be sent to other threads, they live on the event loop's thread
/// and requests from winit's windows are being forwarded to them either via
/// `WindowUpdate` or buffer on the associated with it `WindowHandle`.
pub window_map: HashMap<WindowId, WindowHandle>,
}

View File

@@ -0,0 +1,419 @@
use std::sync::{Arc, Mutex};
use super::{make_wid, DeviceId};
use smithay_client_toolkit::{
keyboard::{
self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind,
},
reexports::client::protocol::{wl_keyboard, wl_seat},
};
use crate::event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent,
};
pub fn init_keyboard(
seat: &wl_seat::WlSeat,
sink: ::calloop::channel::Sender<crate::event::Event<()>>,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
) -> wl_keyboard::WlKeyboard {
// { variables to be captured by the closures
let target = Arc::new(Mutex::new(None));
let my_sink = sink.clone();
let repeat_sink = sink.clone();
let repeat_target = target.clone();
let my_modifiers = modifiers_tracker.clone();
// }
let ret = map_keyboard_auto_with_repeat(
seat,
KeyRepeatKind::System,
move |evt: KbEvent<'_>, _| {
match evt {
KbEvent::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(true),
})
.unwrap();
*target.lock().unwrap() = Some(wid);
}
KbEvent::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(false),
})
.unwrap();
*target.lock().unwrap() = None;
}
KbEvent::Key {
rawkey,
keysym,
state,
utf8,
..
} => {
if let Some(wid) = *target.lock().unwrap() {
let state = match state {
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
wl_keyboard::KeyState::Released => ElementState::Released,
_ => unreachable!(),
};
let vkcode = key_to_vkey(rawkey, keysym);
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: rawkey,
virtual_keycode: vkcode,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
},
})
.unwrap();
// send char event only on key press, not release
if let ElementState::Released = state {
return;
}
if let Some(txt) = utf8 {
for chr in txt.chars() {
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::ReceivedCharacter(chr),
})
.unwrap();
}
}
}
}
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
KbEvent::Modifiers {
modifiers: event_modifiers,
} => {
let modifiers = ModifiersState::from_wayland(event_modifiers);
*modifiers_tracker.lock().unwrap() = modifiers;
my_sink
.send(Event::DeviceEvent {
device_id: device_id(),
event: DeviceEvent::ModifiersChanged { modifiers },
})
.unwrap();
}
}
},
move |repeat_event: KeyRepeatEvent, _| {
if let Some(wid) = *repeat_target.lock().unwrap() {
let state = ElementState::Pressed;
let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym);
repeat_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: repeat_event.rawkey,
virtual_keycode: vkcode,
modifiers: my_modifiers.lock().unwrap().clone(),
},
},
})
.unwrap();
if let Some(txt) = repeat_event.utf8 {
for chr in txt.chars() {
repeat_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::ReceivedCharacter(chr),
})
.unwrap();
}
}
}
},
);
match ret {
Ok(keyboard) => keyboard,
Err(_) => {
// This is a fallback impl if libxkbcommon was not available
// This case should probably never happen, as most wayland
// compositors _need_ libxkbcommon anyway...
//
// In this case, we don't have the keymap information (it is
// supposed to be serialized by the compositor using libxkbcommon)
seat.get_keyboard(|keyboard| {
// { variables to be captured by the closure
let mut target = None;
let my_sink = sink;
// }
keyboard.implement_closure(
move |evt, _| match evt {
wl_keyboard::Event::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(true),
})
.unwrap();
target = Some(wid);
}
wl_keyboard::Event::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(false),
})
.unwrap();
target = None;
}
wl_keyboard::Event::Key { key, state, .. } => {
if let Some(wid) = target {
let state = match state {
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
wl_keyboard::KeyState::Released => ElementState::Released,
_ => unreachable!(),
};
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: key,
virtual_keycode: None,
modifiers: ModifiersState::default(),
},
},
})
.unwrap();
}
}
_ => (),
},
(),
)
})
.unwrap()
}
}
}
fn key_to_vkey(rawkey: u32, keysym: u32) -> Option<VirtualKeyCode> {
match rawkey {
1 => Some(VirtualKeyCode::Escape),
2 => Some(VirtualKeyCode::Key1),
3 => Some(VirtualKeyCode::Key2),
4 => Some(VirtualKeyCode::Key3),
5 => Some(VirtualKeyCode::Key4),
6 => Some(VirtualKeyCode::Key5),
7 => Some(VirtualKeyCode::Key6),
8 => Some(VirtualKeyCode::Key7),
9 => Some(VirtualKeyCode::Key8),
10 => Some(VirtualKeyCode::Key9),
11 => Some(VirtualKeyCode::Key0),
_ => keysym_to_vkey(keysym),
}
}
fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
use smithay_client_toolkit::keyboard::keysyms;
match keysym {
// letters
keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A),
keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B),
keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C),
keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D),
keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E),
keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F),
keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G),
keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H),
keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I),
keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J),
keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K),
keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L),
keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M),
keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N),
keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O),
keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P),
keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q),
keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R),
keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S),
keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T),
keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U),
keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V),
keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W),
keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X),
keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y),
keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z),
// F--
keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1),
keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2),
keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3),
keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4),
keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5),
keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6),
keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7),
keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8),
keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9),
keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10),
keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11),
keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12),
keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13),
keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14),
keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15),
keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16),
keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17),
keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18),
keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19),
keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20),
keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21),
keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22),
keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23),
keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24),
// flow control
keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot),
keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll),
keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause),
keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert),
keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home),
keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete),
keysyms::XKB_KEY_End => Some(VirtualKeyCode::End),
keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown),
keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp),
// arrows
keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left),
keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up),
keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right),
keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down),
//
keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back),
keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return),
keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space),
// keypad
keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock),
keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0),
keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1),
keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2),
keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3),
keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4),
keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5),
keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6),
keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7),
keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8),
keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9),
// misc
// => Some(VirtualKeyCode::AbntC1),
// => Some(VirtualKeyCode::AbntC2),
keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Add),
keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe),
// => Some(VirtualKeyCode::Apps),
// => Some(VirtualKeyCode::At),
// => Some(VirtualKeyCode::Ax),
keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash),
// => Some(VirtualKeyCode::Calculator),
// => Some(VirtualKeyCode::Capital),
keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon),
keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma),
// => Some(VirtualKeyCode::Convert),
// => Some(VirtualKeyCode::Decimal),
// => Some(VirtualKeyCode::Divide),
keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals),
// => Some(VirtualKeyCode::Grave),
// => Some(VirtualKeyCode::Kana),
// => Some(VirtualKeyCode::Kanji),
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
// => Some(VirtualKeyCode::LBracket),
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
// => Some(VirtualKeyCode::LWin),
// => Some(VirtualKeyCode::Mail),
// => Some(VirtualKeyCode::MediaSelect),
// => Some(VirtualKeyCode::MediaStop),
keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus),
keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Multiply),
// => Some(VirtualKeyCode::Mute),
// => Some(VirtualKeyCode::MyComputer),
// => Some(VirtualKeyCode::NextTrack),
// => Some(VirtualKeyCode::NoConvert),
keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma),
keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter),
keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals),
keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::Add),
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::Subtract),
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::Divide),
keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp),
keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown),
keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home),
keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End),
// => Some(VirtualKeyCode::OEM102),
// => Some(VirtualKeyCode::Period),
// => Some(VirtualKeyCode::Playpause),
// => Some(VirtualKeyCode::Power),
// => Some(VirtualKeyCode::Prevtrack),
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
// => Some(VirtualKeyCode::RBracket),
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
// => Some(VirtualKeyCode::RWin),
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash),
// => Some(VirtualKeyCode::Sleep),
// => Some(VirtualKeyCode::Stop),
// => Some(VirtualKeyCode::Subtract),
// => Some(VirtualKeyCode::Sysrq),
keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab),
keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab),
// => Some(VirtualKeyCode::Underline),
// => Some(VirtualKeyCode::Unlabeled),
keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown),
keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp),
// => Some(VirtualKeyCode::Wake),
// => Some(VirtualKeyCode::Webback),
// => Some(VirtualKeyCode::WebFavorites),
// => Some(VirtualKeyCode::WebForward),
// => Some(VirtualKeyCode::WebHome),
// => Some(VirtualKeyCode::WebRefresh),
// => Some(VirtualKeyCode::WebSearch),
// => Some(VirtualKeyCode::WebStop),
// => Some(VirtualKeyCode::Yen),
keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy),
keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste),
keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut),
// fallback
_ => None,
}
}
impl ModifiersState {
pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState {
ModifiersState {
shift: mods.shift,
ctrl: mods.ctrl,
alt: mods.alt,
logo: mods.logo,
}
}
}
fn device_id() -> crate::event::DeviceId {
crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId))
}
fn mk_root_wid(wid: crate::platform_impl::wayland::WindowId) -> crate::window::WindowId {
crate::window::WindowId(crate::platform_impl::WindowId::Wayland(wid))
}

View File

@@ -1,34 +1,41 @@
#![cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd",
target_os = "netbsd", target_os = "openbsd"))]
use sctk::reexports::client::protocol::wl_surface::WlSurface;
pub use self::{
event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode,
WindowEventsSink,
},
window::Window,
};
pub use crate::platform_impl::platform::WindowId;
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use output::{MonitorHandle, VideoMode};
pub use window::Window;
use smithay_client_toolkit::reexports::client::protocol::wl_surface;
mod env;
mod event_loop;
mod output;
mod seat;
mod keyboard;
mod pointer;
mod touch;
mod window;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
#[inline]
fn make_wid(surface: &WlSurface) -> WindowId {
WindowId(surface.as_ref().c_ptr() as u64)
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(usize);
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId(0)
}
}
#[inline]
fn make_wid(s: &wl_surface::WlSurface) -> WindowId {
WindowId(s.as_ref().c_ptr() as usize)
}

View File

@@ -1,250 +0,0 @@
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Display;
use sctk::environment::Environment;
use sctk::output::OutputStatusListener;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode};
use crate::platform_impl::platform::{
MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode,
};
use super::env::WinitEnv;
use super::event_loop::EventLoopWindowTarget;
/// Output manager.
pub struct OutputManager {
/// A handle that actually performs all operations on outputs.
handle: OutputManagerHandle,
_output_listener: OutputStatusListener,
}
impl OutputManager {
pub fn new(env: &Environment<WinitEnv>) -> Self {
let handle = OutputManagerHandle::new();
// Handle existing outputs.
for output in env.get_all_outputs() {
match sctk::output::with_output_info(&output, |info| info.obsolete) {
Some(false) => (),
// The output is obsolete or we've failed to access its data, skipping.
_ => continue,
}
// The output is present and unusable, add it to the output manager manager.
handle.add_output(output);
}
let handle_for_listener = handle.clone();
let output_listener = env.listen_for_outputs(move |output, info, _| {
if info.obsolete {
handle_for_listener.remove_output(output)
} else {
handle_for_listener.add_output(output)
}
});
Self {
handle,
_output_listener: output_listener,
}
}
pub fn handle(&self) -> OutputManagerHandle {
self.handle.clone()
}
}
/// A handle to output manager.
#[derive(Debug, Clone)]
pub struct OutputManagerHandle {
outputs: Arc<Mutex<VecDeque<MonitorHandle>>>,
}
impl OutputManagerHandle {
fn new() -> Self {
let outputs = Arc::new(Mutex::new(VecDeque::new()));
Self { outputs }
}
/// Handle addition of the output.
fn add_output(&self, output: WlOutput) {
let mut outputs = self.outputs.lock().unwrap();
let position = outputs.iter().position(|handle| handle.proxy == output);
if position.is_none() {
outputs.push_back(MonitorHandle::new(output));
}
}
/// Handle removal of the output.
fn remove_output(&self, output: WlOutput) {
let mut outputs = self.outputs.lock().unwrap();
let position = outputs.iter().position(|handle| handle.proxy == output);
if let Some(position) = position {
outputs.remove(position);
}
}
/// Get all observed outputs.
pub fn available_outputs(&self) -> VecDeque<MonitorHandle> {
self.outputs.lock().unwrap().clone()
}
}
#[derive(Clone, Debug)]
pub struct MonitorHandle {
pub(crate) proxy: WlOutput,
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
self.native_identifier() == other.native_identifier()
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.native_identifier().cmp(&other.native_identifier())
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.native_identifier().hash(state);
}
}
impl MonitorHandle {
#[inline]
pub(crate) fn new(proxy: WlOutput) -> Self {
Self { proxy }
}
#[inline]
pub fn name(&self) -> Option<String> {
sctk::output::with_output_info(&self.proxy, |info| {
format!("{} ({})", info.model, info.make)
})
}
#[inline]
pub fn native_identifier(&self) -> u32 {
sctk::output::with_output_info(&self.proxy, |info| info.id).unwrap_or(0)
}
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
match sctk::output::with_output_info(&self.proxy, |info| {
info.modes
.iter()
.find(|mode| mode.is_current)
.map(|mode| mode.dimensions)
}) {
Some(Some((w, h))) => (w as u32, h as u32),
_ => (0, 0),
}
.into()
}
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
sctk::output::with_output_info(&self.proxy, |info| info.location)
.unwrap_or((0, 0))
.into()
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
sctk::output::with_output_info(&self.proxy, |info| {
info.modes
.iter()
.find_map(|mode| mode.is_current.then(|| mode.refresh_rate as u32))
})
.flatten()
}
#[inline]
pub fn scale_factor(&self) -> i32 {
sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1)
}
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone())
.unwrap_or_default();
let monitor = self.clone();
modes.into_iter().map(move |mode| RootVideoMode {
video_mode: PlatformVideoMode::Wayland(VideoMode {
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
refresh_rate_millihertz: mode.refresh_rate as u32,
bit_depth: 32,
monitor: monitor.clone(),
}),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: PhysicalSize<u32>,
pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) monitor: MonitorHandle,
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.size
}
#[inline]
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(self.monitor.clone()),
}
}
}
impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn display(&self) -> &Display {
&self.display
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
self.output_manager.handle.available_outputs()
}
#[inline]
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
// There's no primary monitor on Wayland.
None
}
}

View File

@@ -0,0 +1,268 @@
use std::sync::{Arc, Mutex};
use crate::event::{
DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
WindowEvent,
};
use super::{
event_loop::{CursorManager, WindowEventsSink},
window::WindowStore,
DeviceId,
};
use smithay_client_toolkit::reexports::client::protocol::{
wl_pointer::{self, Event as PtrEvent, WlPointer},
wl_seat,
};
use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, zwp_relative_pointer_v1::Event,
zwp_relative_pointer_v1::ZwpRelativePointerV1,
};
use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{
zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::Lifetime,
zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
};
use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface;
pub fn implement_pointer<T: 'static>(
seat: &wl_seat::WlSeat,
sink: Arc<Mutex<WindowEventsSink<T>>>,
store: Arc<Mutex<WindowStore>>,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
cursor_manager: Arc<Mutex<CursorManager>>,
) -> WlPointer {
seat.get_pointer(|pointer| {
let mut mouse_focus = None;
let mut axis_buffer = None;
let mut axis_discrete_buffer = None;
let mut axis_state = TouchPhase::Ended;
pointer.implement_closure(
move |evt, pointer| {
let mut sink = sink.lock().unwrap();
let store = store.lock().unwrap();
let mut cursor_manager = cursor_manager.lock().unwrap();
match evt {
PtrEvent::Enter {
surface,
surface_x,
surface_y,
..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
mouse_focus = Some(wid);
sink.send_window_event(
WindowEvent::CursorEntered {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
},
wid,
);
sink.send_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
position: (surface_x, surface_y).into(),
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
cursor_manager.reload_cursor_style();
}
PtrEvent::Leave { surface, .. } => {
mouse_focus = None;
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
sink.send_window_event(
WindowEvent::CursorLeft {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
},
wid,
);
}
}
PtrEvent::Motion {
surface_x,
surface_y,
..
} => {
if let Some(wid) = mouse_focus {
sink.send_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
position: (surface_x, surface_y).into(),
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
PtrEvent::Button { button, state, .. } => {
if let Some(wid) = mouse_focus {
let state = match state {
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
wl_pointer::ButtonState::Released => ElementState::Released,
_ => unreachable!(),
};
let button = match button {
0x110 => MouseButton::Left,
0x111 => MouseButton::Right,
0x112 => MouseButton::Middle,
// TODO figure out the translation ?
_ => return,
};
sink.send_window_event(
WindowEvent::MouseInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
state,
button,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
PtrEvent::Axis { axis, value, .. } => {
if let Some(wid) = mouse_focus {
if pointer.as_ref().version() < 5 {
let (mut x, mut y) = (0.0, 0.0);
// old seat compatibility
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x += value as f32,
_ => unreachable!(),
}
sink.send_window_event(
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
delta: MouseScrollDelta::PixelDelta(
(x as f64, y as f64).into(),
),
phase: TouchPhase::Moved,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
} else {
let (mut x, mut y) = axis_buffer.unwrap_or((0.0, 0.0));
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x += value as f32,
_ => unreachable!(),
}
axis_buffer = Some((x, y));
axis_state = match axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
}
}
PtrEvent::Frame => {
let axis_buffer = axis_buffer.take();
let axis_discrete_buffer = axis_discrete_buffer.take();
if let Some(wid) = mouse_focus {
if let Some((x, y)) = axis_discrete_buffer {
sink.send_window_event(
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
delta: MouseScrollDelta::LineDelta(x as f32, y as f32),
phase: axis_state,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
} else if let Some((x, y)) = axis_buffer {
sink.send_window_event(
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
delta: MouseScrollDelta::PixelDelta(
(x as f64, y as f64).into(),
),
phase: axis_state,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
}
PtrEvent::AxisSource { .. } => (),
PtrEvent::AxisStop { .. } => {
axis_state = TouchPhase::Ended;
}
PtrEvent::AxisDiscrete { axis, discrete } => {
let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0, 0));
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= discrete,
wl_pointer::Axis::HorizontalScroll => x += discrete,
_ => unreachable!(),
}
axis_discrete_buffer = Some((x, y));
axis_state = match axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
_ => unreachable!(),
}
},
(),
)
})
.unwrap()
}
pub fn implement_relative_pointer<T: 'static>(
sink: Arc<Mutex<WindowEventsSink<T>>>,
pointer: &WlPointer,
manager: &ZwpRelativePointerManagerV1,
) -> Result<ZwpRelativePointerV1, ()> {
manager.get_relative_pointer(pointer, |rel_pointer| {
rel_pointer.implement_closure(
move |evt, _rel_pointer| {
let mut sink = sink.lock().unwrap();
match evt {
Event::RelativeMotion { dx, dy, .. } => sink
.send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId),
_ => unreachable!(),
}
},
(),
)
})
}
pub fn implement_locked_pointer(
surface: &WlSurface,
pointer: &WlPointer,
constraints: &ZwpPointerConstraintsV1,
) -> Result<ZwpLockedPointerV1, ()> {
constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent.to_raw(), |c| {
c.implement_closure(|_, _| (), ())
})
}

View File

@@ -1,151 +0,0 @@
//! Handling of various keyboard events.
use sctk::reexports::client::protocol::wl_keyboard::KeyState;
use sctk::seat::keyboard::Event as KeyboardEvent;
use crate::event::{ElementState, KeyboardInput, ModifiersState, WindowEvent};
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::{self, DeviceId};
use super::keymap;
use super::KeyboardInner;
#[inline]
pub(super) fn handle_keyboard(
event: KeyboardEvent<'_>,
inner: &mut KeyboardInner,
winit_state: &mut WinitState,
) {
let event_sink = &mut winit_state.event_sink;
match event {
KeyboardEvent::Enter { surface, .. } => {
let window_id = wayland::make_wid(&surface);
// Window gained focus.
event_sink.push_window_event(WindowEvent::Focused(true), window_id);
// Dispatch modifers changes that we've received before getting `Enter` event.
if let Some(modifiers) = inner.pending_modifers_state.take() {
*inner.modifiers_state.borrow_mut() = modifiers;
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
}
inner.target_window_id = Some(window_id);
}
KeyboardEvent::Leave { surface, .. } => {
let window_id = wayland::make_wid(&surface);
// Notify that no modifiers are being pressed.
if !inner.modifiers_state.borrow().is_empty() {
event_sink.push_window_event(
WindowEvent::ModifiersChanged(ModifiersState::empty()),
window_id,
);
}
// Window lost focus.
event_sink.push_window_event(WindowEvent::Focused(false), window_id);
// Reset the id.
inner.target_window_id = None;
}
KeyboardEvent::Key {
rawkey,
keysym,
state,
utf8,
..
} => {
let window_id = match inner.target_window_id {
Some(window_id) => window_id,
None => return,
};
let state = match state {
KeyState::Pressed => ElementState::Pressed,
KeyState::Released => ElementState::Released,
_ => unreachable!(),
};
let virtual_keycode = keymap::keysym_to_vkey(keysym);
event_sink.push_window_event(
#[allow(deprecated)]
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
input: KeyboardInput {
state,
scancode: rawkey,
virtual_keycode,
modifiers: *inner.modifiers_state.borrow(),
},
is_synthetic: false,
},
window_id,
);
// Send ReceivedCharacter event only on ElementState::Pressed.
if ElementState::Released == state {
return;
}
if let Some(txt) = utf8 {
for ch in txt.chars() {
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
}
}
}
KeyboardEvent::Repeat {
rawkey,
keysym,
utf8,
..
} => {
let window_id = match inner.target_window_id {
Some(window_id) => window_id,
None => return,
};
let virtual_keycode = keymap::keysym_to_vkey(keysym);
event_sink.push_window_event(
#[allow(deprecated)]
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
input: KeyboardInput {
state: ElementState::Pressed,
scancode: rawkey,
virtual_keycode,
modifiers: *inner.modifiers_state.borrow(),
},
is_synthetic: false,
},
window_id,
);
if let Some(txt) = utf8 {
for ch in txt.chars() {
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
}
}
}
KeyboardEvent::Modifiers { modifiers } => {
let modifiers = ModifiersState::from(modifiers);
if let Some(window_id) = inner.target_window_id {
*inner.modifiers_state.borrow_mut() = modifiers;
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
} else {
// Compositor must send modifiers after wl_keyboard::enter, however certain
// compositors are still sending it before, so stash such events and send
// them on wl_keyboard::enter.
inner.pending_modifers_state = Some(modifiers);
}
}
}
}

View File

@@ -1,192 +0,0 @@
//! Convert Wayland keys to winit keys.
use crate::event::VirtualKeyCode;
pub fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
use sctk::seat::keyboard::keysyms;
match keysym {
// Numbers.
keysyms::XKB_KEY_1 => Some(VirtualKeyCode::Key1),
keysyms::XKB_KEY_2 => Some(VirtualKeyCode::Key2),
keysyms::XKB_KEY_3 => Some(VirtualKeyCode::Key3),
keysyms::XKB_KEY_4 => Some(VirtualKeyCode::Key4),
keysyms::XKB_KEY_5 => Some(VirtualKeyCode::Key5),
keysyms::XKB_KEY_6 => Some(VirtualKeyCode::Key6),
keysyms::XKB_KEY_7 => Some(VirtualKeyCode::Key7),
keysyms::XKB_KEY_8 => Some(VirtualKeyCode::Key8),
keysyms::XKB_KEY_9 => Some(VirtualKeyCode::Key9),
keysyms::XKB_KEY_0 => Some(VirtualKeyCode::Key0),
// Letters.
keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A),
keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B),
keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C),
keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D),
keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E),
keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F),
keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G),
keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H),
keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I),
keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J),
keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K),
keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L),
keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M),
keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N),
keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O),
keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P),
keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q),
keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R),
keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S),
keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T),
keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U),
keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V),
keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W),
keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X),
keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y),
keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z),
// Escape.
keysyms::XKB_KEY_Escape => Some(VirtualKeyCode::Escape),
// Function keys.
keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1),
keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2),
keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3),
keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4),
keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5),
keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6),
keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7),
keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8),
keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9),
keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10),
keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11),
keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12),
keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13),
keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14),
keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15),
keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16),
keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17),
keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18),
keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19),
keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20),
keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21),
keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22),
keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23),
keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24),
// Flow control.
keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot),
keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll),
keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause),
keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert),
keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home),
keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete),
keysyms::XKB_KEY_End => Some(VirtualKeyCode::End),
keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown),
keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp),
// Arrows.
keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left),
keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up),
keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right),
keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down),
keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back),
keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return),
keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space),
keysyms::XKB_KEY_Multi_key => Some(VirtualKeyCode::Compose),
keysyms::XKB_KEY_caret => Some(VirtualKeyCode::Caret),
// Keypad.
keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock),
keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0),
keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1),
keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2),
keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3),
keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4),
keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5),
keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6),
keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7),
keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8),
keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9),
// Misc.
// => Some(VirtualKeyCode::AbntC1),
// => Some(VirtualKeyCode::AbntC2),
keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus),
keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe),
// => Some(VirtualKeyCode::Apps),
keysyms::XKB_KEY_at => Some(VirtualKeyCode::At),
// => Some(VirtualKeyCode::Ax),
keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash),
keysyms::XKB_KEY_XF86Calculator => Some(VirtualKeyCode::Calculator),
// => Some(VirtualKeyCode::Capital),
keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon),
keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma),
// => Some(VirtualKeyCode::Convert),
keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals),
keysyms::XKB_KEY_grave => Some(VirtualKeyCode::Grave),
// => Some(VirtualKeyCode::Kana),
keysyms::XKB_KEY_Kanji => Some(VirtualKeyCode::Kanji),
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
keysyms::XKB_KEY_bracketleft => Some(VirtualKeyCode::LBracket),
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
keysyms::XKB_KEY_Super_L => Some(VirtualKeyCode::LWin),
keysyms::XKB_KEY_XF86Mail => Some(VirtualKeyCode::Mail),
// => Some(VirtualKeyCode::MediaSelect),
// => Some(VirtualKeyCode::MediaStop),
keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus),
keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk),
keysyms::XKB_KEY_XF86AudioMute => Some(VirtualKeyCode::Mute),
// => Some(VirtualKeyCode::MyComputer),
keysyms::XKB_KEY_XF86AudioNext => Some(VirtualKeyCode::NextTrack),
// => Some(VirtualKeyCode::NoConvert),
keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma),
keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter),
keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals),
keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd),
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract),
keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply),
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide),
keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal),
keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp),
keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown),
keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home),
keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End),
keysyms::XKB_KEY_KP_Left => Some(VirtualKeyCode::Left),
keysyms::XKB_KEY_KP_Up => Some(VirtualKeyCode::Up),
keysyms::XKB_KEY_KP_Right => Some(VirtualKeyCode::Right),
keysyms::XKB_KEY_KP_Down => Some(VirtualKeyCode::Down),
// => Some(VirtualKeyCode::OEM102),
keysyms::XKB_KEY_period => Some(VirtualKeyCode::Period),
// => Some(VirtualKeyCode::Playpause),
keysyms::XKB_KEY_XF86PowerOff => Some(VirtualKeyCode::Power),
keysyms::XKB_KEY_XF86AudioPrev => Some(VirtualKeyCode::PrevTrack),
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
keysyms::XKB_KEY_bracketright => Some(VirtualKeyCode::RBracket),
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
keysyms::XKB_KEY_Super_R => Some(VirtualKeyCode::RWin),
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash),
keysyms::XKB_KEY_XF86Sleep => Some(VirtualKeyCode::Sleep),
// => Some(VirtualKeyCode::Stop),
// => Some(VirtualKeyCode::Sysrq),
keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab),
keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab),
keysyms::XKB_KEY_underscore => Some(VirtualKeyCode::Underline),
// => Some(VirtualKeyCode::Unlabeled),
keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown),
keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp),
// => Some(VirtualKeyCode::Wake),
// => Some(VirtualKeyCode::Webback),
// => Some(VirtualKeyCode::WebFavorites),
// => Some(VirtualKeyCode::WebForward),
// => Some(VirtualKeyCode::WebHome),
// => Some(VirtualKeyCode::WebRefresh),
// => Some(VirtualKeyCode::WebSearch),
// => Some(VirtualKeyCode::WebStop),
keysyms::XKB_KEY_yen => Some(VirtualKeyCode::Yen),
keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy),
keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste),
keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut),
// Fallback.
_ => None,
}
}

View File

@@ -1,90 +0,0 @@
//! Wayland keyboard handling.
use std::cell::RefCell;
use std::rc::Rc;
use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::Attached;
use sctk::reexports::calloop::LoopHandle;
use sctk::seat::keyboard;
use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::WindowId;
mod handlers;
mod keymap;
pub(crate) struct Keyboard {
pub keyboard: WlKeyboard,
}
impl Keyboard {
pub fn new(
seat: &Attached<WlSeat>,
loop_handle: LoopHandle<'static, WinitState>,
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Option<Self> {
let mut inner = KeyboardInner::new(modifiers_state);
let keyboard = keyboard::map_keyboard_repeat(
loop_handle.clone(),
seat,
None,
keyboard::RepeatKind::System,
move |event, _, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_keyboard(event, &mut inner, winit_state);
},
)
.ok()?;
Some(Self { keyboard })
}
}
impl Drop for Keyboard {
fn drop(&mut self) {
if self.keyboard.as_ref().version() >= 3 {
self.keyboard.release();
}
}
}
struct KeyboardInner {
/// Currently focused surface.
target_window_id: Option<WindowId>,
/// A pending state of modifiers.
///
/// This state is getting set if we've got a modifiers update
/// before `Enter` event, which shouldn't happen in general, however
/// some compositors are still doing so.
pending_modifers_state: Option<ModifiersState>,
/// Current state of modifiers keys.
modifiers_state: Rc<RefCell<ModifiersState>>,
}
impl KeyboardInner {
fn new(modifiers_state: Rc<RefCell<ModifiersState>>) -> Self {
Self {
target_window_id: None,
pending_modifers_state: None,
modifiers_state,
}
}
}
impl From<keyboard::ModifiersState> for ModifiersState {
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
let mut wl_mods = ModifiersState::empty();
wl_mods.set(ModifiersState::SHIFT, mods.shift);
wl_mods.set(ModifiersState::CTRL, mods.ctrl);
wl_mods.set(ModifiersState::ALT, mods.alt);
wl_mods.set(ModifiersState::LOGO, mods.logo);
wl_mods
}
}

View File

@@ -1,208 +0,0 @@
//! Seat handling and managing.
use std::cell::RefCell;
use std::rc::Rc;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::Attached;
use sctk::environment::Environment;
use sctk::reexports::calloop::LoopHandle;
use sctk::seat::pointer::ThemeManager;
use sctk::seat::{SeatData, SeatListener};
use super::env::WinitEnv;
use super::event_loop::WinitState;
use crate::event::ModifiersState;
mod keyboard;
pub mod pointer;
pub mod text_input;
mod touch;
use keyboard::Keyboard;
use pointer::Pointers;
use text_input::TextInput;
use touch::Touch;
pub struct SeatManager {
/// Listener for seats.
_seat_listener: SeatListener,
}
impl SeatManager {
pub fn new(
env: &Environment<WinitEnv>,
loop_handle: LoopHandle<'static, WinitState>,
theme_manager: ThemeManager,
) -> Self {
let relative_pointer_manager = env.get_global::<ZwpRelativePointerManagerV1>();
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>();
let text_input_manager = env.get_global::<ZwpTextInputManagerV3>();
let mut inner = SeatManagerInner::new(
theme_manager,
relative_pointer_manager,
pointer_constraints,
text_input_manager,
loop_handle,
);
// Handle existing seats.
for seat in env.get_all_seats() {
let seat_data = match sctk::seat::clone_seat_data(&seat) {
Some(seat_data) => seat_data,
None => continue,
};
inner.process_seat_update(&seat, &seat_data);
}
let seat_listener = env.listen_for_seats(move |seat, seat_data, _| {
inner.process_seat_update(&seat, seat_data);
});
Self {
_seat_listener: seat_listener,
}
}
}
/// Inner state of the seat manager.
struct SeatManagerInner {
/// Currently observed seats.
seats: Vec<SeatInfo>,
/// Loop handle.
loop_handle: LoopHandle<'static, WinitState>,
/// Relative pointer manager.
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
/// Pointer constraints.
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
/// Text input manager.
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
/// A theme manager.
theme_manager: ThemeManager,
}
impl SeatManagerInner {
fn new(
theme_manager: ThemeManager,
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
loop_handle: LoopHandle<'static, WinitState>,
) -> Self {
Self {
seats: Vec::new(),
loop_handle,
relative_pointer_manager,
pointer_constraints,
text_input_manager,
theme_manager,
}
}
/// Handle seats update from the `SeatListener`.
pub fn process_seat_update(&mut self, seat: &Attached<WlSeat>, seat_data: &SeatData) {
let detached_seat = seat.detach();
let position = self.seats.iter().position(|si| si.seat == detached_seat);
let index = position.unwrap_or_else(|| {
self.seats.push(SeatInfo::new(detached_seat));
self.seats.len() - 1
});
let seat_info = &mut self.seats[index];
// Pointer handling.
if seat_data.has_pointer && !seat_data.defunct {
if seat_info.pointer.is_none() {
seat_info.pointer = Some(Pointers::new(
seat,
&self.theme_manager,
&self.relative_pointer_manager,
&self.pointer_constraints,
seat_info.modifiers_state.clone(),
));
}
} else {
seat_info.pointer = None;
}
// Handle keyboard.
if seat_data.has_keyboard && !seat_data.defunct {
if seat_info.keyboard.is_none() {
seat_info.keyboard = Keyboard::new(
seat,
self.loop_handle.clone(),
seat_info.modifiers_state.clone(),
);
}
} else {
seat_info.keyboard = None;
}
// Handle touch.
if seat_data.has_touch && !seat_data.defunct {
if seat_info.touch.is_none() {
seat_info.touch = Some(Touch::new(seat));
}
} else {
seat_info.touch = None;
}
// Handle text input.
if let Some(text_input_manager) = self.text_input_manager.as_ref() {
if seat_data.defunct {
seat_info.text_input = None;
} else if seat_info.text_input.is_none() {
seat_info.text_input = Some(TextInput::new(seat, text_input_manager));
}
}
}
}
/// Resources associtated with a given seat.
struct SeatInfo {
/// Seat to which this `SeatInfo` belongs.
seat: WlSeat,
/// A keyboard handle with its repeat rate handling.
keyboard: Option<Keyboard>,
/// All pointers we're using on a seat.
pointer: Option<Pointers>,
/// Touch handling.
touch: Option<Touch>,
/// Text input handling aka IME.
text_input: Option<TextInput>,
/// The current state of modifiers observed in keyboard handler.
///
/// We keep modifiers state on a seat, since it's being used by pointer events as well.
modifiers_state: Rc<RefCell<ModifiersState>>,
}
impl SeatInfo {
pub fn new(seat: WlSeat) -> Self {
Self {
seat,
keyboard: None,
pointer: None,
touch: None,
text_input: None,
modifiers_state: Rc::new(RefCell::new(ModifiersState::default())),
}
}
}

View File

@@ -1,82 +0,0 @@
//! Data which is used in pointer callbacks.
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
use crate::event::{ModifiersState, TouchPhase};
/// A data being used by pointer handlers.
pub(super) struct PointerData {
/// Winit's surface the pointer is currently over.
pub surface: Option<WlSurface>,
/// Current modifiers state.
///
/// This refers a state of modifiers from `WlKeyboard` on
/// the given seat.
pub modifiers_state: Rc<RefCell<ModifiersState>>,
/// Pointer constraints.
pub pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
pub confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
pub locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
/// Latest observed serial in pointer events.
pub latest_serial: Rc<Cell<u32>>,
/// Latest observed serial in pointer enter events.
pub latest_enter_serial: Rc<Cell<u32>>,
/// The currently accumulated axis data on a pointer.
pub axis_data: AxisData,
}
impl PointerData {
pub fn new(
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Self {
Self {
surface: None,
latest_serial: Rc::new(Cell::new(0)),
latest_enter_serial: Rc::new(Cell::new(0)),
confined_pointer,
locked_pointer,
modifiers_state,
pointer_constraints,
axis_data: AxisData::new(),
}
}
}
/// Axis data.
#[derive(Clone, Copy)]
pub(super) struct AxisData {
/// Current state of the axis.
pub axis_state: TouchPhase,
/// A buffer for `PixelDelta` event.
pub axis_buffer: Option<(f32, f32)>,
/// A buffer for `LineDelta` event.
pub axis_discrete_buffer: Option<(f32, f32)>,
}
impl AxisData {
pub fn new() -> Self {
Self {
axis_state: TouchPhase::Ended,
axis_buffer: None,
axis_discrete_buffer: None,
}
}
}

View File

@@ -1,318 +0,0 @@
//! Handlers for the pointers we're using.
use std::cell::RefCell;
use std::rc::Rc;
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent};
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent;
use sctk::seat::pointer::ThemedPointer;
use crate::dpi::LogicalPosition;
use crate::event::{
DeviceEvent, ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
};
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::{self, DeviceId};
use super::{PointerData, WinitPointer};
// These values are comming from <linux/input-event-codes.h>.
const BTN_LEFT: u32 = 0x110;
const BTN_RIGHT: u32 = 0x111;
const BTN_MIDDLE: u32 = 0x112;
#[inline]
pub(super) fn handle_pointer(
pointer: ThemedPointer,
event: PointerEvent,
pointer_data: &Rc<RefCell<PointerData>>,
winit_state: &mut WinitState,
seat: WlSeat,
) {
let event_sink = &mut winit_state.event_sink;
let mut pointer_data = pointer_data.borrow_mut();
match event {
PointerEvent::Enter {
surface,
surface_x,
surface_y,
serial,
..
} => {
pointer_data.latest_serial.replace(serial);
pointer_data.latest_enter_serial.replace(serial);
let window_id = wayland::make_wid(&surface);
if !winit_state.window_map.contains_key(&window_id) {
return;
}
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
pointer_data.surface = Some(surface);
// Notify window that pointer entered the surface.
let winit_pointer = WinitPointer {
pointer,
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(),
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
seat,
};
window_handle.pointer_entered(winit_pointer);
event_sink.push_window_event(
WindowEvent::CursorEntered {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
},
window_id,
);
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
event_sink.push_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
position,
modifiers: *pointer_data.modifiers_state.borrow(),
},
window_id,
);
}
PointerEvent::Leave { surface, serial } => {
pointer_data.surface = None;
pointer_data.latest_serial.replace(serial);
let window_id = wayland::make_wid(&surface);
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
// Notify a window that pointer is no longer observing it.
let winit_pointer = WinitPointer {
pointer,
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(),
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
seat,
};
window_handle.pointer_left(winit_pointer);
event_sink.push_window_event(
WindowEvent::CursorLeft {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
},
window_id,
);
}
PointerEvent::Motion {
surface_x,
surface_y,
..
} => {
let surface = match pointer_data.surface.as_ref() {
Some(surface) => surface,
None => return,
};
let window_id = wayland::make_wid(surface);
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
event_sink.push_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
position,
modifiers: *pointer_data.modifiers_state.borrow(),
},
window_id,
);
}
PointerEvent::Button {
button,
state,
serial,
..
} => {
pointer_data.latest_serial.replace(serial);
let window_id = match pointer_data.surface.as_ref().map(wayland::make_wid) {
Some(window_id) => window_id,
None => return,
};
let state = match state {
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
wl_pointer::ButtonState::Released => ElementState::Released,
_ => unreachable!(),
};
let button = match button {
BTN_LEFT => MouseButton::Left,
BTN_RIGHT => MouseButton::Right,
BTN_MIDDLE => MouseButton::Middle,
button => MouseButton::Other(button as u16),
};
event_sink.push_window_event(
WindowEvent::MouseInput {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
state,
button,
modifiers: *pointer_data.modifiers_state.borrow(),
},
window_id,
);
}
PointerEvent::Axis { axis, value, .. } => {
let surface = match pointer_data.surface.as_ref() {
Some(surface) => surface,
None => return,
};
let window_id = wayland::make_wid(surface);
if pointer.as_ref().version() < 5 {
let (mut x, mut y) = (0.0, 0.0);
// Old seat compatibility.
match axis {
// Wayland sign convention is the inverse of winit.
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
_ => unreachable!(),
}
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
let delta = LogicalPosition::new(x as f64, y as f64).to_physical(scale_factor);
event_sink.push_window_event(
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
delta: MouseScrollDelta::PixelDelta(delta),
phase: TouchPhase::Moved,
modifiers: *pointer_data.modifiers_state.borrow(),
},
window_id,
);
} else {
let (mut x, mut y) = pointer_data.axis_data.axis_buffer.unwrap_or((0.0, 0.0));
match axis {
// Wayland sign convention is the inverse of winit.
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
_ => unreachable!(),
}
pointer_data.axis_data.axis_buffer = Some((x, y));
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
}
PointerEvent::AxisDiscrete { axis, discrete } => {
let (mut x, mut y) = pointer_data
.axis_data
.axis_discrete_buffer
.unwrap_or((0., 0.));
match axis {
// Wayland sign convention is the inverse of winit.
wl_pointer::Axis::VerticalScroll => y -= discrete as f32,
wl_pointer::Axis::HorizontalScroll => x -= discrete as f32,
_ => unreachable!(),
}
pointer_data.axis_data.axis_discrete_buffer = Some((x, y));
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
PointerEvent::AxisSource { .. } => (),
PointerEvent::AxisStop { .. } => {
pointer_data.axis_data.axis_state = TouchPhase::Ended;
}
PointerEvent::Frame => {
let axis_buffer = pointer_data.axis_data.axis_buffer.take();
let axis_discrete_buffer = pointer_data.axis_data.axis_discrete_buffer.take();
let surface = match pointer_data.surface.as_ref() {
Some(surface) => surface,
None => return,
};
let window_id = wayland::make_wid(surface);
let window_event = if let Some((x, y)) = axis_discrete_buffer {
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
delta: MouseScrollDelta::LineDelta(x, y),
phase: pointer_data.axis_data.axis_state,
modifiers: *pointer_data.modifiers_state.borrow(),
}
} else if let Some((x, y)) = axis_buffer {
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
let delta = LogicalPosition::new(x, y).to_physical(scale_factor);
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
delta: MouseScrollDelta::PixelDelta(delta),
phase: pointer_data.axis_data.axis_state,
modifiers: *pointer_data.modifiers_state.borrow(),
}
} else {
return;
};
event_sink.push_window_event(window_event, window_id);
}
_ => (),
}
}
#[inline]
pub(super) fn handle_relative_pointer(event: RelativePointerEvent, winit_state: &mut WinitState) {
if let RelativePointerEvent::RelativeMotion {
dx_unaccel,
dy_unaccel,
..
} = event
{
winit_state.event_sink.push_device_event(
DeviceEvent::MouseMotion {
delta: (dx_unaccel, dy_unaccel),
},
DeviceId,
)
}
}

View File

@@ -1,343 +0,0 @@
//! All pointer related handling.
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime};
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
use sctk::window::Window;
use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::window::WinitFrame;
use crate::window::CursorIcon;
mod data;
mod handlers;
use data::PointerData;
/// A proxy to Wayland pointer, which serves requests from a `WindowHandle`.
pub struct WinitPointer {
pointer: ThemedPointer,
/// Create confined pointers.
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
/// Cursor to handle confine requests.
confined_pointer: Weak<RefCell<Option<ZwpConfinedPointerV1>>>,
/// Cursor to handle locked requests.
locked_pointer: Weak<RefCell<Option<ZwpLockedPointerV1>>>,
/// Latest observed serial in pointer events.
/// used by Window::start_interactive_move()
latest_serial: Rc<Cell<u32>>,
/// Latest observed serial in pointer enter events.
/// used by Window::set_cursor()
latest_enter_serial: Rc<Cell<u32>>,
/// Seat.
seat: WlSeat,
}
impl PartialEq for WinitPointer {
fn eq(&self, other: &Self) -> bool {
*self.pointer == *other.pointer
}
}
impl Eq for WinitPointer {}
impl WinitPointer {
/// Set the cursor icon.
///
/// Providing `None` will hide the cursor.
pub fn set_cursor(&self, cursor_icon: Option<CursorIcon>) {
let cursor_icon = match cursor_icon {
Some(cursor_icon) => cursor_icon,
None => {
// Hide the cursor.
// WlPointer::set_cursor() expects the serial of the last *enter*
// event (compare to to start_interactive_move()).
(*self.pointer).set_cursor(self.latest_enter_serial.get(), None, 0, 0);
return;
}
};
let cursors: &[&str] = match cursor_icon {
CursorIcon::Alias => &["link"],
CursorIcon::Arrow => &["arrow"],
CursorIcon::Cell => &["plus"],
CursorIcon::Copy => &["copy"],
CursorIcon::Crosshair => &["crosshair"],
CursorIcon::Default => &["left_ptr"],
CursorIcon::Hand => &["hand2", "hand1"],
CursorIcon::Help => &["question_arrow"],
CursorIcon::Move => &["move"],
CursorIcon::Grab => &["openhand", "grab"],
CursorIcon::Grabbing => &["closedhand", "grabbing"],
CursorIcon::Progress => &["progress"],
CursorIcon::AllScroll => &["all-scroll"],
CursorIcon::ContextMenu => &["context-menu"],
CursorIcon::NoDrop => &["no-drop", "circle"],
CursorIcon::NotAllowed => &["crossed_circle"],
// Resize cursors
CursorIcon::EResize => &["right_side"],
CursorIcon::NResize => &["top_side"],
CursorIcon::NeResize => &["top_right_corner"],
CursorIcon::NwResize => &["top_left_corner"],
CursorIcon::SResize => &["bottom_side"],
CursorIcon::SeResize => &["bottom_right_corner"],
CursorIcon::SwResize => &["bottom_left_corner"],
CursorIcon::WResize => &["left_side"],
CursorIcon::EwResize => &["h_double_arrow"],
CursorIcon::NsResize => &["v_double_arrow"],
CursorIcon::NwseResize => &["bd_double_arrow", "size_fdiag"],
CursorIcon::NeswResize => &["fd_double_arrow", "size_bdiag"],
CursorIcon::ColResize => &["split_h", "h_double_arrow"],
CursorIcon::RowResize => &["split_v", "v_double_arrow"],
CursorIcon::Text => &["text", "xterm"],
CursorIcon::VerticalText => &["vertical-text"],
CursorIcon::Wait => &["watch"],
CursorIcon::ZoomIn => &["zoom-in"],
CursorIcon::ZoomOut => &["zoom-out"],
};
let serial = Some(self.latest_enter_serial.get());
for cursor in cursors {
if self.pointer.set_cursor(cursor, serial).is_ok() {
return;
}
}
warn!("Failed to set cursor to {:?}", cursor_icon);
}
/// Confine the pointer to a surface.
pub fn confine(&self, surface: &WlSurface) {
let pointer_constraints = match &self.pointer_constraints {
Some(pointer_constraints) => pointer_constraints,
None => return,
};
let confined_pointer = match self.confined_pointer.upgrade() {
Some(confined_pointer) => confined_pointer,
// A pointer is gone.
None => return,
};
*confined_pointer.borrow_mut() = Some(init_confined_pointer(
pointer_constraints,
surface,
&*self.pointer,
));
}
/// Tries to unconfine the pointer if the current pointer is confined.
pub fn unconfine(&self) {
let confined_pointer = match self.confined_pointer.upgrade() {
Some(confined_pointer) => confined_pointer,
// A pointer is gone.
None => return,
};
let mut confined_pointer = confined_pointer.borrow_mut();
if let Some(confined_pointer) = confined_pointer.take() {
confined_pointer.destroy();
}
}
pub fn lock(&self, surface: &WlSurface) {
let pointer_constraints = match &self.pointer_constraints {
Some(pointer_constraints) => pointer_constraints,
None => return,
};
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
*locked_pointer.borrow_mut() = Some(init_locked_pointer(
pointer_constraints,
surface,
&*self.pointer,
));
}
pub fn unlock(&self) {
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
let mut locked_pointer = locked_pointer.borrow_mut();
if let Some(locked_pointer) = locked_pointer.take() {
locked_pointer.destroy();
}
}
pub fn set_cursor_position(&self, surface_x: u32, surface_y: u32) {
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
let locked_pointer = locked_pointer.borrow_mut();
if let Some(locked_pointer) = locked_pointer.as_ref() {
locked_pointer.set_cursor_position_hint(surface_x.into(), surface_y.into());
}
}
pub fn drag_window(&self, window: &Window<WinitFrame>) {
// WlPointer::setart_interactive_move() expects the last serial of *any*
// pointer event (compare to set_cursor()).
window.start_interactive_move(&self.seat, self.latest_serial.get());
}
}
/// A pointer wrapper for easy releasing and managing pointers.
pub(super) struct Pointers {
/// A pointer itself.
pointer: ThemedPointer,
/// A relative pointer handler.
relative_pointer: Option<ZwpRelativePointerV1>,
/// Confined pointer.
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
/// Locked pointer.
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
}
impl Pointers {
pub(super) fn new(
seat: &Attached<WlSeat>,
theme_manager: &ThemeManager,
relative_pointer_manager: &Option<Attached<ZwpRelativePointerManagerV1>>,
pointer_constraints: &Option<Attached<ZwpPointerConstraintsV1>>,
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Self {
let confined_pointer = Rc::new(RefCell::new(None));
let locked_pointer = Rc::new(RefCell::new(None));
let pointer_data = Rc::new(RefCell::new(PointerData::new(
confined_pointer.clone(),
locked_pointer.clone(),
pointer_constraints.clone(),
modifiers_state,
)));
let pointer_seat = seat.detach();
let pointer = theme_manager.theme_pointer_with_impl(
seat,
move |event, pointer, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_pointer(
pointer,
event,
&pointer_data,
winit_state,
pointer_seat.clone(),
);
},
);
// Setup relative_pointer if it's available.
let relative_pointer = relative_pointer_manager
.as_ref()
.map(|relative_pointer_manager| {
init_relative_pointer(relative_pointer_manager, &*pointer)
});
Self {
pointer,
relative_pointer,
confined_pointer,
locked_pointer,
}
}
}
impl Drop for Pointers {
fn drop(&mut self) {
// Drop relative pointer.
if let Some(relative_pointer) = self.relative_pointer.take() {
relative_pointer.destroy();
}
// Drop confined pointer.
if let Some(confined_pointer) = self.confined_pointer.borrow_mut().take() {
confined_pointer.destroy();
}
// Drop lock ponter.
if let Some(locked_pointer) = self.locked_pointer.borrow_mut().take() {
locked_pointer.destroy();
}
// Drop the pointer itself in case it's possible.
if self.pointer.as_ref().version() >= 3 {
self.pointer.release();
}
}
}
pub(super) fn init_relative_pointer(
relative_pointer_manager: &ZwpRelativePointerManagerV1,
pointer: &WlPointer,
) -> ZwpRelativePointerV1 {
let relative_pointer = relative_pointer_manager.get_relative_pointer(pointer);
relative_pointer.quick_assign(move |_, event, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_relative_pointer(event, winit_state);
});
relative_pointer.detach()
}
pub(super) fn init_confined_pointer(
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
surface: &WlSurface,
pointer: &WlPointer,
) -> ZwpConfinedPointerV1 {
let confined_pointer =
pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent);
confined_pointer.quick_assign(move |_, _, _| {});
confined_pointer.detach()
}
pub(super) fn init_locked_pointer(
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
surface: &WlSurface,
pointer: &WlPointer,
) -> ZwpLockedPointerV1 {
let locked_pointer =
pointer_constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent);
locked_pointer.quick_assign(move |_, _, _| {});
locked_pointer.detach()
}

View File

@@ -1,107 +0,0 @@
//! Handling of IME events.
use sctk::reexports::client::Main;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::{
Event as TextInputEvent, ZwpTextInputV3,
};
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::event_loop::WinitState;
use super::{Preedit, TextInputHandler, TextInputInner};
#[inline]
pub(super) fn handle_text_input(
text_input: Main<ZwpTextInputV3>,
inner: &mut TextInputInner,
event: TextInputEvent,
winit_state: &mut WinitState,
) {
let event_sink = &mut winit_state.event_sink;
match event {
TextInputEvent::Enter { surface } => {
let window_id = wayland::make_wid(&surface);
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
inner.target_window_id = Some(window_id);
// Enable text input on that surface.
if window_handle.ime_allowed.get() {
text_input.enable();
text_input.commit();
event_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
}
// Notify a window we're currently over about text input handler.
let text_input_handler = TextInputHandler {
text_input: text_input.detach(),
};
window_handle.text_input_entered(text_input_handler);
}
TextInputEvent::Leave { surface } => {
// Always issue a disable.
text_input.disable();
text_input.commit();
let window_id = wayland::make_wid(&surface);
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
inner.target_window_id = None;
// Remove text input handler from the window we're leaving.
let text_input_handler = TextInputHandler {
text_input: text_input.detach(),
};
window_handle.text_input_left(text_input_handler);
event_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
}
TextInputEvent::PreeditString {
text,
cursor_begin,
cursor_end,
} => {
let cursor_begin = usize::try_from(cursor_begin).ok();
let cursor_end = usize::try_from(cursor_end).ok();
let text = text.unwrap_or_default();
inner.pending_preedit = Some(Preedit {
text,
cursor_begin,
cursor_end,
});
}
TextInputEvent::CommitString { text } => {
// Update currenly commited string and reset previous preedit.
inner.pending_preedit = None;
inner.pending_commit = Some(text.unwrap_or_default());
}
TextInputEvent::Done { .. } => {
let window_id = match inner.target_window_id {
Some(window_id) => window_id,
_ => return,
};
if let Some(text) = inner.pending_commit.take() {
event_sink.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
}
// Push preedit string we've got after latest commit.
if let Some(preedit) = inner.pending_preedit.take() {
let cursor_range = preedit
.cursor_begin
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
let event = Ime::Preedit(preedit.text, cursor_range);
event_sink.push_window_event(WindowEvent::Ime(event), window_id);
}
}
_ => (),
}
}

View File

@@ -1,87 +0,0 @@
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::ZwpTextInputV3;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::WindowId;
mod handlers;
/// A handler for text input that we're advertising for `WindowHandle`.
#[derive(Eq, PartialEq)]
pub struct TextInputHandler {
text_input: ZwpTextInputV3,
}
impl TextInputHandler {
#[inline]
pub fn set_ime_position(&self, x: i32, y: i32) {
self.text_input.set_cursor_rectangle(x, y, 0, 0);
self.text_input.commit();
}
#[inline]
pub fn set_input_allowed(&self, allowed: bool) {
if allowed {
self.text_input.enable();
} else {
self.text_input.disable();
}
self.text_input.commit();
}
}
/// A wrapper around text input to automatically destroy the object on `Drop`.
pub struct TextInput {
text_input: Attached<ZwpTextInputV3>,
}
impl TextInput {
pub fn new(seat: &Attached<WlSeat>, text_input_manager: &ZwpTextInputManagerV3) -> Self {
let text_input = text_input_manager.get_text_input(seat);
let mut text_input_inner = TextInputInner::new();
text_input.quick_assign(move |text_input, event, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_text_input(text_input, &mut text_input_inner, event, winit_state);
});
let text_input: Attached<ZwpTextInputV3> = text_input.into();
Self { text_input }
}
}
impl Drop for TextInput {
fn drop(&mut self) {
self.text_input.destroy();
}
}
struct TextInputInner {
/// Currently focused surface.
target_window_id: Option<WindowId>,
/// Pending commit event which will be dispatched on `text_input_v3::Done`.
pending_commit: Option<String>,
/// Pending preedit event which will be dispatched on `text_input_v3::Done`.
pending_preedit: Option<Preedit>,
}
struct Preedit {
text: String,
cursor_begin: Option<usize>,
cursor_end: Option<usize>,
}
impl TextInputInner {
fn new() -> Self {
Self {
target_window_id: None,
pending_commit: None,
pending_preedit: None,
}
}
}

View File

@@ -1,128 +0,0 @@
//! Various handlers for touch events.
use sctk::reexports::client::protocol::wl_touch::Event as TouchEvent;
use crate::dpi::LogicalPosition;
use crate::event::{TouchPhase, WindowEvent};
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::{self, DeviceId};
use super::{TouchInner, TouchPoint};
/// Handle WlTouch events.
#[inline]
pub(super) fn handle_touch(
event: TouchEvent,
inner: &mut TouchInner,
winit_state: &mut WinitState,
) {
let event_sink = &mut winit_state.event_sink;
match event {
TouchEvent::Down {
surface, id, x, y, ..
} => {
let window_id = wayland::make_wid(&surface);
if !winit_state.window_map.contains_key(&window_id) {
return;
}
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
let position = LogicalPosition::new(x, y);
event_sink.push_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Started,
location: position.to_physical(scale_factor),
force: None, // TODO
id: id as u64,
}),
window_id,
);
// For `TouchEvent::Up` we don't receive a position, so we're tracking active
// touch points. Update either a known touch id or register a new one.
if let Some(i) = inner.touch_points.iter().position(|p| p.id == id) {
inner.touch_points[i].position = position;
} else {
inner
.touch_points
.push(TouchPoint::new(surface, position, id));
}
}
TouchEvent::Up { id, .. } => {
let touch_point = match inner.touch_points.iter().find(|p| p.id == id) {
Some(touch_point) => touch_point,
None => return,
};
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
let location = touch_point.position.to_physical(scale_factor);
let window_id = wayland::make_wid(&touch_point.surface);
event_sink.push_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Ended,
location,
force: None, // TODO
id: id as u64,
}),
window_id,
);
}
TouchEvent::Motion { id, x, y, .. } => {
let touch_point = match inner.touch_points.iter_mut().find(|p| p.id == id) {
Some(touch_point) => touch_point,
None => return,
};
touch_point.position = LogicalPosition::new(x, y);
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
let location = touch_point.position.to_physical(scale_factor);
let window_id = wayland::make_wid(&touch_point.surface);
event_sink.push_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Moved,
location,
force: None, // TODO
id: id as u64,
}),
window_id,
);
}
TouchEvent::Frame => (),
TouchEvent::Cancel => {
for touch_point in inner.touch_points.drain(..) {
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
let location = touch_point.position.to_physical(scale_factor);
let window_id = wayland::make_wid(&touch_point.surface);
event_sink.push_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Cancelled,
location,
force: None, // TODO
id: touch_point.id as u64,
}),
window_id,
);
}
}
_ => (),
}
}

View File

@@ -1,78 +0,0 @@
//! Touch handling.
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::protocol::wl_touch::WlTouch;
use sctk::reexports::client::Attached;
use crate::dpi::LogicalPosition;
use crate::platform_impl::wayland::event_loop::WinitState;
mod handlers;
/// Wrapper around touch to handle release.
pub struct Touch {
/// Proxy to touch.
touch: WlTouch,
}
impl Touch {
pub fn new(seat: &Attached<WlSeat>) -> Self {
let touch = seat.get_touch();
let mut inner = TouchInner::new();
touch.quick_assign(move |_, event, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_touch(event, &mut inner, winit_state);
});
Self {
touch: touch.detach(),
}
}
}
impl Drop for Touch {
fn drop(&mut self) {
if self.touch.as_ref().version() >= 3 {
self.touch.release();
}
}
}
/// The data used by touch handlers.
pub(super) struct TouchInner {
/// Current touch points.
touch_points: Vec<TouchPoint>,
}
impl TouchInner {
fn new() -> Self {
Self {
touch_points: Vec::new(),
}
}
}
/// Location of touch press.
pub(super) struct TouchPoint {
/// A surface where the touch point is located.
surface: WlSurface,
/// Location of the touch point.
position: LogicalPosition<f64>,
/// Id.
id: i32,
}
impl TouchPoint {
pub fn new(surface: WlSurface, position: LogicalPosition<f64>, id: i32) -> Self {
Self {
surface,
position,
id,
}
}
}

View File

@@ -0,0 +1,114 @@
use std::sync::{Arc, Mutex};
use crate::event::{TouchPhase, WindowEvent};
use super::{event_loop::WindowEventsSink, window::WindowStore, DeviceId, WindowId};
use smithay_client_toolkit::reexports::client::protocol::{
wl_seat,
wl_touch::{Event as TouchEvent, WlTouch},
};
struct TouchPoint {
wid: WindowId,
location: (f64, f64),
id: i32,
}
pub(crate) fn implement_touch<T: 'static>(
seat: &wl_seat::WlSeat,
sink: Arc<Mutex<WindowEventsSink<T>>>,
store: Arc<Mutex<WindowStore>>,
) -> WlTouch {
let mut pending_ids = Vec::new();
seat.get_touch(|touch| {
touch.implement_closure(
move |evt, _| {
let mut sink = sink.lock().unwrap();
let store = store.lock().unwrap();
match evt {
TouchEvent::Down {
surface, id, x, y, ..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Started,
location: (x, y).into(),
force: None, // TODO
id: id as u64,
}),
wid,
);
pending_ids.push(TouchPoint {
wid,
location: (x, y),
id,
});
}
}
TouchEvent::Up { id, .. } => {
let idx = pending_ids.iter().position(|p| p.id == id);
if let Some(idx) = idx {
let pt = pending_ids.remove(idx);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Ended,
location: pt.location.into(),
force: None, // TODO
id: id as u64,
}),
pt.wid,
);
}
}
TouchEvent::Motion { id, x, y, .. } => {
let pt = pending_ids.iter_mut().find(|p| p.id == id);
if let Some(pt) = pt {
pt.location = (x, y);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Moved,
location: (x, y).into(),
force: None, // TODO
id: id as u64,
}),
pt.wid,
);
}
}
TouchEvent::Frame => (),
TouchEvent::Cancel => {
for pt in pending_ids.drain(..) {
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Cancelled,
location: pt.location.into(),
force: None, // TODO
id: pt.id as u64,
}),
pt.wid,
);
}
}
_ => unreachable!(),
}
},
(),
)
})
.unwrap()
}

View File

@@ -0,0 +1,472 @@
use raw_window_handle::unix::WaylandHandle;
use std::{
collections::VecDeque,
mem::replace,
sync::{Arc, Mutex, Weak},
};
use crate::{
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
monitor::MonitorHandle as RootMonitorHandle,
platform_impl::{
platform::wayland::event_loop::{available_monitors, primary_monitor},
MonitorHandle as PlatformMonitorHandle,
PlatformSpecificWindowBuilderAttributes as PlAttributes,
},
window::{CursorIcon, Fullscreen, WindowAttributes},
};
use smithay_client_toolkit::{
output::OutputMgr,
reexports::client::{
protocol::{wl_seat, wl_surface},
Display,
},
surface::{get_dpi_factor, get_outputs},
window::{ConceptFrame, Event as WEvent, State as WState, Theme, Window as SWindow},
};
use super::{event_loop::CursorManager, make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
pub struct Window {
surface: wl_surface::WlSurface,
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
cursor_manager: Arc<Mutex<CursorManager>>,
outputs: OutputMgr, // Access to info for all monitors
size: Arc<Mutex<(u32, u32)>>,
kill_switch: (Arc<Mutex<bool>>, Arc<Mutex<bool>>),
display: Arc<Display>,
need_frame_refresh: Arc<Mutex<bool>>,
need_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
cursor_grab_changed: Arc<Mutex<Option<bool>>>, // Update grab state
}
impl Window {
pub fn new<T>(
evlp: &EventLoopWindowTarget<T>,
attributes: WindowAttributes,
pl_attribs: PlAttributes,
) -> Result<Window, RootOsError> {
let (width, height) = attributes.inner_size.map(Into::into).unwrap_or((800, 600));
// Create the window
let size = Arc::new(Mutex::new((width, height)));
let fullscreen = Arc::new(Mutex::new(false));
let window_store = evlp.store.clone();
let cursor_manager = evlp.cursor_manager.clone();
let surface = evlp.env.create_surface(move |dpi, surface| {
window_store.lock().unwrap().dpi_change(&surface, dpi);
surface.set_buffer_scale(dpi);
});
let window_store = evlp.store.clone();
let my_surface = surface.clone();
let mut frame = SWindow::<ConceptFrame>::init_from_env(
&evlp.env,
surface.clone(),
(width, height),
move |event| match event {
WEvent::Configure { new_size, states } => {
let mut store = window_store.lock().unwrap();
let is_fullscreen = states.contains(&WState::Fullscreen);
for window in &mut store.windows {
if window.surface.as_ref().equals(&my_surface.as_ref()) {
window.newsize = new_size;
*(window.need_refresh.lock().unwrap()) = true;
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
*(window.need_frame_refresh.lock().unwrap()) = true;
return;
}
}
}
WEvent::Refresh => {
let store = window_store.lock().unwrap();
for window in &store.windows {
if window.surface.as_ref().equals(&my_surface.as_ref()) {
*(window.need_frame_refresh.lock().unwrap()) = true;
return;
}
}
}
WEvent::Close => {
let mut store = window_store.lock().unwrap();
for window in &mut store.windows {
if window.surface.as_ref().equals(&my_surface.as_ref()) {
window.closed = true;
return;
}
}
}
},
)
.unwrap();
if let Some(app_id) = pl_attribs.app_id {
frame.set_app_id(app_id);
}
for &(_, ref seat) in evlp.seats.lock().unwrap().iter() {
frame.new_seat(seat);
}
// Check for fullscreen requirements
match attributes.fullscreen {
Some(Fullscreen::Exclusive(_)) => {
panic!("Wayland doesn't support exclusive fullscreen")
}
Some(Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
})) => frame.set_fullscreen(Some(&monitor_id.proxy)),
Some(Fullscreen::Borderless(_)) => unreachable!(),
None => {
if attributes.maximized {
frame.set_maximized();
}
}
}
frame.set_resizable(attributes.resizable);
// set decorations
frame.set_decorate(attributes.decorations);
// set title
frame.set_title(attributes.title);
// min-max dimensions
frame.set_min_size(attributes.min_inner_size.map(Into::into));
frame.set_max_size(attributes.max_inner_size.map(Into::into));
let kill_switch = Arc::new(Mutex::new(false));
let need_frame_refresh = Arc::new(Mutex::new(true));
let frame = Arc::new(Mutex::new(frame));
let need_refresh = Arc::new(Mutex::new(true));
let cursor_grab_changed = Arc::new(Mutex::new(None));
evlp.store.lock().unwrap().windows.push(InternalWindow {
closed: false,
newsize: None,
size: size.clone(),
need_refresh: need_refresh.clone(),
fullscreen: fullscreen.clone(),
cursor_grab_changed: cursor_grab_changed.clone(),
need_frame_refresh: need_frame_refresh.clone(),
surface: surface.clone(),
kill_switch: kill_switch.clone(),
frame: Arc::downgrade(&frame),
current_dpi: 1,
new_dpi: None,
});
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
Ok(Window {
display: evlp.display.clone(),
surface,
frame,
outputs: evlp.env.outputs.clone(),
size,
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
need_frame_refresh,
need_refresh,
cursor_manager,
fullscreen,
cursor_grab_changed,
})
}
#[inline]
pub fn id(&self) -> WindowId {
make_wid(&self.surface)
}
pub fn set_title(&self, title: &str) {
self.frame.lock().unwrap().set_title(title.into());
}
pub fn set_visible(&self, _visible: bool) {
// TODO
}
#[inline]
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn set_outer_position(&self, _pos: LogicalPosition) {
// Not possible with wayland
}
pub fn inner_size(&self) -> LogicalSize {
self.size.lock().unwrap().clone().into()
}
pub fn request_redraw(&self) {
*self.need_refresh.lock().unwrap() = true;
}
#[inline]
pub fn outer_size(&self) -> LogicalSize {
let (w, h) = self.size.lock().unwrap().clone();
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
(w, h).into()
}
#[inline]
// NOTE: This will only resize the borders, the contents must be updated by the user
pub fn set_inner_size(&self, size: LogicalSize) {
let (w, h) = size.into();
self.frame.lock().unwrap().resize(w, h);
*(self.size.lock().unwrap()) = (w, h);
}
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<LogicalSize>) {
self.frame
.lock()
.unwrap()
.set_min_size(dimensions.map(Into::into));
}
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<LogicalSize>) {
self.frame
.lock()
.unwrap()
.set_max_size(dimensions.map(Into::into));
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
self.frame.lock().unwrap().set_resizable(resizable);
}
#[inline]
pub fn hidpi_factor(&self) -> i32 {
get_dpi_factor(&self.surface)
}
pub fn set_decorations(&self, decorate: bool) {
self.frame.lock().unwrap().set_decorate(decorate);
*(self.need_frame_refresh.lock().unwrap()) = true;
}
pub fn set_maximized(&self, maximized: bool) {
if maximized {
self.frame.lock().unwrap().set_maximized();
} else {
self.frame.lock().unwrap().unset_maximized();
}
}
pub fn fullscreen(&self) -> Option<Fullscreen> {
if *(self.fullscreen.lock().unwrap()) {
Some(Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(self.current_monitor()),
}))
} else {
None
}
}
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
match fullscreen {
Some(Fullscreen::Exclusive(_)) => {
panic!("Wayland doesn't support exclusive fullscreen")
}
Some(Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
})) => {
self.frame
.lock()
.unwrap()
.set_fullscreen(Some(&monitor_id.proxy));
}
Some(Fullscreen::Borderless(_)) => unreachable!(),
None => self.frame.lock().unwrap().unset_fullscreen(),
}
}
pub fn set_theme<T: Theme>(&self, theme: T) {
self.frame.lock().unwrap().set_theme(theme)
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let mut cursor_manager = self.cursor_manager.lock().unwrap();
cursor_manager.set_cursor_icon(cursor);
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
let mut cursor_manager = self.cursor_manager.lock().unwrap();
cursor_manager.set_cursor_visible(visible);
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
*self.cursor_grab_changed.lock().unwrap() = Some(grab);
Ok(())
}
#[inline]
pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn display(&self) -> &Display {
&*self.display
}
pub fn surface(&self) -> &wl_surface::WlSurface {
&self.surface
}
pub fn current_monitor(&self) -> MonitorHandle {
let output = get_outputs(&self.surface).last().unwrap().clone();
MonitorHandle {
proxy: output,
mgr: self.outputs.clone(),
}
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
available_monitors(&self.outputs)
}
pub fn primary_monitor(&self) -> MonitorHandle {
primary_monitor(&self.outputs)
}
pub fn raw_window_handle(&self) -> WaylandHandle {
WaylandHandle {
surface: self.surface().as_ref().c_ptr() as *mut _,
display: self.display().as_ref().c_ptr() as *mut _,
..WaylandHandle::empty()
}
}
}
impl Drop for Window {
fn drop(&mut self) {
*(self.kill_switch.0.lock().unwrap()) = true;
*(self.kill_switch.1.lock().unwrap()) = true;
}
}
/*
* Internal store for windows
*/
struct InternalWindow {
surface: wl_surface::WlSurface,
newsize: Option<(u32, u32)>,
size: Arc<Mutex<(u32, u32)>>,
need_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
need_frame_refresh: Arc<Mutex<bool>>,
cursor_grab_changed: Arc<Mutex<Option<bool>>>,
closed: bool,
kill_switch: Arc<Mutex<bool>>,
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
current_dpi: i32,
new_dpi: Option<i32>,
}
pub struct WindowStore {
windows: Vec<InternalWindow>,
}
impl WindowStore {
pub fn new() -> WindowStore {
WindowStore {
windows: Vec::new(),
}
}
pub fn find_wid(&self, surface: &wl_surface::WlSurface) -> Option<WindowId> {
for window in &self.windows {
if surface.as_ref().equals(&window.surface.as_ref()) {
return Some(make_wid(surface));
}
}
None
}
pub fn cleanup(&mut self) -> Vec<WindowId> {
let mut pruned = Vec::new();
self.windows.retain(|w| {
if *w.kill_switch.lock().unwrap() {
// window is dead, cleanup
pruned.push(make_wid(&w.surface));
w.surface.destroy();
false
} else {
true
}
});
pruned
}
pub fn new_seat(&self, seat: &wl_seat::WlSeat) {
for window in &self.windows {
if let Some(w) = window.frame.upgrade() {
w.lock().unwrap().new_seat(seat);
}
}
}
fn dpi_change(&mut self, surface: &wl_surface::WlSurface, new: i32) {
for window in &mut self.windows {
if surface.as_ref().equals(&window.surface.as_ref()) {
window.new_dpi = Some(new);
}
}
}
pub fn for_each<F>(&mut self, mut f: F)
where
F: FnMut(
Option<(u32, u32)>,
&mut (u32, u32),
Option<i32>,
bool,
bool,
bool,
Option<bool>,
&wl_surface::WlSurface,
WindowId,
Option<&mut SWindow<ConceptFrame>>,
),
{
for window in &mut self.windows {
let opt_arc = window.frame.upgrade();
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
f(
window.newsize.take(),
&mut *(window.size.lock().unwrap()),
window.new_dpi,
replace(&mut *window.need_refresh.lock().unwrap(), false),
replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
window.closed,
window.cursor_grab_changed.lock().unwrap().take(),
&window.surface,
make_wid(&window.surface),
opt_mutex_lock.as_mut().map(|m| &mut **m),
);
if let Some(dpi) = window.new_dpi.take() {
window.current_dpi = dpi;
}
// avoid re-spamming the event
window.closed = false;
}
}
}

View File

@@ -1,618 +0,0 @@
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Display;
use sctk::reexports::calloop;
use raw_window_handle::{
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
};
use sctk::window::Decorations;
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform_impl::{
MonitorHandle as PlatformMonitorHandle, OsError,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
};
use crate::window::{
CursorGrabMode, CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes,
};
use super::env::WindowingFeatures;
use super::event_loop::WinitState;
use super::output::{MonitorHandle, OutputManagerHandle};
use super::{EventLoopWindowTarget, WindowId};
pub mod shim;
use shim::{WindowHandle, WindowRequest, WindowUpdate};
#[cfg(feature = "sctk-adwaita")]
pub type WinitFrame = sctk_adwaita::AdwaitaFrame;
#[cfg(not(feature = "sctk-adwaita"))]
pub type WinitFrame = sctk::window::FallbackFrame;
#[cfg(feature = "sctk-adwaita")]
const WAYLAND_CSD_THEME_ENV_VAR: &str = "WINIT_WAYLAND_CSD_THEME";
pub struct Window {
/// Window id.
window_id: WindowId,
/// The Wayland display.
display: Display,
/// The underlying wl_surface.
surface: WlSurface,
/// The current window size.
size: Arc<Mutex<LogicalSize<u32>>>,
/// A handle to output manager.
output_manager_handle: OutputManagerHandle,
/// Event loop proxy to wake it up.
event_loop_awakener: calloop::ping::Ping,
/// Fullscreen state.
fullscreen: Arc<AtomicBool>,
/// Maximized state.
maximized: Arc<AtomicBool>,
/// Available windowing features.
windowing_features: WindowingFeatures,
/// Requests that SCTK window should perform.
window_requests: Arc<Mutex<Vec<WindowRequest>>>,
/// Whether the window is resizeable.
resizeable: AtomicBool,
/// Whether the window is decorated.
decorated: AtomicBool,
/// Grabbing mode.
cursor_grab_mode: Mutex<CursorGrabMode>,
}
impl Window {
pub(crate) fn new<T>(
event_loop_window_target: &EventLoopWindowTarget<T>,
attributes: WindowAttributes,
platform_attributes: PlatformAttributes,
) -> Result<Self, RootOsError> {
let surface = event_loop_window_target
.env
.create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
// Get the window that received the event.
let window_id = super::make_wid(&surface);
let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap();
// Set pending scale factor.
window_update.scale_factor = Some(scale);
window_update.redraw_requested = true;
surface.set_buffer_scale(scale);
})
.detach();
let scale_factor = sctk::get_surface_scale_factor(&surface);
let window_id = super::make_wid(&surface);
let maximized = Arc::new(AtomicBool::new(false));
let maximized_clone = maximized.clone();
let fullscreen = Arc::new(AtomicBool::new(false));
let fullscreen_clone = fullscreen.clone();
let (width, height) = attributes
.inner_size
.map(|size| size.to_logical::<f64>(scale_factor as f64).into())
.unwrap_or((800, 600));
let theme_manager = event_loop_window_target.theme_manager.clone();
let mut window = event_loop_window_target
.env
.create_window::<WinitFrame, _>(
surface.clone(),
Some(theme_manager),
(width, height),
move |event, mut dispatch_data| {
use sctk::window::{Event, State};
let winit_state = dispatch_data.get::<WinitState>().unwrap();
let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap();
match event {
Event::Refresh => {
window_update.refresh_frame = true;
}
Event::Configure { new_size, states } => {
let is_maximized = states.contains(&State::Maximized);
maximized_clone.store(is_maximized, Ordering::Relaxed);
let is_fullscreen = states.contains(&State::Fullscreen);
fullscreen_clone.store(is_fullscreen, Ordering::Relaxed);
window_update.refresh_frame = true;
window_update.redraw_requested = true;
if let Some((w, h)) = new_size {
window_update.size = Some(LogicalSize::new(w, h));
}
}
Event::Close => {
window_update.close_window = true;
}
}
},
)
.map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?;
// Set CSD frame config
#[cfg(feature = "sctk-adwaita")]
{
let theme = platform_attributes.csd_theme.unwrap_or_else(|| {
let env = std::env::var(WAYLAND_CSD_THEME_ENV_VAR).unwrap_or_default();
match env.to_lowercase().as_str() {
"dark" => Theme::Dark,
_ => Theme::Light,
}
});
window.set_frame_config(theme.into());
}
// Set decorations.
if attributes.decorations {
window.set_decorate(Decorations::FollowServer);
} else {
window.set_decorate(Decorations::None);
}
// Min dimensions.
let min_size = attributes
.min_inner_size
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
window.set_min_size(min_size);
// Max dimensions.
let max_size = attributes
.max_inner_size
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
window.set_max_size(max_size);
// Set Wayland specific window attributes.
if let Some(name) = platform_attributes.name {
window.set_app_id(name.general);
}
// Set common window attributes.
//
// We set resizable after other attributes, since it touches min and max size under
// the hood.
window.set_resizable(attributes.resizable);
window.set_title(attributes.title);
// Set fullscreen/maximized if so was requested.
match attributes.fullscreen {
Some(Fullscreen::Exclusive(_)) => {
warn!("`Fullscreen::Exclusive` is ignored on Wayland")
}
Some(Fullscreen::Borderless(monitor)) => {
let monitor =
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
#[cfg(feature = "x11")]
PlatformMonitorHandle::X(_) => None,
});
window.set_fullscreen(monitor.as_ref());
}
None => {
if attributes.maximized {
window.set_maximized();
}
}
}
// Without this commit here at least on kwin 5.23.3 the initial configure
// will have a size (1,1), the second configure including the decoration
// mode will have the min_size as its size. With this commit the initial
// configure will have no size, the application will draw it's content
// with the initial size and everything works as expected afterwards.
//
// The window commit must be after setting on top level properties, but right before any
// buffer attachments commits.
window.surface().commit();
let size = Arc::new(Mutex::new(LogicalSize::new(width, height)));
// We should trigger redraw and commit the surface for the newly created window.
let mut window_update = WindowUpdate::new();
window_update.refresh_frame = true;
window_update.redraw_requested = true;
let window_id = super::make_wid(&surface);
let window_requests = Arc::new(Mutex::new(Vec::with_capacity(64)));
// Create a handle that performs all the requests on underlying sctk a window.
let window_handle = WindowHandle::new(
&event_loop_window_target.env,
window,
size.clone(),
window_requests.clone(),
);
// Set resizable state, so we can determine how to handle `Window::set_inner_size`.
window_handle.is_resizable.set(attributes.resizable);
let mut winit_state = event_loop_window_target.state.borrow_mut();
winit_state.window_map.insert(window_id, window_handle);
// On Wayland window doesn't have Focus by default and it'll get it later on. So be
// explicit here.
winit_state
.event_sink
.push_window_event(crate::event::WindowEvent::Focused(false), window_id);
winit_state
.window_updates
.insert(window_id, WindowUpdate::new());
let windowing_features = event_loop_window_target.windowing_features;
// To make our window usable for drawing right away we must `ack` a `configure`
// from the server, the acking part here is done by SCTK window frame, so we just
// need to sync with server so it'll be done automatically for us.
{
let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut();
let event_queue = wayland_source.queue();
let _ = event_queue.sync_roundtrip(&mut *winit_state, |_, _, _| unreachable!());
}
// We all praise GNOME for these 3 lines of pure magic. If we don't do that,
// GNOME will shrink our window a bit for the size of the decorations. I guess it
// happens because we haven't committed them with buffers to the server.
let window_handle = winit_state.window_map.get_mut(&window_id).unwrap();
window_handle.window.refresh();
let output_manager_handle = event_loop_window_target.output_manager.handle();
let window = Self {
window_id,
surface,
display: event_loop_window_target.display.clone(),
output_manager_handle,
size,
window_requests,
event_loop_awakener: event_loop_window_target.event_loop_awakener.clone(),
fullscreen,
maximized,
windowing_features,
resizeable: AtomicBool::new(attributes.resizable),
decorated: AtomicBool::new(attributes.decorations),
cursor_grab_mode: Mutex::new(CursorGrabMode::None),
};
Ok(window)
}
}
impl Window {
#[inline]
pub fn id(&self) -> WindowId {
self.window_id
}
#[inline]
pub fn set_title(&self, title: &str) {
self.send_request(WindowRequest::Title(title.to_owned()));
}
#[inline]
pub fn set_visible(&self, _visible: bool) {
// Not possible on Wayland.
}
#[inline]
pub fn is_visible(&self) -> Option<bool> {
None
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn set_outer_position(&self, _: Position) {
// Not possible on Wayland.
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
self.size
.lock()
.unwrap()
.to_physical(self.scale_factor() as f64)
}
#[inline]
pub fn request_redraw(&self) {
self.send_request(WindowRequest::Redraw);
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
self.size
.lock()
.unwrap()
.to_physical(self.scale_factor() as f64)
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
let scale_factor = self.scale_factor() as f64;
let size = size.to_logical::<u32>(scale_factor);
*self.size.lock().unwrap() = size;
self.send_request(WindowRequest::FrameSize(size));
}
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
let scale_factor = self.scale_factor() as f64;
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
self.send_request(WindowRequest::MinSize(size));
}
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
let scale_factor = self.scale_factor() as f64;
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
self.send_request(WindowRequest::MaxSize(size));
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
self.resizeable.store(resizable, Ordering::Relaxed);
self.send_request(WindowRequest::Resizeable(resizable));
}
#[inline]
pub fn is_resizable(&self) -> bool {
self.resizeable.load(Ordering::Relaxed)
}
#[inline]
pub fn scale_factor(&self) -> u32 {
// The scale factor from `get_surface_scale_factor` is always greater than zero, so
// u32 conversion is safe.
sctk::get_surface_scale_factor(&self.surface) as u32
}
#[inline]
pub fn set_decorations(&self, decorate: bool) {
self.decorated.store(decorate, Ordering::Relaxed);
self.send_request(WindowRequest::Decorate(decorate));
}
#[inline]
pub fn is_decorated(&self) -> bool {
self.decorated.load(Ordering::Relaxed)
}
#[inline]
pub fn set_csd_theme(&self, theme: Theme) {
self.send_request(WindowRequest::CsdThemeVariant(theme));
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
// You can't unminimize the window on Wayland.
if !minimized {
return;
}
self.send_request(WindowRequest::Minimize);
}
#[inline]
pub fn is_maximized(&self) -> bool {
self.maximized.load(Ordering::Relaxed)
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
self.send_request(WindowRequest::Maximize(maximized));
}
#[inline]
pub fn fullscreen(&self) -> Option<Fullscreen> {
if self.fullscreen.load(Ordering::Relaxed) {
let current_monitor = self.current_monitor().map(|monitor| RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(monitor),
});
Some(Fullscreen::Borderless(current_monitor))
} else {
None
}
}
#[inline]
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
let fullscreen_request = match fullscreen {
Some(Fullscreen::Exclusive(_)) => {
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
return;
}
Some(Fullscreen::Borderless(monitor)) => {
let monitor =
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
#[cfg(feature = "x11")]
PlatformMonitorHandle::X(_) => None,
});
WindowRequest::Fullscreen(monitor)
}
None => WindowRequest::UnsetFullscreen,
};
self.send_request(fullscreen_request);
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.send_request(WindowRequest::NewCursorIcon(cursor));
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
self.send_request(WindowRequest::ShowCursor(visible));
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
if !self.windowing_features.pointer_constraints() {
if mode == CursorGrabMode::None {
return Ok(());
}
return Err(ExternalError::NotSupported(NotSupportedError::new()));
}
*self.cursor_grab_mode.lock().unwrap() = mode;
self.send_request(WindowRequest::SetCursorGrabMode(mode));
Ok(())
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
if !self.windowing_features.xdg_activation() {
warn!("`request_user_attention` isn't supported");
return;
}
self.send_request(WindowRequest::Attention(request_type));
}
#[inline]
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
// Positon can be set only for locked cursor.
if *self.cursor_grab_mode.lock().unwrap() != CursorGrabMode::Locked {
return Err(ExternalError::Os(os_error!(OsError::WaylandMisc(
"cursor position can be set only for locked cursor."
))));
}
let scale_factor = self.scale_factor() as f64;
let position = position.to_logical(scale_factor);
self.send_request(WindowRequest::SetLockedCursorPosition(position));
Ok(())
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
self.send_request(WindowRequest::DragWindow);
Ok(())
}
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
self.send_request(WindowRequest::PassthroughMouseInput(!hittest));
Ok(())
}
#[inline]
pub fn set_ime_position(&self, position: Position) {
let scale_factor = self.scale_factor() as f64;
let position = position.to_logical(scale_factor);
self.send_request(WindowRequest::ImePosition(position));
}
#[inline]
pub fn set_ime_allowed(&self, allowed: bool) {
self.send_request(WindowRequest::AllowIme(allowed));
}
#[inline]
pub fn display(&self) -> &Display {
&self.display
}
#[inline]
pub fn surface(&self) -> &WlSurface {
&self.surface
}
#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
let output = sctk::get_surface_outputs(&self.surface).last()?.clone();
Some(MonitorHandle::new(output))
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
self.output_manager_handle.available_outputs()
}
#[inline]
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
None
}
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = WaylandWindowHandle::empty();
window_handle.surface = self.surface.as_ref().c_ptr() as *mut _;
RawWindowHandle::Wayland(window_handle)
}
#[inline]
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = WaylandDisplayHandle::empty();
display_handle.display = self.display.get_display_ptr() as *mut _;
RawDisplayHandle::Wayland(display_handle)
}
#[inline]
fn send_request(&self, request: WindowRequest) {
self.window_requests.lock().unwrap().push(request);
self.event_loop_awakener.ping();
}
}
impl Drop for Window {
fn drop(&mut self) {
self.send_request(WindowRequest::Close);
}
}
#[cfg(feature = "sctk-adwaita")]
impl From<Theme> for sctk_adwaita::FrameConfig {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => sctk_adwaita::FrameConfig::light(),
Theme::Dark => sctk_adwaita::FrameConfig::dark(),
}
}
}

View File

@@ -1,577 +0,0 @@
use std::cell::Cell;
use std::mem::ManuallyDrop;
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::environment::Environment;
use sctk::window::{Decorations, Window};
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::env::WinitEnv;
use crate::platform_impl::wayland::event_loop::{EventSink, WinitState};
use crate::platform_impl::wayland::seat::pointer::WinitPointer;
use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
use crate::platform_impl::wayland::WindowId;
use crate::window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType};
use super::WinitFrame;
/// A request to SCTK window from Winit window.
#[derive(Debug, Clone)]
pub enum WindowRequest {
/// Set fullscreen.
///
/// Passing `None` will set it on the current monitor.
Fullscreen(Option<WlOutput>),
/// Unset fullscreen.
UnsetFullscreen,
/// Show cursor for the certain window or not.
ShowCursor(bool),
/// Change the cursor icon.
NewCursorIcon(CursorIcon),
/// Change cursor grabbing mode.
SetCursorGrabMode(CursorGrabMode),
/// Set cursor position.
SetLockedCursorPosition(LogicalPosition<u32>),
/// Drag window.
DragWindow,
/// Maximize the window.
Maximize(bool),
/// Minimize the window.
Minimize,
/// Request decorations change.
Decorate(bool),
/// Request decorations change.
CsdThemeVariant(Theme),
/// Make the window resizeable.
Resizeable(bool),
/// Set the title for window.
Title(String),
/// Min size.
MinSize(Option<LogicalSize<u32>>),
/// Max size.
MaxSize(Option<LogicalSize<u32>>),
/// New frame size.
FrameSize(LogicalSize<u32>),
/// Set IME window position.
ImePosition(LogicalPosition<u32>),
/// Enable IME on the given window.
AllowIme(bool),
/// Request Attention.
///
/// `None` unsets the attention request.
Attention(Option<UserAttentionType>),
/// Passthrough mouse input to underlying windows.
PassthroughMouseInput(bool),
/// Redraw was requested.
Redraw,
/// Window should be closed.
Close,
}
/// Pending update to a window from SCTK window.
#[derive(Debug, Clone, Copy)]
pub struct WindowUpdate {
/// New window size.
pub size: Option<LogicalSize<u32>>,
/// New scale factor.
pub scale_factor: Option<i32>,
/// Whether `redraw` was requested.
pub redraw_requested: bool,
/// Wether the frame should be refreshed.
pub refresh_frame: bool,
/// Close the window.
pub close_window: bool,
}
impl WindowUpdate {
pub fn new() -> Self {
Self {
size: None,
scale_factor: None,
redraw_requested: false,
refresh_frame: false,
close_window: false,
}
}
pub fn take(&mut self) -> Self {
let size = self.size.take();
let scale_factor = self.scale_factor.take();
let redraw_requested = self.redraw_requested;
self.redraw_requested = false;
let refresh_frame = self.refresh_frame;
self.refresh_frame = false;
let close_window = self.close_window;
self.close_window = false;
Self {
size,
scale_factor,
redraw_requested,
refresh_frame,
close_window,
}
}
}
/// A handle to perform operations on SCTK window
/// and react to events.
pub struct WindowHandle {
/// An actual window.
pub window: ManuallyDrop<Window<WinitFrame>>,
/// The current size of the window.
pub size: Arc<Mutex<LogicalSize<u32>>>,
/// A pending requests to SCTK window.
pub pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
/// Current cursor icon.
pub cursor_icon: Cell<CursorIcon>,
/// Whether the window is resizable.
pub is_resizable: Cell<bool>,
/// Allow IME events for that window.
pub ime_allowed: Cell<bool>,
/// Visible cursor or not.
cursor_visible: Cell<bool>,
/// Cursor confined to the surface.
cursor_grab_mode: Cell<CursorGrabMode>,
/// Pointers over the current surface.
pointers: Vec<WinitPointer>,
/// Text inputs on the current surface.
text_inputs: Vec<TextInputHandler>,
/// XdgActivation object.
xdg_activation: Option<Attached<XdgActivationV1>>,
/// Indicator whether user attention is requested.
attention_requested: Cell<bool>,
/// Compositor
compositor: Attached<WlCompositor>,
}
impl WindowHandle {
pub fn new(
env: &Environment<WinitEnv>,
window: Window<WinitFrame>,
size: Arc<Mutex<LogicalSize<u32>>>,
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
) -> Self {
let xdg_activation = env.get_global::<XdgActivationV1>();
// Unwrap is safe, since we can't create window without compositor anyway and won't be
// here.
let compositor = env.get_global::<WlCompositor>().unwrap();
Self {
window: ManuallyDrop::new(window),
size,
pending_window_requests,
cursor_icon: Cell::new(CursorIcon::Default),
is_resizable: Cell::new(true),
cursor_grab_mode: Cell::new(CursorGrabMode::None),
cursor_visible: Cell::new(true),
pointers: Vec::new(),
text_inputs: Vec::new(),
xdg_activation,
attention_requested: Cell::new(false),
compositor,
ime_allowed: Cell::new(false),
}
}
pub fn set_cursor_grab(&self, mode: CursorGrabMode) {
// The new requested state matches the current confine status, return.
let old_mode = self.cursor_grab_mode.replace(mode);
if old_mode == mode {
return;
}
// Clear old pointer data.
match old_mode {
CursorGrabMode::None => (),
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.unconfine()),
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.unlock()),
}
let surface = self.window.surface();
match mode {
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.lock(surface)),
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.confine(surface)),
CursorGrabMode::None => {
// Current lock/confine was already removed.
}
}
}
pub fn set_locked_cursor_position(&self, position: LogicalPosition<u32>) {
// XXX the cursor locking is ensured inside `Window`.
self.pointers
.iter()
.for_each(|p| p.set_cursor_position(position.x, position.y));
}
pub fn set_user_attention(&self, request_type: Option<UserAttentionType>) {
let xdg_activation = match self.xdg_activation.as_ref() {
None => return,
Some(xdg_activation) => xdg_activation,
};
// Urgency is only removed by the compositor and there's no need to raise urgency when it
// was already raised.
if request_type.is_none() || self.attention_requested.get() {
return;
}
let xdg_activation_token = xdg_activation.get_activation_token();
let surface = self.window.surface();
let window_id = wayland::make_wid(surface);
let xdg_activation = xdg_activation.clone();
xdg_activation_token.quick_assign(move |xdg_token, event, mut dispatch_data| {
let token = match event {
xdg_activation_token_v1::Event::Done { token } => token,
_ => return,
};
let winit_state = dispatch_data.get::<WinitState>().unwrap();
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
let surface = window_handle.window.surface();
xdg_activation.activate(token, surface);
// Mark that attention request was done and drop the token.
window_handle.attention_requested.replace(false);
xdg_token.destroy();
});
xdg_activation_token.set_surface(surface);
xdg_activation_token.commit();
self.attention_requested.replace(true);
}
/// Pointer appeared over the window.
pub fn pointer_entered(&mut self, pointer: WinitPointer) {
let position = self.pointers.iter().position(|p| *p == pointer);
if position.is_none() {
let surface = self.window.surface();
match self.cursor_grab_mode.get() {
CursorGrabMode::None => (),
CursorGrabMode::Locked => pointer.lock(surface),
CursorGrabMode::Confined => pointer.confine(surface),
}
self.pointers.push(pointer);
}
// Apply the current cursor style.
self.set_cursor_visible(self.cursor_visible.get());
}
/// Pointer left the window.
pub fn pointer_left(&mut self, pointer: WinitPointer) {
let position = self.pointers.iter().position(|p| *p == pointer);
if let Some(position) = position {
let pointer = self.pointers.remove(position);
// Drop the grabbing mode.
match self.cursor_grab_mode.get() {
CursorGrabMode::None => (),
CursorGrabMode::Locked => pointer.unlock(),
CursorGrabMode::Confined => pointer.unconfine(),
}
}
}
pub fn text_input_entered(&mut self, text_input: TextInputHandler) {
if !self.text_inputs.iter().any(|t| *t == text_input) {
self.text_inputs.push(text_input);
}
}
pub fn text_input_left(&mut self, text_input: TextInputHandler) {
if let Some(position) = self.text_inputs.iter().position(|t| *t == text_input) {
self.text_inputs.remove(position);
}
}
pub fn set_ime_position(&self, position: LogicalPosition<u32>) {
// 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
// which seat we're setting IME position.
let (x, y) = (position.x as i32, position.y as i32);
for text_input in self.text_inputs.iter() {
text_input.set_ime_position(x, y);
}
}
pub fn passthrough_mouse_input(&self, passthrough_mouse_input: bool) {
if passthrough_mouse_input {
let region = self.compositor.create_region();
region.add(0, 0, 0, 0);
self.window
.surface()
.set_input_region(Some(&region.detach()));
region.destroy();
} else {
// Using `None` results in the entire window being clickable.
self.window.surface().set_input_region(None);
}
}
pub fn set_ime_allowed(&self, allowed: bool, event_sink: &mut EventSink) {
if self.ime_allowed.get() == allowed {
return;
}
self.ime_allowed.replace(allowed);
let window_id = wayland::make_wid(self.window.surface());
for text_input in self.text_inputs.iter() {
text_input.set_input_allowed(allowed);
}
let event = if allowed {
WindowEvent::Ime(Ime::Enabled)
} else {
WindowEvent::Ime(Ime::Disabled)
};
event_sink.push_window_event(event, window_id);
}
pub fn set_cursor_visible(&self, visible: bool) {
self.cursor_visible.replace(visible);
let cursor_icon = match visible {
true => Some(self.cursor_icon.get()),
false => None,
};
for pointer in self.pointers.iter() {
pointer.set_cursor(cursor_icon)
}
}
pub fn set_cursor_icon(&self, cursor_icon: CursorIcon) {
self.cursor_icon.replace(cursor_icon);
if !self.cursor_visible.get() {
return;
}
for pointer in self.pointers.iter() {
pointer.set_cursor(Some(cursor_icon));
}
}
pub fn drag_window(&self) {
for pointer in self.pointers.iter() {
pointer.drag_window(&self.window);
}
}
}
#[inline]
pub fn handle_window_requests(winit_state: &mut WinitState) {
let window_map = &mut winit_state.window_map;
let window_updates = &mut winit_state.window_updates;
let mut windows_to_close: Vec<WindowId> = Vec::new();
// Process the rest of the events.
for (window_id, window_handle) in window_map.iter_mut() {
let mut requests = window_handle.pending_window_requests.lock().unwrap();
let requests = requests.drain(..);
for request in requests {
match request {
WindowRequest::Fullscreen(fullscreen) => {
window_handle.window.set_fullscreen(fullscreen.as_ref());
}
WindowRequest::UnsetFullscreen => {
window_handle.window.unset_fullscreen();
}
WindowRequest::ShowCursor(show_cursor) => {
window_handle.set_cursor_visible(show_cursor);
}
WindowRequest::NewCursorIcon(cursor_icon) => {
window_handle.set_cursor_icon(cursor_icon);
}
WindowRequest::ImePosition(position) => {
window_handle.set_ime_position(position);
}
WindowRequest::AllowIme(allow) => {
let event_sink = &mut winit_state.event_sink;
window_handle.set_ime_allowed(allow, event_sink);
}
WindowRequest::SetCursorGrabMode(mode) => {
window_handle.set_cursor_grab(mode);
}
WindowRequest::SetLockedCursorPosition(position) => {
window_handle.set_locked_cursor_position(position);
}
WindowRequest::DragWindow => {
window_handle.drag_window();
}
WindowRequest::Maximize(maximize) => {
if maximize {
window_handle.window.set_maximized();
} else {
window_handle.window.unset_maximized();
}
}
WindowRequest::Minimize => {
window_handle.window.set_minimized();
}
WindowRequest::Decorate(decorate) => {
let decorations = match decorate {
true => Decorations::FollowServer,
false => Decorations::None,
};
window_handle.window.set_decorate(decorations);
// We should refresh the frame to apply decorations change.
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
#[cfg(feature = "sctk-adwaita")]
WindowRequest::CsdThemeVariant(theme) => {
window_handle.window.set_frame_config(theme.into());
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
#[cfg(not(feature = "sctk-adwaita"))]
WindowRequest::CsdThemeVariant(_) => {}
WindowRequest::Resizeable(resizeable) => {
window_handle.window.set_resizable(resizeable);
// We should refresh the frame to update button state.
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::Title(title) => {
window_handle.window.set_title(title);
// We should refresh the frame to draw new title.
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::MinSize(size) => {
let size = size.map(|size| (size.width, size.height));
window_handle.window.set_min_size(size);
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.redraw_requested = true;
}
WindowRequest::MaxSize(size) => {
let size = size.map(|size| (size.width, size.height));
window_handle.window.set_max_size(size);
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.redraw_requested = true;
}
WindowRequest::FrameSize(size) => {
if !window_handle.is_resizable.get() {
// On Wayland non-resizable window is achieved by setting both min and max
// size of the window to the same value.
let size = Some((size.width, size.height));
window_handle.window.set_max_size(size);
window_handle.window.set_min_size(size);
}
window_handle.window.resize(size.width, size.height);
// We should refresh the frame after resize.
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::PassthroughMouseInput(passthrough) => {
window_handle.passthrough_mouse_input(passthrough);
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::Attention(request_type) => {
window_handle.set_user_attention(request_type);
}
WindowRequest::Redraw => {
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.redraw_requested = true;
}
WindowRequest::Close => {
// The window was requested to be closed.
windows_to_close.push(*window_id);
// Send event that the window was destroyed.
let event_sink = &mut winit_state.event_sink;
event_sink.push_window_event(WindowEvent::Destroyed, *window_id);
}
};
}
}
// Close the windows.
for window in windows_to_close {
let _ = window_map.remove(&window);
let _ = window_updates.remove(&window);
}
}
impl Drop for WindowHandle {
fn drop(&mut self) {
unsafe {
let surface = self.window.surface().clone();
// The window must be destroyed before wl_surface.
ManuallyDrop::drop(&mut self.window);
surface.destroy();
}
}
}

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