Compare commits

..

3 Commits

Author SHA1 Message Date
Kirill Chibisov
546ab7575e Winit version 0.28.1 2023-02-02 14:49:31 +03:00
Kirill Chibisov
3e258a377f Fix window drop on Wayland
In some scenarious of window dropping the callback for keyboard
may run after the window was dropped.
2023-02-02 14:45:38 +03:00
Kirill Chibisov
e5260da95b Winit version 0.28.0 2023-02-02 06:52:53 +03:00
357 changed files with 30665 additions and 45621 deletions

20
.github/CODEOWNERS vendored
View File

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

View File

@@ -6,198 +6,116 @@ on:
branches: [master] branches: [master]
jobs: jobs:
fmt: Check_Formatting:
name: Check formatting
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@stable - uses: hecrj/setup-rust-action@v1
with: with:
rust-version: stable
components: rustfmt components: rustfmt
- name: Check Formatting - name: Check Formatting
run: cargo fmt -- --check run: cargo +stable fmt --all -- --check
tests: tests:
name: Test ${{ matrix.toolchain }} ${{ matrix.platform.name }} name: Tests
runs-on: ${{ matrix.platform.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
toolchain: [stable, nightly, '1.70.0'] rust_version: ['1.60.0', stable, nightly]
platform: platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml! # Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, } - { target: x86_64-pc-windows-msvc, os: windows-latest, }
- { name: 'Windows 32bit MSVC', target: i686-pc-windows-msvc, os: windows-latest, } - { target: i686-pc-windows-msvc, os: windows-latest, }
- { name: 'Windows 64bit GNU', target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu } - { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
- { name: 'Windows 32bit GNU', target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu } - { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
- { name: 'Linux 32bit', target: i686-unknown-linux-gnu, os: ubuntu-latest, } - { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--features=android-native-activity', cmd: 'apk --' } - { target: aarch64-linux-android, os: ubuntu-latest, options: -p winit, cmd: 'apk --', features: "android-native-activity" }
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, } - { target: x86_64-unknown-redox, os: ubuntu-latest, }
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, } - { target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, } - { target: x86_64-apple-ios, os: macos-latest, }
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, } - { target: aarch64-apple-ios, os: macos-latest, }
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, } # We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
exclude: # doesn't currently work on Linux.
# Android is tested on stable-3 - { target: wasm32-unknown-unknown, os: windows-latest, }
- toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
include:
- toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- toolchain: 'nightly'
platform: {
name: 'web Atomic',
target: wasm32-unknown-unknown,
os: ubuntu-latest,
options: '-Zbuild-std=panic_abort,std',
rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
components: rust-src,
}
env: env:
# Set more verbose terminal output
CARGO_TERM_VERBOSE: true
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
# Faster compilation and error on warnings RUSTFLAGS: "-C debuginfo=0 --deny warnings"
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}' OPTIONS: ${{ matrix.platform.options }}
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
CMD: ${{ matrix.platform.cmd }} CMD: ${{ matrix.platform.cmd }}
RUSTDOCFLAGS: -Dwarnings
runs-on: ${{ matrix.platform.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
# Used to cache cargo-web
- name: Restore cache of cargo folder - name: Cache cargo folder
# We use `restore` and later `save`, so that we can create the key after uses: actions/cache@v1
# the cache has been downloaded.
#
# This could be avoided if we added Cargo.lock to the repository.
uses: actions/cache/restore@v3
with: with:
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci path: ~/.cargo
path: | key: ${{ matrix.platform.target }}-cargo-${{ matrix.rust_version }}
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-never-intended-to-be-found
restore-keys: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}
- name: Generate lockfile - uses: hecrj/setup-rust-action@v1
# Also updates the crates.io index with:
run: cargo generate-lockfile rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
components: clippy
- name: Install GCC Multilib - name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib run: sudo apt-get update && sudo apt-get install gcc-multilib
- name: Cache cargo-apk
if: contains(matrix.platform.target, 'android')
id: cargo-apk-cache
uses: actions/cache@v3
with:
path: ~/.cargo/bin/cargo-apk
# Change this key if we update the required cargo-apk version
key: cargo-apk-v0-9-7
- uses: dtolnay/rust-toolchain@master
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
with:
toolchain: stable
- name: Install cargo-apk - name: Install cargo-apk
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true') if: contains(matrix.platform.target, 'android')
run: cargo install cargo-apk --version=^0.9.7 --locked run: cargo install cargo-apk
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
components: clippy, ${{ matrix.platform.components }}
- name: Check documentation - name: Check documentation
run: cargo doc --no-deps $OPTIONS --document-private-items shell: bash
env: run: cargo doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}'
- name: Build crate - name: Build crate
run: cargo $CMD build -p winit $OPTIONS shell: bash
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Build tests - name: Build tests
shell: bash
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.rust_version != '1.60.0'
run: cargo $CMD test -p winit --no-run $OPTIONS run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Run tests - name: Run tests
shell: bash
if: > if: >
!contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.rust_version != '1.60.0'
run: cargo $CMD test $OPTIONS run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Lint with clippy - name: Lint with clippy
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features') shell: bash
run: cargo clippy --all-targets $OPTIONS -- -Dwarnings if: (matrix.rust_version == 'stable') && !contains(matrix.platform.options, '--no-default-features')
run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings
- name: Build tests with serde enabled - name: Build tests with serde enabled
shell: bash
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.rust_version != '1.60.0'
run: cargo $CMD test --no-run $OPTIONS -p winit --features serde run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
- name: Run tests with serde enabled - name: Run tests with serde enabled
shell: bash
if: > if: >
!contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.rust_version != '1.60.0'
run: cargo $CMD test $OPTIONS --features serde run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
- name: Check docs.rs documentation
if: matrix.toolchain == 'nightly'
run: cargo doc --no-deps $OPTIONS --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
env:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs'
# See restore step above
- name: Save cache of cargo folder
uses: actions/cache/save@v3
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
cargo-deny:
name: Run cargo-deny on ${{ matrix.platform.name }}
runs-on: ubuntu-latest
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
strategy:
fail-fast: false
matrix:
platform:
- { name: 'Android', target: aarch64-linux-android }
- { name: 'iOS', target: aarch64-apple-ios }
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
- { name: 'macOS', target: x86_64-apple-darwin }
- { name: 'Redox OS', target: x86_64-unknown-redox }
- { name: 'web', target: wasm32-unknown-unknown }
- { name: 'Windows', target: x86_64-pc-windows-gnu }
steps:
- uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check
log-level: error
arguments: --all-features --target ${{ matrix.platform.target }}

View File

@@ -1,50 +0,0 @@
name: Docs
on:
push:
branches: [master]
jobs:
docs:
name: Documentation
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}winit
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- name: Run Rustdoc
env:
RUSTDOCFLAGS: --crate-version master --cfg=docsrs
run: |
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Fix permissions
run: |
chmod -c -R +rX "target/doc" | while read line; do
echo "::warning title=Invalid file permissions automatically fixed::$line"
done
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: target/doc
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "deps/apk-builder"]
path = deps/apk-builder
url = https://github.com/rust-windowing/android-rs-glue

View File

@@ -2,331 +2,12 @@
All notable changes to this project will be documented in this file. 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 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).
`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 `# And please only add new entries to the top of this list, right below the `# Unreleased` header.
Unreleased` header.
# Unreleased # Unreleased
- On X11, fix swapped instance and general class names.
- **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`.
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
- **Breaking:** Remove `Window::set_cursor_icon`
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`
- Add `CustomCursor`
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s.
- On macOS, add services menu.
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
- On Web, fix setting cursor icon overriding cursor visibility.
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
- On Web, add the ability to toggle calling `Event.preventDefault()` on `Window`.
- **Breaking:** Remove `WindowAttributes::fullscreen()` and expose as field directly.
- **Breaking:** Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold static data.
- **Breaking:** No longer export `platform::x11::XNotSupported`.
- **Breaking:** Renamed `platform::x11::XWindowType` to `platform::x11::WindowType`.
- Add the `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases.
- **Breaking:** Rename `TouchpadMagnify` to `PinchGesture`, `SmartMagnify` to `DoubleTapGesture` and `TouchpadRotate` to `RotationGesture` to represent the action rather than the intent.
- on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`.
- on Windows: add `with_system_backdrop`, `with_border_color`, `with_title_background_color`, `with_title_text_color` and `with_corner_preference`
- On Windows, Remove `WS_CAPTION`, `WS_BORDER` and `WS_EX_WINDOWEDGE` styles for child windows without decorations.
- On Windows, fixed a race condition when sending an event through the loop proxy.
- **Breaking:** Removed `EventLoopError::AlreadyRunning`, which can't happen as it is already prevented by the type system.
- On Wayland, disable `Occluded` event handling.
- Added `EventLoop::builder`, which is intended to replace the (now deprecated) `EventLoopBuilder::new`.
- **Breaking:** Changed the signature of `EventLoop::with_user_event` to return a builder.
- **Breaking:** Removed `EventLoopBuilder::with_user_event`, the functionality is now available in `EventLoop::with_user_event`.
- Add `Window::builder`, which is intended to replace the (now deprecated) `WindowBuilder::new`.
- On X11, reload dpi on `_XSETTINGS_SETTINGS` update.
- On X11, fix deadlock when adjusting DPI and resizing at the same time.
- On Wayland, fix `Focused(false)` being send when other seats still have window focused.
- On Wayland, fix `Window::set_{min,max}_inner_size` not always applied.
- On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop.
- On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
- **Breaking:** Move some types to the `winit-core` crate. Most types are
re-exported verbatim; however:
- `Event`, `WindowEvent` and `KeyEvent` now take a generic that `winit`
implements.
- `ActivationToken` and `InnerSizeWriter` expose new methods to make them
useful.
- `InnerSizeWriter::request_inner_size` returns an `InnerSizeIgnored` error
instead of an `ExternalError`.
# 0.29.10
- On Web, account for canvas being focused already before event loop starts.
- On Web, increase cursor position accuracy.
# 0.29.9
- On X11, fix `NotSupported` error not propagated when creating event loop.
- On Wayland, fix resize not issued when scale changes
- On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`.
- On macOS, report correct logical key when Ctrl or Cmd is pressed.
# 0.29.8
- On X11, fix IME input lagging behind.
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
- On X11, fix keymap not updated from xmodmap.
- On X11, reduce the amount of time spent fetching screen resources.
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
- On Wayland, fix `Window::inner_size` not using the correct rounding.
# 0.29.7
- On X11, fix `Xft.dpi` reload during runtime.
- On X11, fix window minimize.
# 0.29.6
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
- On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window.
- Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation
# 0.29.5
- On macOS, remove spurious error logging when handling `Fn`.
- On X11, fix an issue where floating point data from the server is
misinterpreted during a drag and drop operation.
- On X11, fix a bug where focusing the window would panic.
- On macOS, fix `refresh_rate_millihertz`.
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
- On X11, fix `Xft.dpi` detection from Xresources.
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
- On Wayland, fix resize being sent on focus change.
- On Windows, fix `set_ime_cursor_area`.
# 0.29.4
- Fix crash when running iOS app on macOS.
- On X11, check common alternative cursor names when loading cursor.
- On X11, reload the DPI after a property change event.
- On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread.
- On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account.
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
- On Wayland, fix `wl_surface` being destroyed before associated objects.
- On macOS, fix assertion when pressing `Fn` key.
- On Windows, add `WindowBuilderExtWindows::with_clip_children` to control `WS_CLIPCHILDREN` style.
# 0.29.3
- On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible.
- On Wayland, fix `RedrawRequsted` being always sent without decorations and `sctk-adwaita` feature.
- On Wayland, ignore resize requests when the window is fully tiled.
- On Wayland, use `configure_bounds` to constrain `with_inner_size` when compositor wants users to pick size.
- On Windows, fix deadlock when accessing the state during `Cursor{Enter,Leave}`.
- On Windows, add support for `Window::set_transparent`.
- On macOS, fix deadlock when entering a nested event loop from an event handler.
- On macOS, add support for `Window::set_blur`.
# 0.29.2
- **Breaking:** Bump MSRV from `1.60` to `1.65`.
- **Breaking:** Add `Event::MemoryWarning`; implemented on iOS/Android.
- **Breaking:** Bump `ndk` version to `0.8.0`, ndk-sys to `0.5.0`, `android-activity` to `0.5.0`.
- **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`.
- **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`.
- **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely.
- **Breaking:** Moved `ControlFlow` to `EventLoopWindowTarget::set_control_flow()` and `EventLoopWindowTarget::control_flow()`.
- **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>`
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
- **Breaking:** on Wayland, dispatching user created Wayland queue won't wake up the loop unless winit has event to send back.
- **Breaking:** remove `DeviceEvent::Text`.
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
- **Breaking** `run() -> !` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_on_demand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** Remove all deprecated `modifiers` fields.
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
- **Breaking** Add `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events on Wayland, X11, Windows, macOS and Web.
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to the canvas size will be reported through `WindowEvent::Resized`.
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
- **Breaking:** `CursorIcon::Arrow` was removed.
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
- **Breaking:** Overhaul keyboard input handling.
- Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`.
- Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`.
- Change `Event::Key` to contain a `RawKeyEvent`.
- Remove `Event::ReceivedCharacter`. In its place, you should use
`KeyEvent.text` in combination with `WindowEvent::Ime`.
- Replace `VirtualKeyCode` with the `Key` enum.
- Replace `ScanCode` with the `KeyCode` enum.
- Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`.
- Add `PhysicalKey` wrapping `KeyCode` and `NativeKeyCode`.
- Add `KeyCode` to refer to keys (roughly) by their physical location.
- Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't
understand.
- Add `Key` to represent the keys after they've been interpreted by the
active (software) keyboard layout.
- Add `NamedKey` to represent the categorized keys.
- Add `NativeKey` to represent raw `Key`s which Winit doesn't understand.
- Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing,
but can appear simultaneously in different spots on the same keyboard
layout.
- Add `Window::reset_dead_keys` to enable application-controlled cancellation
of dead key sequences.
- Add `KeyEventExtModifierSupplement` to expose additional (and less
portable) interpretations of a given key-press.
- Add `PhysicalKeyExtScancode`, which lets you convert between scancodes and
`PhysicalKey`.
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
- Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead:
- `platform::windows::HINSTANCE`.
- `WindowExtWindows::hinstance`.
- `WindowExtWindows::hwnd`.
- `WindowExtIOS::ui_window`.
- `WindowExtIOS::ui_view_controller`.
- `WindowExtIOS::ui_view`.
- `WindowExtMacOS::ns_window`.
- `WindowExtMacOS::ns_view`.
- `EventLoopWindowTargetExtWayland::wayland_display`.
- `WindowExtWayland::wayland_surface`.
- `WindowExtWayland::wayland_display`.
- `WindowExtX11::xlib_window`.
- `WindowExtX11::xlib_display`.
- `WindowExtX11::xlib_screen_id`.
- `WindowExtX11::xcb_connection`.
- Reexport `raw-window-handle` in `window` module.
- Add `ElementState::is_pressed`.
- Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
- Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now.
- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now.
- Implement `AsFd`/`AsRawFd` for `EventLoop<T>` on X11 and Wayland.
- Implement `PartialOrd` and `Ord` for `MouseButton`.
- Implement `PartialOrd` and `Ord` on types in the `dpi` module.
- Make `WindowBuilder` `Send + Sync`.
- Make iOS `MonitorHandle` and `VideoMode` usable from other threads.
- Make iOS windows usable from other threads.
- On Android, add force data to touch events.
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
- On Android, fix `DeviceId` to contain device id's.
- On Orbital, fix `ModifiersChanged` not being sent.
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
- On Wayland, add `Window::drag_resize_window` method.
- On Wayland, remove `WINIT_WAYLAND_CSD_THEME` variable.
- On Wayland, fix `TouchPhase::Canceled` being sent for moved events.
- On Wayland, fix forward compatibility issues.
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
- On Wayland, fix maximized startup not taking full size on GNOME.
- On Wayland, fix maximized window creation and window geometry handling.
- On Wayland, fix window not checking that it actually got initial configure event.
- On Wayland, make double clicking and moving the CSD frame more reliable.
- On Wayland, support `Occluded` event with xdg-shell v6
- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor.
- On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback.
- On Web, `EventLoopProxy` now implements `Send`.
- On Web, `Window` now implements `Send` and `Sync`.
- On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position.
- On Web, add Fullscreen API compatibility for Safari.
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and `DeviceEvent::Key` support.
- On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback.
- On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
- On Web, allow event loops to be recreated with `spawn`.
- On Web, enable event propagation.
- On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time.
- On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window.
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during transient activation.
- On Web, fix pen treated as mouse input.
- On Web, fix pointer button events not being processed when a buttons is already pressed.
- On Web, fix scale factor resize suggestion always overwriting the canvas size.
- On Web, fix some `WindowBuilder` methods doing nothing.
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
- On Web, fix touch input not gaining or loosing focus.
- On Web, fix touch location to be as accurate as mouse position.
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
- On Web, implement `Window::focus_window()`.
- On Web, implement `Window::set_(min|max)_inner_size()`.
- On Web, implement `WindowEvent::Occluded`.
- On Web, never return a `MonitorHandle`.
- On Web, prevent clicks on the canvas to select text.
- On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected.
- On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`.
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
- On Web, scale factor and dark mode detection are now more robust.
- On Web, send mouse position on button release as well.
- On Web, take all transient activations on the canvas and window into account to queue a fullscreen request.
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events.
- On Web, use the correct canvas size when calculating the new size during scale factor change, instead of using the output bitmap size.
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
- On Web: fix position of touch events to be relative to the canvas.
- On Windows, add `drag_resize_window` method support.
- On Windows, add horizontal MouseWheel `DeviceEvent`.
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
- On Windows, fix IME APIs not working when from non event loop thread.
- On Windows, fix `CursorEnter/Left` not being sent when grabbing the mouse.
- On Windows, fix `RedrawRequested` not being delivered when calling `Window::request_redraw` from `RedrawRequested`.
- On Windows, port to `windows-sys` version 0.48.0.
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
- On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`.
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
- On X11, set `visual_id` in returned `raw-window-handle`.
- On iOS, add ability to change the status bar style.
- On iOS, add force data to touch events when using the Apple Pencil.
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
- On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground.
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
- On macOS, fix assertion when pressing `Globe` key.
- On macOS, fix crash in `window.set_minimized(false)`.
- On macOS, fix crash when dropping `Window`.
# 0.28.7
- Fix window size sometimes being invalid when resizing on macOS 14 Sonoma.
# 0.28.6
- On macOS, fixed memory leak when getting monitor handle.
- On macOS, fix `Backspace` being emitted when clearing preedit with it.
# 0.28.5
- On macOS, fix `key_up` being ignored when `Ime` is disabled.
# 0.28.4
- On macOS, fix empty marked text blocking regular input.
- On macOS, fix potential panic when getting refresh rate.
- On macOS, fix crash when calling `Window::set_ime_position` from another thread.
# 0.28.3
- Fix macOS memory leaks.
# 0.28.2
- Implement `HasRawDisplayHandle` for `EventLoop`.
- On macOS, set resize increments only for live resizes.
- On Wayland, fix rare crash on DPI change
- Web: Added support for `Window::theme`.
- On Wayland, fix rounding issues when doing resize.
- On macOS, fix wrong focused state on startup.
- On Windows, fix crash on setting taskbar when using Visual Studio debugger.
- On macOS, resize simple fullscreen windows on windowDidChangeScreen events.
# 0.28.1 # 0.28.1
- On Wayland, fix crash when dropping a window in multi-window setup. - On Wayland, fix crash when dropping a window in multi-window setup.
@@ -602,7 +283,7 @@ Unreleased` header.
# 0.23.0 (2020-10-02) # 0.23.0 (2020-10-02)
- On iOS, fixed support for the "Debug View Hierarchy" feature in Xcode. - 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 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 Unix, X11 and Wayland are now optional features (enabled by default)
- On X11, fix deadlock when calling `set_fullscreen_inner`. - On X11, fix deadlock when calling `set_fullscreen_inner`.

View File

@@ -11,7 +11,7 @@ may be worth creating a separate crate that extends Winit's API to add that func
When reporting an issue, in order to help the maintainers understand what the problem is, please make When reporting an issue, in order to help the maintainers understand what the problem is, please make
your description of the issue as detailed as possible: your description of the issue as detailed as possible:
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to - if it is a bug, please provide clear explanation of what happens, what should happen, and how to
reproduce the issue, ideally by providing a minimal program exhibiting the problem reproduce the issue, ideally by providing a minimal program exhibiting the problem
- if it is a feature request, please provide a clear argumentation about why you believe this feature - if it is a feature request, please provide a clear argumentation about why you believe this feature
should be supported by winit should be supported by winit
@@ -20,8 +20,8 @@ your description of the issue as detailed as possible:
When making a code contribution to winit, before opening your pull request, please make sure that: When making a code contribution to winit, before opening your pull request, please make sure that:
- your patch builds with Winit's minimal supported rust version - Rust 1.70. - your patch builds with Winit's minimal supported rust version - Rust 1.60.
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms - you tested your modifications on all the platforms impacted, or if not possible detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit - you updated any relevant documentation in winit
- you left comments in your code explaining any part that is not straightforward, so that the - you left comments in your code explaining any part that is not straightforward, so that the
@@ -34,7 +34,7 @@ When making a code contribution to winit, before opening your pull request, plea
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features) relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
should be updated. should be updated.
Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
is that a PR must be approved by at least two maintainers of winit before being merged, including 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). at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
@@ -46,26 +46,27 @@ Once your PR is deemed ready, the merging maintainer will take care of resolving
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file. The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
If you are interested in being pinged when testing is needed for a specific platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table! If you are interested in being pinged when testing is needed for a certain platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
## Release process ## Release process
Given that winit is a widely used library, we should be able to make a patch Given that winit is a widely used library we should be able to make a patch
releases at any time we want without blocking the development of new features. releases at any time we want without blocking the development of new features.
To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch. To achieve these goals, a new branch is created for every new release. Releases
and later patch releases are committed and tagged in this branch.
The exact steps for an exemplary `0.2.0` release might look like this: The exact steps for an exemplary `0.2.0` release might look like this:
1. Initially, the version on the latest master is `0.1.0` 1. Initially the version on the latest master is `0.1.0`
2. A new `v0.2.x` branch is created for the release 2. A new `v0.2.x` branch is created for the release
3. In the branch, the version is bumped to `v0.2.0` 3. In the branch, the version is bumped to `v0.2.0`
4. The new commit in the branch is tagged `v0.2.0` 4. The new commit in the branch is tagged `v0.2.0`
5. The version is pushed to crates.io 5. The version is pushed to crates.io
6. A GitHub release is created for the `v0.2.0` tag 6. A GitHub release is created for the `v0.2.0` tag
7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated 7. On master, the version is bumped to `0.2.0` and the CHANGELOG is updated
When doing a patch release, the process is similar: When doing a patch release the process is similar:
1. Initially, the version of the latest release is `0.2.0` 1. Initially the version of the latest release is `0.2.0`
2. Checkout the `v0.2.x` branch 2. Checkout the `v0.2.x` branch
3. Cherry-pick the required non-breaking changes into the `v0.2.x` 3. Cherry-pick the required non-breaking changes into the `v0.2.x`
4. Follow steps 3-7 of the regular release example 4. Follow steps 3-7 of the regular release example

View File

@@ -1,16 +1,161 @@
[package]
name = "winit"
version = "0.28.1"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
keywords = ["windowing"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version = "1.60.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",
]
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "mio", "percent-encoding"]
wayland = ["wayland-client", "wayland-protocols", "sctk", "wayland-commons"]
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
android-native-activity = [ "android-activity/native-activity" ]
android-game-activity = [ "android-activity/game-activity" ]
[build-dependencies]
cfg_aliases = "0.1.1"
[dependencies]
bitflags = "1"
instant = { version = "0.1", features = ["wasm-bindgen"] }
log = "0.4"
mint = { version = "0.5.6", optional = true }
once_cell = "1.12"
raw_window_handle = { package = "raw-window-handle", version = "0.5" }
serde = { version = "1", optional = true, features = ["serde_derive"] }
[dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] }
simple_logger = { version = "2.1.0", default_features = false }
[target.'cfg(target_os = "android")'.dependencies]
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
android-activity = "0.4.0"
ndk = "0.7.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
objc2 = "=0.3.0-beta.3"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.22.3"
dispatch = "0.2.0"
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.45"
features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Media",
"Win32_System_Com_StructuredStorage",
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
"Win32_System_WindowsProgramming",
"Win32_UI_Accessibility",
"Win32_UI_Controls",
"Win32_UI_HiDpi",
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Input_Pointer",
"Win32_UI_Input_Touch",
"Win32_UI_Shell",
"Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging",
]
[target.'cfg(all(unix, not(any(target_os = "redox", target_arch = "wasm32", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
libc = "0.2.64"
mio = { version = "0.8", features = ["os-ext"], optional = true }
percent-encoding = { version = "2.0", optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.5.1", default_features = false, optional = true }
wayland-client = { version = "0.29.5", default_features = false, features = ["use_system_lib"], optional = true }
wayland-protocols = { version = "0.29.5", features = [ "staging_protocols"], optional = true }
wayland-commons = { version = "0.29.5", optional = true }
x11-dl = { version = "2.18.5", optional = true }
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.42", default-features = false }
redox_syscall = "0.3"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.build-dependencies]
wayland-scanner = "0.29.5"
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.22"
features = [
'console',
"AddEventListenerOptions",
'CssStyleDeclaration',
'BeforeUnloadEvent',
'Document',
'DomRect',
'Element',
'Event',
"EventListenerOptions",
'EventTarget',
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'KeyboardEvent',
'MediaQueryList',
'MediaQueryListEvent',
'MouseEvent',
'Node',
'PointerEvent',
'Window',
'WheelEvent'
]
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2.45"
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_log = "0.2"
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
[workspace] [workspace]
members = [ members = [
"run-wasm", "run-wasm",
"winit",
"winit-core"
] ]
resolver = "2"
[workspace.dependencies]
bitflags = "2"
cfg_aliases = "0.2.0"
cursor-icon = "1.1.0"
serde = { version = "1", features = ["serde_derive"] }
smol_str = "0.2.0"
web-time = "1"
winit-core = { path = "./winit-core", default-features = false, features = ["std"] }

View File

@@ -1,6 +1,6 @@
# Winit Scope # Winit Scope
Winit aims to expose an interface that abstracts over window creation and input handling and can Winit aims to expose an interface that abstracts over window creation and input handling, and can
be used to create both games and applications. It supports the following main graphical platforms: be used to create both games and applications. It supports the following main graphical platforms:
- Desktop - Desktop
- Windows 7+ (10+ is tested regularly) - Windows 7+ (10+ is tested regularly)
@@ -13,9 +13,7 @@ be used to create both games and applications. It supports the following main gr
- iOS - iOS
- Android - Android
- Web - Web
- Chrome - via WASM
- Firefox
- Safari 13.1+
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
aim to support every single feature of every platform, but rather to abstract over the common features aim to support every single feature of every platform, but rather to abstract over the common features
@@ -45,10 +43,10 @@ be released and the library will enter maintenance mode. For the most part, new
be added past this point. New platform features may be accepted and exposed through point releases. be added past this point. New platform features may be accepted and exposed through point releases.
### Tier upgrades ### Tier upgrades
Some platform features could, in theory, be exposed across multiple platforms, but have not gone Some platform features could in theory be exposed across multiple platforms, but have not gone
through the implementation work necessary to function on all platforms. When one of these features through the implementation work necessary to function on all platforms. When one of these features
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature. gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions get deprecated and become permanently If that gets accepted, the platform-specific functions gets deprecated and become permanently
exposed through the core, cross-platform API. exposed through the core, cross-platform API.
# Features # Features
@@ -88,7 +86,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after - **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation. creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor - **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows and, if applicable, captures the monitor for exclusive for fullscreen windows, and if applicable, captures the monitor for exclusive
use by this application. use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content. - **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent - **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
@@ -105,8 +103,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **Mouse set location**: Forcibly changing the location of the pointer. - **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor locking**: Locking the cursor inside the window so it cannot move. - **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 confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor icon**: Changing the cursor icon or hiding the cursor. - **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Cursor image**: Changing the cursor to your own image.
- **Cursor hittest**: Handle or ignore mouse events for a window. - **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events. - **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied. - **Touch pressure**: Touch events contain information about the amount of force being applied.
@@ -120,17 +117,11 @@ If your PR makes notable changes to Winit's features, please update this section
## Platform ## Platform
### Windows ### Windows
* Setting the name of the internal window class
* Setting the taskbar icon * Setting the taskbar icon
* Setting the parent window * Setting the parent window
* Setting a menu bar * Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support * `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme * Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
* Changing a system-drawn backdrop
* Setting the window border color
* Setting the title bar background color
* Setting the title color
* Setting the corner rounding preference
### macOS ### macOS
* Window activation policy * Window activation policy
@@ -152,17 +143,20 @@ If your PR makes notable changes to Winit's features, please update this section
### iOS ### iOS
* `winit` has a minimum OS requirement of iOS 8 * `winit` has a minimum OS requirement of iOS 8
* Get the `UIWindow` object pointer
* Get the `UIViewController` object pointer
* Get the `UIView` object pointer
* Get the `UIScreen` object pointer * Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor * Setting the `UIView` hidpi factor
* Valid orientations * Valid orientations
* Home indicator visibility * Home indicator visibility
* Status bar visibility and style * Status bar visibility
* Deferring system gestures * Deferrring system gestures
* Getting the device idiom * Getting the device idiom
* Getting the preferred video mode * Getting the preferred video mode
### Web ### Web
* Get if the systems preferred color scheme is "dark" * Get if systems preferred color scheme is "dark"
## Usability ## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial) * `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
@@ -172,23 +166,22 @@ If your PR makes notable changes to Winit's features, please update this section
Legend: Legend:
- ✔️: Works as intended - ✔️: Works as intended
- ▢: Mostly works, but some bugs are known - ▢: Mostly works but some bugs are known
- ❌: Missing feature or large bugs making it unusable - ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform - **N/A**: Not applicable for this platform
- ❓: Unknown status - ❓: Unknown status
### Windowing ### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS| |Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |Redox OS|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | |-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ | |Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ | |Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** | |Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ | |Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | |Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ | |Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ |✔️ |
|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** | |Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ | |Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | |Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | |Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | |Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
@@ -199,43 +192,42 @@ Legend:
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** | |Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
### System information ### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ | |---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ | |Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ | |Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
### Input handling ### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | |----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ | |Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** | |Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ | |Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ | |Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** | |Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** | |Cursor hittest |✔️ |✔️ | |✔️ |**N/A**|**N/A**| | |
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** | |Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** | |Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** | |Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ | |Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |[#720] |**N/A**|**N/A**|❓ |**N/A** | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |[#306] |**N/A**|**N/A**|❓ |**N/A** |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** | |Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** | |Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** | |Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** | |Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** | |Resize with cursor | |❌ |✔️ | |**N/A**|**N/A**|**N/A** |**N/A** |
### Pending API Reworks ### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms. Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ | |New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ | |Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |✔️ |✔️ |❓ | |
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ | |Keyboard Input ([#812]) |❌ | | | |❌ |❌ |❓ | |
### Completed API Reworks ### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
[#165]: https://github.com/rust-windowing/winit/issues/165 [#165]: https://github.com/rust-windowing/winit/issues/165
@@ -251,5 +243,5 @@ Changes in the API that have been agreed upon but aren't implemented across all
[#720]: https://github.com/rust-windowing/winit/issues/720 [#720]: https://github.com/rust-windowing/winit/issues/720
[#721]: https://github.com/rust-windowing/winit/issues/721 [#721]: https://github.com/rust-windowing/winit/issues/721
[#750]: https://github.com/rust-windowing/winit/issues/750 [#750]: https://github.com/rust-windowing/winit/issues/750
[#753]: https://github.com/rust-windowing/winit/issues/753
[#804]: https://github.com/rust-windowing/winit/issues/804 [#804]: https://github.com/rust-windowing/winit/issues/804
[#812]: https://github.com/rust-windowing/winit/issues/812

View File

@@ -2,20 +2,21 @@
The winit maintainers would like to recognize the following former winit The winit maintainers would like to recognize the following former winit
contributors, without whom winit would not exist in its current form. We thank contributors, without whom winit would not exist in its current form. We thank
them deeply for their time and efforts and wish them the best of luck in their them deeply for their time and efforts, and wish them best of luck in their
future endeavors: 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. years of existence.
* [@vberger]: For diligently creating the Wayland backend and being its * [@vberger]: For diligently creating the Wayland backend, and being its
extremely helpful and benevolent maintainer for years. extremely helpful and benevolent maintainer for years.
* [@francesca64]: For taking over the responsibility of maintaining almost every * [@francesca64]: For taking over the responsibility of maintaining almost every
winit backend and standardizing HiDPI support across all of them. winit backend, and standardizing HiDPI support across all of them.
* [@Osspial]: For heroically landing EventLoop 2.0 and valiantly ushering in a * [@Osspial]: For heroically landing EventLoop 2.0, and valiantly ushering in a
vastly more sustainable era of winit. vastly more sustainable era of winit.
* [@goddessfreya]: For selflessly taking over maintainership of glutin and her * [@goddessfreya]: For selflessly taking over maintainership of glutin, and her
stellar dedication to improving both winit and glutin. stellar dedication to improving both winit and glutin.
* [@ArturKovacs]: For consistently maintaining the macOS backend and for his immense involvement in designing and implementing the new keyboard API. * [@ArturKovacs]: For consistently maintaining the macOS backend, and his
immense involvement in designing and implementing the new keyboard API.
[@tomaka]: https://github.com/tomaka [@tomaka]: https://github.com/tomaka
[@vberger]: https://github.com/vberger [@vberger]: https://github.com/vberger

128
README.md
View File

@@ -2,37 +2,63 @@
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit) [![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit) [![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs
)](https://rust-windowing.github.io/winit/winit/index.html)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions) [![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
```toml ```toml
[dependencies] [dependencies]
winit = "0.29.10" winit = "0.28.1"
``` ```
## [Documentation](https://docs.rs/winit) ## [Documentation](https://docs.rs/winit)
For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md). For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md).
For features _outside_ the scope of winit, see [Are we GUI Yet?](https://areweguiyet.com/) and [Are we game yet?](https://arewegameyet.rs/), depending on what kind of project you're looking to do. For features _outside_ the scope of winit, see [Missing features provided by other crates](https://github.com/rust-windowing/winit/wiki/Missing-features-provided-by-other-crates) in the wiki.
## Contact Us ## Contact Us
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit). Join us in any of these:
The maintainers have a meeting every friday at UTC 14. The meeting notes can be found [here](https://hackmd.io/@winit-meetings). [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org)
[![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit)
## Usage ## Usage
Winit is a window creation and management library. It can create windows and lets you handle Winit is a window creation and management library. It can create windows and lets you handle
events (for example: the window being resized, a key being pressed, a mouse movement, etc.) events (for example: the window being resized, a key being pressed, a mouse movement, etc.)
produced by the window. produced by window.
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to
show something on the window you need to use the platform-specific getters provided by winit, or show something on the window you need to use the platform-specific getters provided by winit, or
another library. another library.
```rust
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
let event_loop = EventLoop::new();
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,
_ => (),
}
});
}
```
Winit is only officially supported on the latest stable version of the Rust compiler.
### Cargo Features ### Cargo Features
Winit provides the following features, which can be enabled in your `Cargo.toml` file: Winit provides the following features, which can be enabled in your `Cargo.toml` file:
@@ -41,41 +67,17 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend * `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
* `mint`: Enables mint (math interoperability standard types) conversions. * `mint`: Enables mint (math interoperability standard types) conversions.
## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following
formula:
```
min(sid, stable - 3)
```
Where `sid` is the current version of `rustc` provided by [Debian Sid], and
`stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability.
[Debian Sid]: https://packages.debian.org/sid/rustc
The exception is for the Android platform, where a higher Rust version
must be used for certain Android features. In this case, the MSRV will be
capped at the latest stable version of Rust minus three. This inconsistency is
not reflected in Cargo metadata, as it is not powerful enough to expose this
restriction.
All crates in the [`rust-windowing`] organizations have the
same MSRV policy.
[`rust-windowing`]: https://github.com/rust-windowing
### Platform-specific usage ### Platform-specific usage
#### Wayland #### Wayland
Note that windows don't appear on Wayland until you draw/present to them. Note that windows don't appear on Wayland until you draw/present to them.
#### Web `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` To run the web example: `cargo run-wasm --example web`
@@ -86,7 +88,7 @@ either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
create a `<canvas>` element which you can then retrieve][web canvas getter] and create a `<canvas>` element which you can then retrieve][web canvas getter] and
insert it into the DOM yourself. insert it into the DOM yourself.
For the example code using Winit on Web, check out the [web example]. For For example code using Winit with WebAssembly, check out the [web example]. For
information on using Rust on WebAssembly, check out the [Rust and WebAssembly information on using Rust on WebAssembly, check out the [Rust and WebAssembly
book]. book].
@@ -97,7 +99,7 @@ book].
#### Android #### Android
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/latest/ndk/) crate. The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/0.7.0/ndk/) crate.
Native Android applications need some form of "glue" crate that is responsible Native Android applications need some form of "glue" crate that is responsible
for defining the main entry point for your Rust application as well as tracking for defining the main entry point for your Rust application as well as tracking
@@ -109,14 +111,13 @@ glue crate (prior to `0.28` it used
The version of the glue crate that your application depends on _must_ match the The version of the glue crate that your application depends on _must_ match the
version that Winit depends on because the glue crate is responsible for your version that Winit depends on because the glue crate is responsible for your
application's main entry point. If Cargo resolves multiple versions, they will application's main entrypoint. If Cargo resolves multiple versions they will
clash. clash.
`winit` glue compatibility table: `winit` glue compatibility table:
| winit | ndk-glue | | winit | ndk-glue |
| :---: | :--------------------------: | | :---: | :--------------------------: |
| 0.29 | `android-activity = "0.5"` |
| 0.28 | `android-activity = "0.4"` | | 0.28 | `android-activity = "0.4"` |
| 0.27 | `ndk-glue = "0.7"` | | 0.27 | `ndk-glue = "0.7"` |
| 0.26 | `ndk-glue = "0.5"` | | 0.26 | `ndk-glue = "0.5"` |
@@ -127,7 +128,7 @@ The recommended way to avoid a conflict with the glue version is to avoid explic
depending on the `android-activity` crate, and instead consume the API that depending on the `android-activity` crate, and instead consume the API that
is re-exported by Winit under `winit::platform::android::activity::*` is re-exported by Winit under `winit::platform::android::activity::*`
Running on an Android device needs a dynamic system library. Add this to Cargo.toml: Running on an Android device needs a dynamic system library, add this to Cargo.toml:
```toml ```toml
[lib] [lib]
@@ -135,14 +136,14 @@ name = "main"
crate-type = ["cdylib"] crate-type = ["cdylib"]
``` ```
All Android applications are based on an `Activity` subclass, and the All Android applications are based on an `Activity` subclass and the
`android-activity` crate is designed to support different choices for this base `android-activity` crate is designed to support different choices for this base
class. Your application _must_ specify the base class it needs via a feature flag: class. Your application _must_ specify the base class it needs via a feature flag:
| Base Class | Feature Flag | Notes | | Base Class | Feature Flag | Notes |
| :--------------: | :---------------: | :-----: | | :--------------: | :---------------: | :-----: |
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.| | `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) | | [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity [`GameActivity`]: https://developer.android.com/games/agdk/game-activity
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input [`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
@@ -151,13 +152,40 @@ class. Your application _must_ specify the base class it needs via a feature fla
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries [agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
[Gradle]: https://developer.android.com/studio/build [Gradle]: https://developer.android.com/studio/build
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples). For example, add this to Cargo.toml:
```toml
winit = { version = "0.28", features = [ "android-native-activity" ] }
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.11.0"
```
And, for example, define an entry point for your library like this:
```rust
#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;
#[cfg(target_os = "android")]
#[no_mangle]
fn android_main(app: AndroidApp) {
use winit::platform::android::EventLoopBuilderExtAndroid;
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Trace));
let event_loop = EventLoopBuilder::with_user_event()
.with_android_app(app)
.build();
_main(event_loop);
}
```
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity` ##### Converting from `ndk-glue` to `android-activity`
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be: If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be:
1. Remove `ndk-glue` from your `Cargo.toml` 1. Remove `ndk-glue` from your `Cargo.toml`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.10", features = [ "android-native-activity" ] }` 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.28", features = [ "android-native-activity" ] }`
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above). 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above). 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
@@ -168,13 +196,13 @@ doing anything; this includes creating windows, fetching monitors, drawing,
and so on, see issues [#2238], [#2051] and [#2087]. and so on, see issues [#2238], [#2051] and [#2087].
If you encounter problems, you should try doing your initialization inside If you encounter problems, you should try doing your initialization inside
`Event::Resumed`. `Event::NewEvents(StartCause::Init)`.
#### iOS #### iOS
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
by all UI-related code (see issue [#1705]). It would be best to consider creating your windows by all UI related code, see issue [#1705]. You should consider creating your windows
inside `Event::Resumed`. inside `Event::NewEvents(StartCause::Init)`.
[#2238]: https://github.com/rust-windowing/winit/issues/2238 [#2238]: https://github.com/rust-windowing/winit/issues/2238
@@ -184,5 +212,5 @@ inside `Event::Resumed`.
#### Redox OS #### Redox OS
Redox OS has some functionality not yet present that will be implemented when Redox OS has some functionality not present yet, that will be implemented when
its orbital display server provides it. its orbital display server provides it.

64
build.rs Normal file
View File

@@ -0,0 +1,64 @@
use cfg_aliases::cfg_aliases;
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
),
feature = "wayland",
))]
mod wayland {
use std::env;
use std::path::PathBuf;
use wayland_scanner::Side;
pub fn main() {
let mut path = PathBuf::from(env::var("OUT_DIR").unwrap());
path.push("fractional_scale_v1.rs");
wayland_scanner::generate_code(
"wayland_protocols/fractional-scale-v1.xml",
&path,
Side::Client,
);
}
}
fn main() {
// The script doesn't depend on our code
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=wayland_protocols");
// Setup cfg aliases
cfg_aliases! {
// Systems.
android_platform: { target_os = "android" },
wasm_platform: { target_arch = "wasm32" },
macos_platform: { target_os = "macos" },
ios_platform: { target_os = "ios" },
windows_platform: { target_os = "windows" },
apple: { any(target_os = "ios", target_os = "macos") },
free_unix: { all(unix, not(apple), not(android_platform)) },
redox: { target_os = "redox" },
// Native displays.
x11_platform: { all(feature = "x11", free_unix, not(wasm), not(redox)) },
wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) },
orbital_platform: { redox },
}
// XXX aliases are not available for the build script itself.
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
),
feature = "wayland",
))]
wayland::main();
}

View File

@@ -1,15 +0,0 @@
disallowed-methods = [
{ path = "web_sys::window", reason = "is not available in every context" },
{ path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ path = "icrate::AppKit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
]

View File

@@ -1,65 +0,0 @@
# https://embarkstudios.github.io/cargo-deny/
# cargo install cargo-deny
# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check
# Note: running just `cargo deny check` without a `--target` will result in
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
targets = [
{ triple = "aarch64-apple-ios" },
{ triple = "aarch64-linux-android" },
{ triple = "i686-pc-windows-gnu" },
{ triple = "i686-pc-windows-msvc" },
{ triple = "i686-unknown-linux-gnu" },
{ triple = "wasm32-unknown-unknown" },
{ triple = "x86_64-apple-darwin" },
{ triple = "x86_64-apple-ios" },
{ triple = "x86_64-pc-windows-gnu" },
{ triple = "x86_64-pc-windows-msvc" },
{ triple = "x86_64-unknown-linux-gnu" },
{ triple = "x86_64-unknown-redox" },
]
[advisories]
vulnerability = "deny"
unmaintained = "warn"
yanked = "deny"
ignore = []
[bans]
multiple-versions = "deny"
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
deny = []
skip = [
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "libloading" }, # x11rb uses a different version until the next update
]
skip-tree = [
{ name = "run-wasm", version = "0.1.0" }
]
[licenses]
private = { ignore = true }
unlicensed = "deny"
allow-osi-fsf-free = "neither"
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
copyleft = "deny"
allow = [
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
"ISC", # https://tldrlegal.com/license/-isc-license
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
"MIT-0", # https://choosealicense.com/licenses/mit-0/
"MIT", # https://tldrlegal.com/license/mit-license
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
]

View File

@@ -1,11 +0,0 @@
# Image Attribution
These images are used in the documentation of `winit`.
## keyboard_*.svg
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
which have been released under the same license as a derivative work.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -1,33 +1,22 @@
#[cfg(all( #[cfg(any(x11_platform, macos_platform, windows_platform))]
feature = "rwh_06", fn main() {
any(x11_platform, macos_platform, windows_platform)
))]
#[path = "util/fill.rs"]
mod fill;
#[cfg(all(
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
))]
#[allow(deprecated)]
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap; use std::collections::HashMap;
use raw_window_handle::HasRawWindowHandle;
use winit::{ use winit::{
dpi::{LogicalPosition, LogicalSize, Position}, dpi::{LogicalPosition, LogicalSize, Position},
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyboardInput, WindowEvent},
event_loop::{EventLoop, EventLoopWindowTarget}, event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
raw_window_handle::HasRawWindowHandle, window::{Window, WindowBuilder, WindowId},
window::{Window, WindowId},
}; };
fn spawn_child_window( fn spawn_child_window(
parent: &Window, parent: &Window,
event_loop: &EventLoopWindowTarget, event_loop: &EventLoopWindowTarget<()>,
windows: &mut HashMap<WindowId, Window>, windows: &mut HashMap<WindowId, Window>,
) { ) {
let parent = parent.raw_window_handle().unwrap(); let parent = parent.raw_window_handle();
let mut builder = Window::builder() let mut builder = WindowBuilder::new()
.with_title("child window") .with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32)) .with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
@@ -43,8 +32,8 @@ fn main() -> Result<(), impl std::error::Error> {
let mut windows = HashMap::new(); let mut windows = HashMap::new();
let event_loop: EventLoop<()> = EventLoop::new().unwrap(); let event_loop: EventLoop<()> = EventLoop::new();
let parent_window = Window::builder() let parent_window = WindowBuilder::new()
.with_title("parent window") .with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32)) .with_inner_size(LogicalSize::new(640.0f32, 480.0f32))
@@ -53,12 +42,14 @@ fn main() -> Result<(), impl std::error::Error> {
println!("parent window: {parent_window:?})"); println!("parent window: {parent_window:?})");
event_loop.run(move |event: Event<()>, elwt| { event_loop.run(move |event: Event<'_, ()>, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, window_id } = event { if let Event::WindowEvent { event, window_id } = event {
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
windows.clear(); windows.clear();
elwt.exit(); *control_flow = ControlFlow::Exit;
} }
WindowEvent::CursorEntered { device_id: _ } => { WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window is created // On x11, println when the cursor entered in a window even if the child window is created
@@ -68,19 +59,14 @@ fn main() -> Result<(), impl std::error::Error> {
println!("cursor entered in the window {window_id:?}"); println!("cursor entered in the window {window_id:?}");
} }
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
event: input:
KeyEvent { KeyboardInput {
state: ElementState::Pressed, state: ElementState::Pressed,
.. ..
}, },
.. ..
} => { } => {
spawn_child_window(&parent_window, elwt, &mut windows); spawn_child_window(&parent_window, event_loop, &mut windows);
}
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
} }
_ => (), _ => (),
} }
@@ -88,10 +74,7 @@ fn main() -> Result<(), impl std::error::Error> {
}) })
} }
#[cfg(not(all( #[cfg(not(any(x11_platform, macos_platform, windows_platform)))]
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
)))]
fn main() { fn main() {
panic!("This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature enabled."); panic!("This example is supported only on x11, macOS, and Windows.");
} }

View File

@@ -1,22 +1,14 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
use std::thread; use std::{thread, time};
#[cfg(not(web_platform))]
use std::time;
#[cfg(web_platform)]
use web_time as time;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{Key, NamedKey}, window::WindowBuilder,
window::Window,
}; };
#[path = "util/fill.rs"]
mod fill;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode { enum Mode {
Wait, Wait,
@@ -27,7 +19,7 @@ enum Mode {
const WAIT_TIME: time::Duration = time::Duration::from_millis(100); const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
fn main() -> Result<(), impl std::error::Error> { fn main() {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
println!("Press '1' to switch to Wait mode."); println!("Press '1' to switch to Wait mode.");
@@ -36,8 +28,8 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Press 'R' to toggle request_redraw() calls."); println!("Press 'R' to toggle request_redraw() calls.");
println!("Press 'Esc' to close the window."); println!("Press 'Esc' to close the window.");
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.") .with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
@@ -47,8 +39,8 @@ fn main() -> Result<(), impl std::error::Error> {
let mut wait_cancelled = false; let mut wait_cancelled = false;
let mut close_requested = false; let mut close_requested = false;
event_loop.run(move |event, elwt| { event_loop.run(move |event, _, control_flow| {
use winit::event::StartCause; use winit::event::{ElementState, StartCause, VirtualKeyCode};
println!("{event:?}"); println!("{event:?}");
match event { match event {
Event::NewEvents(start_cause) => { Event::NewEvents(start_cause) => {
@@ -62,67 +54,61 @@ fn main() -> Result<(), impl std::error::Error> {
close_requested = true; close_requested = true;
} }
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
event: input:
KeyEvent { KeyboardInput {
logical_key: key, virtual_keycode: Some(virtual_code),
state: ElementState::Pressed, state: ElementState::Pressed,
.. ..
}, },
.. ..
} => match key.as_ref() { } => match virtual_code {
// WARNING: Consider using `key_without_modifers()` if available on your platform. VirtualKeyCode::Key1 => {
// See the `key_binding` example
Key::Character("1") => {
mode = Mode::Wait; mode = Mode::Wait;
println!("\nmode: {mode:?}\n"); println!("\nmode: {mode:?}\n");
} }
Key::Character("2") => { VirtualKeyCode::Key2 => {
mode = Mode::WaitUntil; mode = Mode::WaitUntil;
println!("\nmode: {mode:?}\n"); println!("\nmode: {mode:?}\n");
} }
Key::Character("3") => { VirtualKeyCode::Key3 => {
mode = Mode::Poll; mode = Mode::Poll;
println!("\nmode: {mode:?}\n"); println!("\nmode: {mode:?}\n");
} }
Key::Character("r") => { VirtualKeyCode::R => {
request_redraw = !request_redraw; request_redraw = !request_redraw;
println!("\nrequest_redraw: {request_redraw}\n"); println!("\nrequest_redraw: {request_redraw}\n");
} }
Key::Named(NamedKey::Escape) => { VirtualKeyCode::Escape => {
close_requested = true; close_requested = true;
} }
_ => (), _ => (),
}, },
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (), _ => (),
}, },
Event::AboutToWait => { Event::MainEventsCleared => {
if request_redraw && !wait_cancelled && !close_requested { if request_redraw && !wait_cancelled && !close_requested {
window.request_redraw(); window.request_redraw();
} }
if close_requested {
control_flow.set_exit();
}
}
Event::RedrawRequested(_window_id) => {}
Event::RedrawEventsCleared => {
match mode { match mode {
Mode::Wait => elwt.set_control_flow(ControlFlow::Wait), Mode::Wait => control_flow.set_wait(),
Mode::WaitUntil => { Mode::WaitUntil => {
if !wait_cancelled { if !wait_cancelled {
elwt.set_control_flow(ControlFlow::WaitUntil( control_flow.set_wait_until(instant::Instant::now() + WAIT_TIME);
time::Instant::now() + WAIT_TIME,
));
} }
} }
Mode::Poll => { Mode::Poll => {
thread::sleep(POLL_SLEEP_TIME); thread::sleep(POLL_SLEEP_TIME);
elwt.set_control_flow(ControlFlow::Poll); control_flow.set_poll();
} }
}; };
if close_requested {
elwt.exit();
}
} }
_ => (), _ => (),
} }
}) });
} }

90
examples/cursor.rs Normal file
View File

@@ -0,0 +1,90 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, WindowEvent},
event_loop::EventLoop,
window::{CursorIcon, WindowBuilder},
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
window.set_title("A fantastic window!");
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::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
control_flow.set_exit();
}
_ => (),
}
});
}
const CURSORS: &[CursorIcon] = &[
CursorIcon::Default,
CursorIcon::Crosshair,
CursorIcon::Hand,
CursorIcon::Arrow,
CursorIcon::Move,
CursorIcon::Text,
CursorIcon::Wait,
CursorIcon::Help,
CursorIcon::Progress,
CursorIcon::NotAllowed,
CursorIcon::ContextMenu,
CursorIcon::Cell,
CursorIcon::VerticalText,
CursorIcon::Alias,
CursorIcon::Copy,
CursorIcon::NoDrop,
CursorIcon::Grab,
CursorIcon::Grabbing,
CursorIcon::AllScroll,
CursorIcon::ZoomIn,
CursorIcon::ZoomOut,
CursorIcon::EResize,
CursorIcon::NResize,
CursorIcon::NeResize,
CursorIcon::NwResize,
CursorIcon::SResize,
CursorIcon::SeResize,
CursorIcon::SwResize,
CursorIcon::WResize,
CursorIcon::EwResize,
CursorIcon::NsResize,
CursorIcon::NeswResize,
CursorIcon::NwseResize,
CursorIcon::ColResize,
CursorIcon::RowResize,
];

70
examples/cursor_grab.rs Normal file
View File

@@ -0,0 +1,70 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
event_loop::EventLoop,
window::{CursorGrabMode, WindowBuilder},
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
.build(&event_loop)
.unwrap();
let mut modifiers = ModifiersState::default();
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::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
..
},
..
} => {
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}");
}
}
WindowEvent::ModifiersChanged(m) => modifiers = m,
_ => (),
},
Event::DeviceEvent { event, .. } => match event {
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {delta:?}"),
DeviceEvent::Button { button, state } => match state {
ElementState::Pressed => println!("mouse button {button} pressed"),
ElementState::Released => println!("mouse button {button} released"),
},
_ => (),
},
_ => (),
}
});
}

View File

@@ -1,26 +1,23 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
#[cfg(not(web_platform))] #[cfg(not(wasm_platform))]
fn main() -> Result<(), impl std::error::Error> { fn main() {
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoopBuilder,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"]
mod fill;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum CustomEvent { enum CustomEvent {
Timer, Timer,
} }
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap(); let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
let window = Window::builder() let _window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
@@ -38,23 +35,21 @@ fn main() -> Result<(), impl std::error::Error> {
} }
}); });
event_loop.run(move |event, elwt| match event { event_loop.run(move |event, _, control_flow| {
Event::UserEvent(event) => println!("user event: {event:?}"), control_flow.set_wait();
Event::WindowEvent {
event: WindowEvent::CloseRequested, match event {
.. Event::UserEvent(event) => println!("user event: {event:?}"),
} => elwt.exit(), Event::WindowEvent {
Event::WindowEvent { event: WindowEvent::CloseRequested,
event: WindowEvent::RedrawRequested, ..
.. } => control_flow.set_exit(),
} => { _ => (),
fill::fill_window(&window);
} }
_ => (), });
})
} }
#[cfg(web_platform)] #[cfg(wasm_platform)]
fn main() { fn main() {
panic!("This example is not supported on web."); panic!("This example is not supported on web.");
} }

75
examples/drag_window.rs Normal file
View File

@@ -0,0 +1,75 @@
#![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");
}

113
examples/fullscreen.rs Normal file
View File

@@ -0,0 +1,113 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::EventLoop;
use winit::window::{Fullscreen, WindowBuilder};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let mut decorations = true;
let mut minimized = false;
let window = WindowBuilder::new()
.with_title("Hello world!")
.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();
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,
..
},
..
} => 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;
} else {
monitor_index = 0;
monitor = elwt.available_monitors().next().expect("no monitor found!");
}
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::D => {
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);
}
_ => (),
},
_ => (),
},
_ => {}
}
});
}

View File

@@ -0,0 +1,86 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, KeyboardInput, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("Your faithful window")
.build(&event_loop)
.unwrap();
let mut close_requested = false;
event_loop.run(move |event, _, control_flow| {
use winit::event::{
ElementState::Released,
VirtualKeyCode::{N, Y},
};
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CloseRequested => {
// `CloseRequested` is sent when the close button on the window is pressed (or
// through whatever other mechanisms the window manager provides for closing a
// window). If you don't handle this event, the close button won't actually do
// anything.
// A common thing to do here is prompt the user if they have unsaved work.
// Creating a proper dialog box for that is far beyond the scope of this
// example, so here we'll just respond to the Y and N keys.
println!("Are you ready to bid your window farewell? [Y/N]");
close_requested = true;
// In applications where you can safely close the window without further
// action from the user, this is generally where you'd handle cleanup before
// 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,
..
},
..
} => {
match virtual_code {
Y => {
if close_requested {
// This is where you'll want to do any cleanup you need.
println!("Buh-bye!");
// For a single-window application like this, you'd normally just
// break out of the event loop here. If you wanted to keep running the
// 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();
}
}
N => {
if close_requested {
println!("Your window will continue to stay by your side.");
close_requested = false;
}
}
_ => (),
}
}
_ => (),
}
}
_ => (),
}
});
}

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

112
examples/ime.rs Normal file
View File

@@ -0,0 +1,112 @@
#![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::{ImePurpose, 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");
println!("Press F3 to cycle through IME purposes.");
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_purpose = ImePurpose::Normal;
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 allowed: {ime_allowed}\n");
}
if input.state == ElementState::Pressed
&& input.virtual_keycode == Some(VirtualKeyCode::F3)
{
ime_purpose = match ime_purpose {
ImePurpose::Normal => ImePurpose::Password,
ImePurpose::Password => ImePurpose::Terminal,
_ => ImePurpose::Normal,
};
window.set_ime_purpose(ime_purpose);
println!("\nIME purpose: {ime_purpose:?}\n");
}
}
_ => (),
}
});
}

View File

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

View File

@@ -4,17 +4,14 @@ use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] fn main() {
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("Mouse Wheel events") .with_title("Mouse Wheel events")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
@@ -34,10 +31,12 @@ In both cases the example window should move like the content of a scroll area i
In other words, the deltas indicate the direction in which to move the content (in this case the window)." In other words, the deltas indicate the direction in which to move the content (in this case the window)."
); );
event_loop.run(move |event, elwt| { event_loop.run(move |event, _, control_flow| {
if let Event::WindowEvent { event, .. } = event { control_flow.set_wait();
match event {
WindowEvent::CloseRequested => elwt.exit(), match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseWheel { delta, .. } => match delta { WindowEvent::MouseWheel { delta, .. } => match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => { winit::event::MouseScrollDelta::LineDelta(x, y) => {
println!("mouse wheel Line Delta: ({x},{y})"); println!("mouse wheel Line Delta: ({x},{y})");
@@ -55,11 +54,9 @@ In other words, the deltas indicate the direction in which to move the content (
window.set_outer_position(pos) window.set_outer_position(pos)
} }
}, },
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (), _ => (),
} },
_ => (),
} }
}) });
} }

199
examples/multithreaded.rs Normal file
View File

@@ -0,0 +1,199 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
fn main() {
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, WindowLevel},
};
const WINDOW_COUNT: usize = 3;
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
SimpleLogger::new().init().unwrap();
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)
.build(&event_loop)
.unwrap();
let mut video_modes: Vec<_> = window.current_monitor().unwrap().video_modes().collect();
let mut video_mode_id = 0usize;
let (tx, rx) = mpsc::channel();
window_senders.insert(window.id(), tx);
thread::spawn(move || {
while let Ok(event) = rx.recv() {
match event {
WindowEvent::Moved { .. } => {
// 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();
video_mode_id = video_mode_id.min(video_modes.len());
let video_mode = video_modes.get(video_mode_id);
// Different monitors may support different video modes,
// and the index we chose previously may now point to a
// completely different video mode, so notify the user
if video_mode != previous_video_mode.as_ref() {
println!(
"Window moved to another monitor, picked video mode: {}",
video_modes.get(video_mode_id).unwrap()
);
}
}
#[allow(deprecated)]
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
..
} => {
window.set_title(&format!("{key:?}"));
let state = !modifiers.shift();
use VirtualKeyCode::*;
match key {
Key1 => window.set_window_level(WindowLevel::AlwaysOnTop),
Key2 => window.set_window_level(WindowLevel::AlwaysOnBottom),
Key3 => window.set_window_level(WindowLevel::Normal),
C => window.set_cursor_icon(match state {
true => CursorIcon::Progress,
false => CursorIcon::Default,
}),
D => window.set_decorations(!state),
// Cycle through video modes
Right | Left => {
video_mode_id = match key {
Left => video_mode_id.saturating_sub(1),
Right => (video_modes.len() - 1).min(video_mode_id + 1),
_ => unreachable!(),
};
println!("Picking video mode: {}", video_modes[video_mode_id]);
}
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()))
}
(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}");
}
}
H => window.set_cursor_visible(!state),
I => {
println!("Info:");
println!("-> outer_position : {:?}", window.outer_position());
println!("-> inner_position : {:?}", window.inner_position());
println!("-> outer_size : {:?}", window.outer_size());
println!("-> inner_size : {:?}", window.inner_size());
println!("-> fullscreen : {:?}", window.fullscreen());
}
L => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE),
false => None,
}),
M => window.set_maximized(state),
P => window.set_outer_position({
let mut position = window.outer_position().unwrap();
let sign = if state { 1 } else { -1 };
position.x += 10 * sign;
position.y += 10 * 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()
}
}
Z => {
window.set_visible(false);
thread::sleep(Duration::from_secs(1));
window.set_visible(true);
}
_ => (),
}
}
_ => (),
}
}
});
}
event_loop.run(move |event, _event_loop, control_flow| {
match !window_senders.is_empty() {
true => control_flow.set_wait(),
false => control_flow.set_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),
..
},
..
} => {
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();
}
}
}
},
_ => {}
}
})
}
#[cfg(wasm_platform)]
fn main() {
panic!("Example not supported on Wasm");
}

61
examples/multiwindow.rs Normal file
View File

@@ -0,0 +1,61 @@
#![allow(clippy::single_match)]
use std::collections::HashMap;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::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();
match event {
Event::WindowEvent { event, window_id } => {
match event {
WindowEvent::CloseRequested => {
println!("Window {window_id:?} has received the signal to close");
// This drops the window, causing it to close.
windows.remove(&window_id);
if windows.is_empty() {
control_flow.set_exit();
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::N),
..
},
is_synthetic: false,
..
} => {
let window = Window::new(event_loop).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
}
_ => (),
}
}
_ => (),
}
})
}

View File

@@ -4,39 +4,38 @@ use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{ElementState, Event, WindowEvent}, event::{ElementState, Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::Window, window::WindowBuilder,
}; };
#[path = "util/fill.rs"] fn main() {
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
event_loop.run(move |event, elwt| { event_loop.run(move |event, _, control_flow| {
println!("{event:?}"); println!("{event:?}");
if let Event::WindowEvent { event, .. } = event { control_flow.set_wait();
match event {
WindowEvent::CloseRequested => elwt.exit(), match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseInput { WindowEvent::MouseInput {
state: ElementState::Released, state: ElementState::Released,
.. ..
} => { } => {
window.request_redraw(); window.request_redraw();
} }
WindowEvent::RedrawRequested => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (), _ => (),
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
} }
_ => (),
} }
}) });
} }

View File

@@ -0,0 +1,48 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
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(wasm_platform)]
fn main() {
unimplemented!() // `Window` can't be sent between threads
}

View File

@@ -3,22 +3,18 @@
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
dpi::LogicalSize, dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey}, window::WindowBuilder,
window::Window,
}; };
#[path = "util/fill.rs"] fn main() {
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new();
let mut resizable = false; let mut resizable = false;
let window = Window::builder() let window = WindowBuilder::new()
.with_title("Hit space to toggle resizability.") .with_title("Hit space to toggle resizability.")
.with_inner_size(LogicalSize::new(600.0, 300.0)) .with_inner_size(LogicalSize::new(600.0, 300.0))
.with_min_inner_size(LogicalSize::new(400.0, 200.0)) .with_min_inner_size(LogicalSize::new(400.0, 200.0))
@@ -27,14 +23,16 @@ fn main() -> Result<(), impl std::error::Error> {
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
event_loop.run(move |event, elwt| { event_loop.run(move |event, _, control_flow| {
if let Event::WindowEvent { event, .. } = event { control_flow.set_wait();
match event {
WindowEvent::CloseRequested => elwt.exit(), match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
event: input:
KeyEvent { KeyboardInput {
physical_key: PhysicalKey::Code(KeyCode::Space), virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released, state: ElementState::Released,
.. ..
}, },
@@ -44,11 +42,9 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Resizable: {resizable}"); println!("Resizable: {resizable}");
window.set_resizable(resizable); window.set_resizable(resizable);
} }
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (), _ => (),
} },
_ => (),
}; };
}) });
} }

71
examples/theme.rs Normal file
View File

@@ -0,0 +1,71 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{Theme, WindowBuilder},
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_theme(Some(Theme::Dark))
.build(&event_loop)
.unwrap();
println!("Initial theme: {:?}", window.theme());
println!("debugging keys:");
println!(" (A) Automatic theme");
println!(" (L) Light theme");
println!(" (D) Dark theme");
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::ThemeChanged(theme),
window_id,
..
} if window_id == window.id() => {
println!("Theme is changed: {theme:?}")
}
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
},
..
},
..
} => match key {
VirtualKeyCode::A => {
println!("Theme was: {:?}", window.theme());
window.set_theme(None);
}
VirtualKeyCode::L => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Light));
}
VirtualKeyCode::D => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Dark));
}
_ => (),
},
_ => (),
}
});
}

42
examples/timer.rs Normal file
View File

@@ -0,0 +1,42 @@
#![allow(clippy::single_match)]
use instant::Instant;
use std::time::Duration;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let timer_length = Duration::new(1, 0);
event_loop.run(move |event, _, control_flow| {
println!("{event:?}");
match event {
Event::NewEvents(StartCause::Init) => {
control_flow.set_wait_until(Instant::now() + timer_length);
}
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
control_flow.set_wait_until(Instant::now() + timer_length);
println!("\nTimer\n");
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
_ => (),
}
});
}

View File

@@ -0,0 +1,46 @@
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("Touchpad gestures")
.build(&event_loop)
.unwrap();
println!("Only supported on macOS at the moment.");
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::TouchpadMagnify { delta, .. } => {
if delta > 0.0 {
println!("Zoomed in {delta}");
} else {
println!("Zoomed out {delta}");
}
}
WindowEvent::SmartMagnify { .. } => {
println!("Smart zoom");
}
WindowEvent::TouchpadRotate { delta, .. } => {
if delta > 0.0 {
println!("Rotated counterclockwise {delta}");
} else {
println!("Rotated clockwise {delta}");
}
}
_ => (),
}
}
});
}

34
examples/transparent.rs Normal file
View File

@@ -0,0 +1,34 @@
#![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_decorations(false)
.with_transparent(true)
.build(&event_loop)
.unwrap();
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(),
_ => (),
}
});
}

View File

@@ -5,7 +5,7 @@ use winit::event_loop::EventLoop;
fn main() { fn main() {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new();
let monitor = match event_loop.primary_monitor() { let monitor = match event_loop.primary_monitor() {
Some(monitor) => monitor, Some(monitor) => monitor,
None => { None => {

90
examples/web.rs Normal file
View File

@@ -0,0 +1,90 @@
#![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(wasm_platform)]
let log_list = wasm::insert_canvas_and_create_log_list(&window);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
#[cfg(wasm_platform)]
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(wasm_platform)]
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 insert_canvas_and_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 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

@@ -1,10 +1,8 @@
#![allow(clippy::disallowed_methods)]
pub fn main() { pub fn main() {
println!("This example must be run with cargo run-wasm --example web_aspect_ratio") println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
} }
#[cfg(web_platform)] #[cfg(wasm_platform)]
mod wasm { mod wasm {
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
@@ -12,9 +10,8 @@ mod wasm {
use winit::{ use winit::{
dpi::PhysicalSize, dpi::PhysicalSize,
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::{ControlFlow, EventLoop},
platform::web::WindowBuilderExtWebSys, window::{Window, WindowBuilder},
window::Window,
}; };
const EXPLANATION: &str = " const EXPLANATION: &str = "
@@ -31,14 +28,13 @@ This example demonstrates the desired future functionality which will possibly b
#[wasm_bindgen(start)] #[wasm_bindgen(start)]
pub fn run() { pub fn run() {
console_log::init_with_level(log::Level::Debug).expect("error initializing logger"); console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")
// When running in a non-wasm environment this would set the window size to 100x100. // When running in a non-wasm environment this would set the window size to 100x100.
// However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page. // However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page.
.with_inner_size(PhysicalSize::new(100, 100)) .with_inner_size(PhysicalSize::new(100, 100))
.with_append(true)
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
@@ -47,14 +43,18 @@ This example demonstrates the desired future functionality which will possibly b
// Render once with the size info we currently have // Render once with the size info we currently have
render_circle(&canvas, window.inner_size()); render_circle(&canvas, window.inner_size());
let _ = event_loop.run(move |event, _| match event { event_loop.run(move |event, _, control_flow| {
Event::WindowEvent { *control_flow = ControlFlow::Wait;
event: WindowEvent::Resized(resize),
window_id, match event {
} if window_id == window.id() => { Event::WindowEvent {
render_circle(&canvas, resize); event: WindowEvent::Resized(resize),
window_id,
} if window_id == window.id() => {
render_circle(&canvas, resize);
}
_ => (),
} }
_ => (),
}); });
} }
@@ -66,10 +66,11 @@ This example demonstrates the desired future functionality which will possibly b
let body = document.body().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. // Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
let canvas = window.canvas().unwrap(); let canvas = window.canvas();
canvas canvas
.style() .style()
.set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;"); .set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;");
body.append_child(&canvas).unwrap();
let explanation = document.create_element("pre").unwrap(); let explanation = document.create_element("pre").unwrap();
explanation.set_text_content(Some(EXPLANATION)); explanation.set_text_content(Some(EXPLANATION));

35
examples/window.rs Normal file
View File

@@ -0,0 +1,35 @@
#![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("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();
}
_ => (),
}
});
}

View File

@@ -0,0 +1,68 @@
#![allow(clippy::single_match)]
// This example is used by developers to test various window functions.
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{DeviceEventFilter, EventLoop},
window::{WindowBuilder, WindowButtons},
};
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(300.0, 300.0))
.build(&event_loop)
.unwrap();
eprintln!("Window Button keys:");
eprintln!(" (F) Toggle close button");
eprintln!(" (G) Toggle maximize button");
eprintln!(" (H) Toggle minimize button");
event_loop.set_device_event_filter(DeviceEventFilter::Never);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
},
..
},
..
} => match key {
VirtualKeyCode::F => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE);
}
VirtualKeyCode::G => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE);
}
VirtualKeyCode::H => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE);
}
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
_ => (),
}
});
}

132
examples/window_debug.rs Normal file
View File

@@ -0,0 +1,132 @@
#![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},
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))
.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");
eprintln!(" (M) Toggle minimized");
eprintln!(" (Q) Quit event loop");
eprintln!(" (V) Toggle visibility");
eprintln!(" (X) Toggle maximized");
let mut minimized = false;
let mut visible = true;
event_loop.set_device_event_filter(DeviceEventFilter::Never);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::DeviceEvent {
event:
DeviceEvent::Key(KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
}),
..
} => match key {
VirtualKeyCode::M => {
if minimized {
minimized = !minimized;
window.set_minimized(minimized);
window.focus_window();
}
}
VirtualKeyCode::V => {
if !visible {
visible = !visible;
window.set_visible(visible);
}
}
_ => (),
},
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
},
..
},
..
} => match key {
VirtualKeyCode::E => {
fn area(size: PhysicalSize<u32>) -> u32 {
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)));
}
}
VirtualKeyCode::P => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
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);
}
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
_ => (),
}
});
}

View File

@@ -2,22 +2,20 @@
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent}, event::{
event_loop::EventLoop, ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
keyboard::Key, },
window::{CursorIcon, ResizeDirection, Window}, event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, ResizeDirection, WindowBuilder},
}; };
const BORDER: f64 = 8.0; const BORDER: f64 = 8.0;
#[path = "util/fill.rs"] fn main() {
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new();
let window = Window::builder() let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0)) .with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0))
.with_min_inner_size(winit::dpi::LogicalSize::new(400.0, 200.0)) .with_min_inner_size(winit::dpi::LogicalSize::new(400.0, 200.0))
.with_decorations(false) .with_decorations(false)
@@ -27,12 +25,12 @@ fn main() -> Result<(), impl std::error::Error> {
let mut border = false; let mut border = false;
let mut cursor_location = None; let mut cursor_location = None;
event_loop.run(move |event, elwt| match event { event_loop.run(move |event, _, control_flow| match event {
Event::NewEvents(StartCause::Init) => { Event::NewEvents(StartCause::Init) => {
eprintln!("Press 'B' to toggle borderless") eprintln!("Press 'B' to toggle borderless")
} }
Event::WindowEvent { event, .. } => match event { Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => elwt.exit(), WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CursorMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
if !window.is_decorated() { if !window.is_decorated() {
let new_location = let new_location =
@@ -40,7 +38,7 @@ fn main() -> Result<(), impl std::error::Error> {
if new_location != cursor_location { if new_location != cursor_location {
cursor_location = new_location; cursor_location = new_location;
window.set_cursor(cursor_direction_icon(cursor_location)) window.set_cursor_icon(cursor_direction_icon(cursor_location))
} }
} }
} }
@@ -52,30 +50,24 @@ fn main() -> Result<(), impl std::error::Error> {
} => { } => {
if let Some(dir) = cursor_location { if let Some(dir) = cursor_location {
let _res = window.drag_resize_window(dir); let _res = window.drag_resize_window(dir);
} else if !window.is_decorated() {
let _res = window.drag_window();
} }
} }
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
event: input:
KeyEvent { KeyboardInput {
state: ElementState::Released, state: ElementState::Released,
logical_key: Key::Character(c), virtual_keycode: Some(VirtualKeyCode::B),
.. ..
}, },
.. ..
} if matches!(c.as_ref(), "B" | "b") => { } => {
border = !border; border = !border;
window.set_decorations(border); window.set_decorations(border);
} }
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (), _ => (),
}, },
_ => (), _ => (),
}) });
} }
fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> CursorIcon { fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> CursorIcon {

View File

@@ -4,15 +4,12 @@ use std::path::Path;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, WindowEvent}, event::Event,
event_loop::EventLoop, event_loop::EventLoop,
window::{Icon, Window}, window::{Icon, WindowBuilder},
}; };
#[path = "util/fill.rs"] fn main() {
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies // You'll have to choose an icon size at your own discretion. On X11, the desired size varies
@@ -23,9 +20,9 @@ fn main() -> Result<(), impl std::error::Error> {
let icon = load_icon(Path::new(path)); let icon = load_icon(Path::new(path));
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new();
let window = Window::builder() let window = WindowBuilder::new()
.with_title("An iconic window!") .with_title("An iconic window!")
// At present, this only does anything on Windows and X11, so if you want to save load // At present, this only does anything on Windows and X11, so if you want to save load
// time, you can put icon loading behind a function that returns `None` on other platforms. // time, you can put icon loading behind a function that returns `None` on other platforms.
@@ -33,18 +30,20 @@ fn main() -> Result<(), impl std::error::Error> {
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
event_loop.run(move |event, elwt| { event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
if let Event::WindowEvent { event, .. } = event { if let Event::WindowEvent { event, .. } = event {
use winit::event::WindowEvent::*;
match event { match event {
WindowEvent::CloseRequested => elwt.exit(), CloseRequested => control_flow.set_exit(),
WindowEvent::DroppedFile(path) => { DroppedFile(path) => {
window.set_window_icon(Some(load_icon(&path))); window.set_window_icon(Some(load_icon(&path)));
} }
WindowEvent::RedrawRequested => fill::fill_window(&window),
_ => (), _ => (),
} }
} }
}) });
} }
fn load_icon(path: &Path) -> Icon { fn load_icon(path: &Path) -> Icon {

View File

@@ -0,0 +1,67 @@
#![allow(clippy::single_match)]
#[cfg(target_os = "macos")]
use winit::platform::macos::{OptionAsAlt, WindowExtMacOS};
#[cfg(target_os = "macos")]
use winit::{
event::ElementState,
event::{Event, MouseButton, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
/// Prints the keyboard events characters received when option_is_alt is true versus false.
/// A left mouse click will toggle option_is_alt.
#[cfg(target_os = "macos")]
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop)
.unwrap();
let mut option_as_alt = window.option_as_alt();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::WindowEvent { event, .. } => match event {
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
option_as_alt = match option_as_alt {
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
OptionAsAlt::Both => OptionAsAlt::None,
};
println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}");
window.set_option_as_alt(option_as_alt);
}
WindowEvent::ReceivedCharacter(c) => println!("ReceivedCharacter: {c:?}"),
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
_ => (),
},
Event::MainEventsCleared => {
window.request_redraw();
}
_ => (),
}
});
}
#[cfg(not(target_os = "macos"))]
fn main() {
println!("This example is only supported on MacOS");
}

View File

@@ -0,0 +1,57 @@
use log::debug;
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::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(LogicalSize::new(128.0, 128.0))
.with_resize_increments(LogicalSize::new(25.0, 25.0))
.build(&event_loop)
.unwrap();
let mut has_increments = true;
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
..
},
..
},
window_id,
} if window_id == window.id() => {
has_increments = !has_increments;
let new_increments = match window.resize_increments() {
Some(_) => None,
None => Some(LogicalSize::new(25.0, 25.0)),
};
debug!("Had increments: {}", new_increments.is_none());
window.set_resize_increments(new_increments);
}
Event::MainEventsCleared => window.request_redraw(),
_ => (),
}
});
}

View File

@@ -0,0 +1,64 @@
#![allow(clippy::single_match)]
// Limit this example to only compatible platforms.
#[cfg(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
android_platform,
orbital_platform,
))]
fn main() {
use std::{thread::sleep, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::run_return::EventLoopExtRunReturn,
window::WindowBuilder,
};
let mut event_loop = EventLoop::new();
SimpleLogger::new().init().unwrap();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let mut quit = false;
while !quit {
event_loop.run_return(|event, _, control_flow| {
control_flow.set_wait();
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{event:?}");
}
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
quit = true;
}
Event::MainEventsCleared => {
control_flow.set_exit();
}
_ => (),
}
});
// Sleep for 1/60 second to simulate rendering
println!("rendering");
sleep(Duration::from_millis(16));
}
}
#[cfg(any(ios_platform, wasm_platform))]
fn main() {
println!("This platform doesn't support run_return.");
}

View File

@@ -2,7 +2,8 @@
name = "run-wasm" name = "run-wasm"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
cargo-run-wasm = "0.2.0" cargo-run-wasm = "0.2.0"

View File

@@ -4,13 +4,13 @@
//! //!
//! Modern computer screens don't have a consistent relationship between resolution and size. //! 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 //! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
//! typically being less than a quarter the size of their desktop counterparts. Moreover, neither //! normally being less than a quarter the size of their desktop counterparts. What's more, neither
//! desktop nor mobile screens have consistent resolutions within their own size classes - common //! 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 //! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
//! and beyond. //! and beyond.
//! //!
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with //! 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 and //! 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 //! 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 //! 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 //! problematic with text rendering, where quarter-sized text becomes a significant legibility
@@ -25,12 +25,12 @@
//! //!
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is //! 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 - //! 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 usually 50 pixels across would be 100 pixels across on a device //! 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`. //! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
//! //!
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's //! 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 //! usually a mistake, since there's no consistent mapping between the scale factor and the screen's
//! actual DPI. Unless printing to a physical medium, you should work in scaled pixels rather //! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather
//! than any DPI-dependent units. //! than any DPI-dependent units.
//! //!
//! ### Position and Size types //! ### Position and Size types
@@ -42,11 +42,11 @@
//! coordinates as input, allowing you to use the most convenient coordinate system for your //! coordinates as input, allowing you to use the most convenient coordinate system for your
//! particular application. //! particular application.
//! //!
//! Winit's position and size types are generic over their exact pixel type, `P`, to allow the //! 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 //! 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 //! 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 //! 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 //! 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 //! 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 //! rounding properly. Note that precision loss will still occur when rounding from a float to an
//! int, although rounding lessens the problem. //! int, although rounding lessens the problem.
@@ -55,35 +55,35 @@
//! //!
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed. //! 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 //! 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 allows you to rescale your application's //! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your
//! UI elements and adjust how the platform changes the window's size to reflect the new scale //! application's UI elements and adjust how the platform changes the window's size to reflect the new
//! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor //! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor
//! can be found by calling [`window.scale_factor()`]. //! can be found by calling [`window.scale_factor()`].
//! //!
//! ## How is the scale factor calculated? //! ## How is the scale factor calculated?
//! //!
//! The scale factor is calculated differently on different platforms: //! Scale factor is calculated differently on different platforms:
//! //!
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the //! - **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 //! 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 //! 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 //! global and changing it requires logging out. See [this article][windows_1] for technical
//! details. //! details.
//! - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific //! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
//! displays. When available, the user may pick a per-monitor scaling factor from a set of //! displays. When this is available, the user may pick a per-monitor scaling factor from a set
//! pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default, //! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
//! but the specific value varies across devices. //! the specific value varies across devices.
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit //! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
//! currently uses a three-pronged approach: //! currently uses a three-pronged approach:
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable if present. //! + 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. //! + 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. //! + 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 //! 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 //! 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`. //! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
//! - **Wayland:** The scale factor is suggested by the compositor for each window individually. The //! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always
//! monitor scale factor may differ from the window scale factor. //! integers (most often 1 or 2).
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range //! - **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 //! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
//! information. //! information.
@@ -97,16 +97,13 @@
//! [points]: https://en.wikipedia.org/wiki/Point_(typography) //! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged //! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged
//! [`window.scale_factor()`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.scale_factor //! [`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 //! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html //! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ //! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities //! [android_1]: https://developer.android.com/training/multiscreen/screendensities
//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio //! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub trait Pixel: Copy + Into<f64> { pub trait Pixel: Copy + Into<f64> {
fn from_f64(f: f64) -> Self; fn from_f64(f: f64) -> Self;
fn cast<P: Pixel>(self) -> P { fn cast<P: Pixel>(self) -> P {
@@ -170,7 +167,7 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the /// 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)>` /// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
/// implementation is provided which does the rounding for you. /// implementation is provided which does the rounding for you.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalPosition<P> { pub struct LogicalPosition<P> {
pub x: P, pub x: P,
@@ -249,7 +246,7 @@ impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
} }
/// A position represented in physical pixels. /// A position represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition<P> { pub struct PhysicalPosition<P> {
pub x: P, pub x: P,
@@ -328,7 +325,7 @@ impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
} }
/// A size represented in logical pixels. /// A size represented in logical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalSize<P> { pub struct LogicalSize<P> {
pub width: P, pub width: P,
@@ -410,7 +407,7 @@ impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
} }
/// A size represented in physical pixels. /// A size represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalSize<P> { pub struct PhysicalSize<P> {
pub width: P, pub width: P,
@@ -584,421 +581,3 @@ impl<P: Pixel> From<LogicalPosition<P>> for Position {
Position::Logical(position.cast()) Position::Logical(position.cast())
} }
} }
#[cfg(test)]
mod tests {
use crate::dpi;
use std::collections::HashSet;
macro_rules! test_pixel_int_impl {
($($name:ident => $ty:ty),*) => {$(
#[test]
fn $name() {
use dpi::Pixel;
assert_eq!(
<$ty as Pixel>::from_f64(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::from_f64(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::from_f64(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::from_f64(37.9),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i32>(37),
37,
);
}
)*};
}
test_pixel_int_impl! {
test_pixel_int_u8 => u8,
test_pixel_int_u16 => u16,
test_pixel_int_u32 => u32,
test_pixel_int_i8 => i8,
test_pixel_int_i16 => i16
}
macro_rules! assert_approx_eq {
($a:expr, $b:expr $(,)?) => {
assert!(
($a - $b).abs() < 0.001,
"{} is not approximately equal to {}",
$a,
$b
);
};
}
macro_rules! test_pixel_float_impl {
($($name:ident => $ty:ty),*) => {$(
#[test]
fn $name() {
use dpi::Pixel;
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.0),
37.0,
);
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.4),
37.4,
);
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.5),
37.5,
);
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.9),
37.9,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37.5),
38,
);
}
)*};
}
test_pixel_float_impl! {
test_pixel_float_f32 => f32,
test_pixel_float_f64 => f64
}
#[test]
fn test_validate_scale_factor() {
assert!(dpi::validate_scale_factor(1.0));
assert!(dpi::validate_scale_factor(2.0));
assert!(dpi::validate_scale_factor(3.0));
assert!(dpi::validate_scale_factor(1.5));
assert!(dpi::validate_scale_factor(0.5));
assert!(!dpi::validate_scale_factor(0.0));
assert!(!dpi::validate_scale_factor(-1.0));
assert!(!dpi::validate_scale_factor(f64::INFINITY));
assert!(!dpi::validate_scale_factor(f64::NAN));
assert!(!dpi::validate_scale_factor(f64::NEG_INFINITY));
}
#[test]
fn test_logical_position() {
let log_pos = dpi::LogicalPosition::new(1.0, 2.0);
assert_eq!(
log_pos.to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
log_pos.to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(2, 4)
);
assert_eq!(log_pos.cast::<u32>(), dpi::LogicalPosition::new(1, 2));
assert_eq!(
log_pos,
dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(1.0, 2.0), 1.0)
);
assert_eq!(
log_pos,
dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(2.0, 4.0), 2.0)
);
assert_eq!(
dpi::LogicalPosition::from((2.0, 2.0)),
dpi::LogicalPosition::new(2.0, 2.0)
);
assert_eq!(
dpi::LogicalPosition::from([2.0, 3.0]),
dpi::LogicalPosition::new(2.0, 3.0)
);
let x: (f64, f64) = log_pos.into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = log_pos.into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_physical_position() {
assert_eq!(
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(1.0, 2.0), 1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(2.0, 4.0), 0.5),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::PhysicalPosition::from((2.0, 2.0)),
dpi::PhysicalPosition::new(2.0, 2.0)
);
assert_eq!(
dpi::PhysicalPosition::from([2.0, 3.0]),
dpi::PhysicalPosition::new(2.0, 3.0)
);
let x: (f64, f64) = dpi::PhysicalPosition::new(1, 2).into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = dpi::PhysicalPosition::new(1, 2).into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_logical_size() {
let log_size = dpi::LogicalSize::new(1.0, 2.0);
assert_eq!(
log_size.to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
log_size.to_physical::<u32>(2.0),
dpi::PhysicalSize::new(2, 4)
);
assert_eq!(log_size.cast::<u32>(), dpi::LogicalSize::new(1, 2));
assert_eq!(
log_size,
dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(1.0, 2.0), 1.0)
);
assert_eq!(
log_size,
dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(2.0, 4.0), 2.0)
);
assert_eq!(
dpi::LogicalSize::from((2.0, 2.0)),
dpi::LogicalSize::new(2.0, 2.0)
);
assert_eq!(
dpi::LogicalSize::from([2.0, 3.0]),
dpi::LogicalSize::new(2.0, 3.0)
);
let x: (f64, f64) = log_size.into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = log_size.into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_physical_size() {
assert_eq!(
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(1.0, 2.0), 1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(2.0, 4.0), 0.5),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::PhysicalSize::from((2.0, 2.0)),
dpi::PhysicalSize::new(2.0, 2.0)
);
assert_eq!(
dpi::PhysicalSize::from([2.0, 3.0]),
dpi::PhysicalSize::new(2.0, 3.0)
);
let x: (f64, f64) = dpi::PhysicalSize::new(1, 2).into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = dpi::PhysicalSize::new(1, 2).into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_size() {
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)),
dpi::Size::Physical(dpi::PhysicalSize::new(1, 2))
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)),
dpi::Size::Logical(dpi::LogicalSize::new(1.0, 2.0))
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(1.0),
dpi::LogicalSize::new(1.0, 2.0)
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(2.0),
dpi::LogicalSize::new(0.5, 1.0)
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0),
dpi::LogicalSize::new(1.0, 2.0)
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(2.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0),
dpi::PhysicalSize::new(2, 4)
);
let small = dpi::Size::Physical((1, 2).into());
let medium = dpi::Size::Logical((3, 4).into());
let medium_physical = dpi::Size::new(medium.to_physical::<u32>(1.0));
let large = dpi::Size::Physical((5, 6).into());
assert_eq!(dpi::Size::clamp(medium, small, large, 1.0), medium_physical);
assert_eq!(dpi::Size::clamp(small, medium, large, 1.0), medium_physical);
assert_eq!(dpi::Size::clamp(large, small, medium, 1.0), medium_physical);
}
#[test]
fn test_position() {
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)),
dpi::Position::Physical(dpi::PhysicalPosition::new(1, 2))
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)),
dpi::Position::Logical(dpi::LogicalPosition::new(1.0, 2.0))
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0),
dpi::LogicalPosition::new(1.0, 2.0)
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0),
dpi::LogicalPosition::new(0.5, 1.0)
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0),
dpi::LogicalPosition::new(1.0, 2.0)
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(2, 4)
);
}
// Eat coverage for the Debug impls et al
#[test]
fn ensure_attrs_do_not_panic() {
let _ = format!("{:?}", dpi::LogicalPosition::<u32>::default().clone());
HashSet::new().insert(dpi::LogicalPosition::<u32>::default());
let _ = format!("{:?}", dpi::PhysicalPosition::<u32>::default().clone());
HashSet::new().insert(dpi::PhysicalPosition::<u32>::default());
let _ = format!("{:?}", dpi::LogicalSize::<u32>::default().clone());
HashSet::new().insert(dpi::LogicalSize::<u32>::default());
let _ = format!("{:?}", dpi::PhysicalSize::<u32>::default().clone());
HashSet::new().insert(dpi::PhysicalSize::<u32>::default());
let _ = format!("{:?}", dpi::Size::Physical((1, 2).into()).clone());
let _ = format!("{:?}", dpi::Position::Physical((1, 2).into()).clone());
}
}

82
src/error.rs Normal file
View File

@@ -0,0 +1,82 @@
use std::{error, fmt};
use crate::platform_impl;
/// An error whose cause it outside Winit's control.
#[derive(Debug)]
pub enum ExternalError {
/// The operation is not supported by the backend.
NotSupported(NotSupportedError),
/// The OS cannot perform the operation.
Os(OsError),
}
/// The error type for when the requested operation is not supported by the backend.
#[derive(Clone)]
pub struct NotSupportedError {
_marker: (),
}
/// The error type for when the OS cannot perform the requested operation.
#[derive(Debug)]
pub struct OsError {
line: u32,
file: &'static str,
error: platform_impl::OsError,
}
impl NotSupportedError {
#[inline]
#[allow(dead_code)]
pub(crate) fn new() -> NotSupportedError {
NotSupportedError { _marker: () }
}
}
impl OsError {
#[allow(dead_code)]
pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError {
OsError { line, file, error }
}
}
#[allow(unused_macros)]
macro_rules! os_error {
($error:expr) => {{
crate::error::OsError::new(line!(), file!(), $error)
}};
}
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.pad(&format!(
"os error at {}:{}: {}",
self.file, self.line, self.error
))
}
}
impl fmt::Display for ExternalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
ExternalError::NotSupported(e) => e.fmt(f),
ExternalError::Os(e) => e.fmt(f),
}
}
}
impl fmt::Debug for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("NotSupportedError").finish()
}
}
impl fmt::Display for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.pad("the requested operation is not supported by Winit")
}
}
impl error::Error for OsError {}
impl error::Error for ExternalError {}
impl error::Error for NotSupportedError {}

1392
src/event.rs Normal file

File diff suppressed because it is too large Load Diff

433
src/event_loop.rs Normal file
View File

@@ -0,0 +1,433 @@
//! 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.
//!
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
use std::marker::PhantomData;
use std::ops::Deref;
use std::{error, fmt};
use instant::{Duration, 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`]
/// 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.
///
/// 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.
///
/// [`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
}
/// 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
/// 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.
/// - **Android:** Must be configured with an `AndroidApp` from `android_main()` by calling
/// [`.with_android_app(app)`] before calling `.build()`.
///
/// [`platform`]: crate::platform
#[cfg_attr(
android,
doc = "[`.with_android_app(app)`]: crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
)]
#[cfg_attr(
not(android),
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[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,
}
}
}
impl<T> fmt::Debug for EventLoop<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoop { .. }")
}
}
impl<T> fmt::Debug for EventLoopWindowTarget<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoopWindowTarget { .. }")
}
}
/// 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`].
///
/// ## 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`].
///
/// [`ExitWithCode`]: Self::ExitWithCode
/// [`Poll`]: Self::Poll
#[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 wait until a timeout has expired.
///
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
/// instead set to [`Wait`].
///
/// [`WaitUntil`]: Self::WaitUntil
/// [`Wait`]: Self::Wait
pub fn set_wait_timeout(&mut self, timeout: Duration) {
match Instant::now().checked_add(timeout) {
Some(instant) => self.set_wait_until(instant),
None => self.set_wait(),
}
}
/// 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;
}
}
impl Default for ControlFlow {
#[inline(always)]
fn default() -> Self {
Self::Poll
}
}
impl EventLoop<()> {
/// Alias for [`EventLoopBuilder::new().build()`].
///
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
#[inline]
pub fn new() -> EventLoop<()> {
EventLoopBuilder::new().build()
}
}
impl Default for EventLoop<()> {
fn default() -> Self {
Self::new()
}
}
impl<T> EventLoop<T> {
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
pub fn with_user_event() -> EventLoop<T> {
EventLoopBuilder::<T>::with_user_event().build()
}
/// Hijacks the calling thread and initializes the winit event loop with the provided
/// closure. Since the closure is `'static`, it must be a `move` closure if it needs to
/// access any data from the calling context.
///
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
/// event loop's behavior.
///
/// Any values not passed to this function will *not* be dropped.
///
/// ## Platform-specific
///
/// - **X11 / Wayland:** The program terminates with exit code 1 if the display server
/// disconnects.
///
/// [`ControlFlow`]: crate::event_loop::ControlFlow
#[inline]
pub fn run<F>(self, event_handler: F) -> !
where
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.
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
event_loop_proxy: self.event_loop.create_proxy(),
}
}
}
impl<T> Deref for EventLoop<T> {
type Target = EventLoopWindowTarget<T>;
fn deref(&self) -> &EventLoopWindowTarget<T> {
self.event_loop.window_target()
}
}
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()
.map(|inner| MonitorHandle { inner })
}
/// 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 / macOS / iOS / Android / Web / Orbital:** Unsupported.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
#[cfg(any(x11_platform, wayland_platform, windows))]
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`].
pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self {
event_loop_proxy: self.event_loop_proxy.clone(),
}
}
}
impl<T: 'static> EventLoopProxy<T> {
/// 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>> {
self.event_loop_proxy.send_event(event)
}
}
impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoopProxy { .. }")
}
}
/// 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);
impl<T> fmt::Display for EventLoopClosed<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Tried to wake up a closed `EventLoop`")
}
}
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
}
}

View File

@@ -49,7 +49,11 @@ impl fmt::Display for BadIcon {
} }
} }
impl Error for BadIcon {} impl Error for BadIcon {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RgbaIcon { pub(crate) struct RgbaIcon {

View File

@@ -7,15 +7,15 @@
//! //!
//! ```no_run //! ```no_run
//! use winit::event_loop::EventLoop; //! use winit::event_loop::EventLoop;
//! let event_loop = EventLoop::new().unwrap(); //! let event_loop = EventLoop::new();
//! ``` //! ```
//! //!
//! Once this is done, there are two ways to create a [`Window`]: //! Once this is done there are two ways to create a [`Window`]:
//! //!
//! - Calling [`Window::new(&event_loop)`][window_new]. //! - Calling [`Window::new(&event_loop)`][window_new].
//! - Calling [`let builder = Window::builder()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build]. //! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
//! //!
//! The first method is the simplest and will give you default values for everything. The second //! The first method is the simplest, and will give you default values for everything. The second
//! method allows you to customize the way your [`Window`] will look and behave by modifying the //! method allows you to customize the way your [`Window`] will look and behave by modifying the
//! fields of the [`WindowBuilder`] object before you create the [`Window`]. //! fields of the [`WindowBuilder`] object before you create the [`Window`].
//! //!
@@ -26,85 +26,64 @@
//! window or a key getting pressed while the window is focused. Devices can generate //! 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. //! [`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 //! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if desired. //! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired.
//! //!
//! You can retrieve events by calling [`EventLoop::run()`]. This function will //! 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 //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`]. //! 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 //! 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 //! 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 //! most other platforms. However, this model can be re-implemented to an extent with
#![cfg_attr( //! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why
any( //! it's discouraged, beyond compatibility reasons.
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
),
doc = "[`EventLoopExtPumpEvents::pump_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_events()]"
)]
#![cfg_attr(
not(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
)),
doc = "`EventLoopExtPumpEvents::pump_events()`"
)]
//! [^1]. See that method's documentation for more reasons about why
//! it's discouraged beyond compatibility reasons.
//! //!
//! //!
//! ```no_run //! ```no_run
//! use winit::{ //! use winit::{
//! event::{Event, WindowEvent}, //! event::{Event, WindowEvent},
//! event_loop::{ControlFlow, EventLoop}, //! event_loop::EventLoop,
//! window::Window, //! window::WindowBuilder,
//! }; //! };
//! //!
//! let event_loop = EventLoop::new().unwrap(); //! let event_loop = EventLoop::new();
//! let window = Window::builder().build(&event_loop).unwrap(); //! let window = WindowBuilder::new().build(&event_loop).unwrap();
//! //!
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't //! event_loop.run(move |event, _, control_flow| {
//! // dispatched any events. This is ideal for games and similar applications. //! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! event_loop.set_control_flow(ControlFlow::Poll); //! // 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. //! // 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 //! // 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. //! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait); //! control_flow.set_wait();
//! //!
//! event_loop.run(move |event, elwt| {
//! match event { //! match event {
//! Event::WindowEvent { //! Event::WindowEvent {
//! event: WindowEvent::CloseRequested, //! event: WindowEvent::CloseRequested,
//! .. //! ..
//! } => { //! } => {
//! println!("The close button was pressed; stopping"); //! println!("The close button was pressed; stopping");
//! elwt.exit(); //! control_flow.set_exit();
//! }, //! },
//! Event::AboutToWait => { //! Event::MainEventsCleared => {
//! // Application update code. //! // Application update code.
//! //!
//! // Queue a RedrawRequested event. //! // Queue a RedrawRequested event.
//! // //! //
//! // You only need to call this if you've determined that you need to redraw in //! // 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 //! // applications which do not always need to. Applications that redraw continuously
//! // can render here instead. //! // can just render here instead.
//! window.request_redraw(); //! window.request_redraw();
//! }, //! },
//! Event::WindowEvent { //! Event::RedrawRequested(_) => {
//! event: WindowEvent::RedrawRequested,
//! ..
//! } => {
//! // Redraw the application. //! // Redraw the application.
//! // //! //
//! // It's preferable for applications that do not render continuously to render in //! // It's preferable for applications that do not render continuously to render in
//! // this event rather than in AboutToWait, since rendering in here allows //! // this event rather than in MainEventsCleared, since rendering in here allows
//! // the program to gracefully handle redraws requested by the OS. //! // the program to gracefully handle redraws requested by the OS.
//! }, //! },
//! _ => () //! _ => ()
@@ -112,16 +91,16 @@
//! }); //! });
//! ``` //! ```
//! //!
//! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be //! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
//! compared to the value returned by [`Window::id()`] to determine which [`Window`] //! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`]
//! dispatched the event. //! dispatched the event.
//! //!
//! # Drawing on the window //! # Drawing on the window
//! //!
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you to //! 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 //! 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 //! [`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. //! 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 //! 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 //! application doesn't render anything to the window by the time the desktop compositor is ready to
@@ -130,46 +109,48 @@
//! window visible only once you're ready to render into it. //! window visible only once you're ready to render into it.
//! //!
//! [`EventLoop`]: event_loop::EventLoop //! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return
//! [`EventLoop::new()`]: event_loop::EventLoop::new //! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [`EventLoop::run()`]: event_loop::EventLoop::run //! [event_loop_run]: event_loop::EventLoop::run
//! [`exit()`]: event_loop::EventLoopWindowTarget::exit //! [`ControlFlow`]: event_loop::ControlFlow
//! [`Exit`]: event_loop::ControlFlow::Exit
//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode
//! [`Window`]: window::Window //! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId //! [`WindowId`]: window::WindowId
//! [`WindowBuilder`]: window::WindowBuilder //! [`WindowBuilder`]: window::WindowBuilder
//! [window_new]: window::Window::new //! [window_new]: window::Window::new
//! [window_builder_new]: window::Window::builder //! [window_builder_new]: window::WindowBuilder::new
//! [window_builder_build]: window::WindowBuilder::build //! [window_builder_build]: window::WindowBuilder::build
//! [`Window::id()`]: window::Window::id //! [window_id_fn]: window::Window::id
//! [`Event`]: event::Event
//! [`WindowEvent`]: event::WindowEvent //! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent //! [`DeviceEvent`]: event::DeviceEvent
//! [`Event::UserEvent`]: event::Event::UserEvent //! [`UserEvent`]: event::Event::UserEvent
//! [`Event::LoopExiting`]: event::Event::LoopExiting //! [`LoopDestroyed`]: event::Event::LoopDestroyed
//! [`platform`]: platform
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle //! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [^1]: `EventLoopExtPumpEvents::pump_events()` is only available on Windows, macOS, Android, X11 and Wayland.
#![deny(rust_2018_idioms)] #![deny(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)] #![deny(clippy::all)]
#![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(feature = "cargo-clippy", deny(warnings))] #![cfg_attr(feature = "cargo-clippy", deny(warnings))]
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc // Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc
#![cfg_attr( #![cfg_attr(docsrs, feature(doc_auto_cfg))]
docsrs,
feature(doc_auto_cfg, doc_cfg_hide),
doc(cfg_hide(doc, docsrs))
)]
#![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_safety_doc)]
#[cfg(feature = "rwh_06")] #[allow(unused_imports)]
pub use rwh_06 as raw_window_handle; #[macro_use]
extern crate log;
#[doc(inline)] #[cfg(feature = "serde")]
pub use winit_core::{dpi, keyboard}; #[macro_use]
extern crate serde;
#[macro_use]
extern crate bitflags;
pub mod dpi;
#[macro_use] #[macro_use]
pub mod error; pub mod error;
mod cursor;
pub mod event; pub mod event;
pub mod event_loop; pub mod event_loop;
mod icon; mod icon;

View File

@@ -10,35 +10,34 @@ use crate::{
platform_impl, platform_impl,
}; };
/// Deprecated! Use `VideoModeHandle` instead.
#[deprecated = "Renamed to `VideoModeHandle`"]
pub type VideoMode = VideoModeHandle;
/// Describes a fullscreen video mode of a monitor. /// Describes a fullscreen video mode of a monitor.
/// ///
/// Can be acquired with [`MonitorHandle::video_modes`]. /// Can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle { pub struct VideoMode {
pub(crate) video_mode: platform_impl::VideoModeHandle, pub(crate) video_mode: platform_impl::VideoMode,
} }
impl std::fmt::Debug for VideoModeHandle { impl std::fmt::Debug for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.video_mode.fmt(f) self.video_mode.fmt(f)
} }
} }
impl PartialOrd for VideoModeHandle { impl PartialOrd for VideoMode {
fn partial_cmp(&self, other: &VideoModeHandle) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl Ord for VideoModeHandle { impl Ord for VideoMode {
fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering { fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
// TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32`
// to `u32` there
let size: (u32, u32) = self.size().into();
let other_size: (u32, u32) = other.size().into();
self.monitor().cmp(&other.monitor()).then( self.monitor().cmp(&other.monitor()).then(
self.size() size.cmp(&other_size)
.cmp(&other.size())
.then( .then(
self.refresh_rate_millihertz() self.refresh_rate_millihertz()
.cmp(&other.refresh_rate_millihertz()) .cmp(&other.refresh_rate_millihertz())
@@ -49,7 +48,7 @@ impl Ord for VideoModeHandle {
} }
} }
impl VideoModeHandle { impl VideoMode {
/// Returns the resolution of this video mode. /// Returns the resolution of this video mode.
#[inline] #[inline]
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
@@ -85,7 +84,7 @@ impl VideoModeHandle {
} }
} }
impl std::fmt::Display for VideoModeHandle { impl std::fmt::Display for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
@@ -112,12 +111,20 @@ impl MonitorHandle {
/// Returns a human-readable name of the monitor. /// Returns a human-readable name of the monitor.
/// ///
/// Returns `None` if the monitor doesn't exist anymore. /// Returns `None` if the monitor doesn't exist anymore.
///
/// ## Platform-specific
///
/// - **Web:** Always returns None
#[inline] #[inline]
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
self.inner.name() self.inner.name()
} }
/// Returns the monitor's resolution. /// Returns the monitor's resolution.
///
/// ## Platform-specific
///
/// - **Web:** Always returns (0,0)
#[inline] #[inline]
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
self.inner.size() self.inner.size()
@@ -125,6 +132,10 @@ impl MonitorHandle {
/// Returns the top-left corner position of the monitor relative to the larger full /// Returns the top-left corner position of the monitor relative to the larger full
/// screen area. /// screen area.
///
/// ## Platform-specific
///
/// - **Web:** Always returns (0,0)
#[inline] #[inline]
pub fn position(&self) -> PhysicalPosition<i32> { pub fn position(&self) -> PhysicalPosition<i32> {
self.inner.position() self.inner.position()
@@ -135,25 +146,22 @@ impl MonitorHandle {
/// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor /// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor
/// the window is on is removed. /// the window is on is removed.
/// ///
/// When using exclusive fullscreen, the refresh rate of the [`VideoModeHandle`] that was /// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to
/// used to enter fullscreen should be used instead. /// enter fullscreen should be used instead.
#[inline] #[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> { pub fn refresh_rate_millihertz(&self) -> Option<u32> {
self.inner.refresh_rate_millihertz() self.inner.refresh_rate_millihertz()
} }
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
/// pixels and vice versa, use [`Window::scale_factor`].
/// ///
/// See the [`dpi`](crate::dpi) module for more information. /// See the [`dpi`](crate::dpi) module for more information.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable. /// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **Wayland:** May differ from [`Window::scale_factor`].
/// - **Android:** Always returns 1.0. /// - **Android:** Always returns 1.0.
/// /// - **Web:** Always returns 1.0
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
#[inline] #[inline]
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor() self.inner.scale_factor()
@@ -165,9 +173,9 @@ impl MonitorHandle {
/// ///
/// - **Web:** Always returns an empty iterator /// - **Web:** Always returns an empty iterator
#[inline] #[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> { pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.inner self.inner
.video_modes() .video_modes()
.map(|video_mode| VideoModeHandle { video_mode }) .map(|video_mode| VideoMode { video_mode })
} }
} }

View File

@@ -3,7 +3,7 @@ use crate::{
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
use self::activity::{AndroidApp, ConfigurationRef, Rect}; use android_activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android. /// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {} pub trait EventLoopExtAndroid {}
@@ -30,7 +30,7 @@ impl WindowExtAndroid for Window {
} }
} }
impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {} impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
/// Additional methods on [`WindowBuilder`] that are specific to Android. /// Additional methods on [`WindowBuilder`] that are specific to Android.
pub trait WindowBuilderExtAndroid {} pub trait WindowBuilderExtAndroid {}
@@ -42,11 +42,6 @@ pub trait EventLoopBuilderExtAndroid {
/// ///
/// This must be called on Android since the `AndroidApp` is not global state. /// This must be called on Android since the `AndroidApp` is not global state.
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self; fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
/// Calling this will mark the volume keys to be manually handled by the application
///
/// Default is to let the operating system handle the volume keys
fn handle_volume_keys(&mut self) -> &mut Self;
} }
impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> { impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
@@ -54,11 +49,6 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
self.platform_specific.android_app = Some(app); self.platform_specific.android_app = Some(app);
self self
} }
fn handle_volume_keys(&mut self) -> &mut Self {
self.platform_specific.ignore_volume_keys = false;
self
}
} }
/// Re-export of the `android_activity` API /// Re-export of the `android_activity` API
@@ -84,21 +74,5 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
/// use winit::platform::android::activity::AndroidApp; /// use winit::platform::android::activity::AndroidApp;
/// ``` /// ```
pub mod activity { pub mod activity {
// We enable the `"native-activity"` feature just so that we can build the
// docs, but it'll be very confusing for users to see the docs with that
// feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page.
#[doc(no_inline)]
#[cfg(android_platform)]
pub use android_activity::*; pub use android_activity::*;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct Rect;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct ConfigurationRef;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct AndroidApp;
} }

View File

@@ -1,8 +1,10 @@
use std::os::raw::c_void; use std::os::raw::c_void;
use objc2::rc::Id;
use crate::{ use crate::{
event_loop::EventLoop, event_loop::EventLoop,
monitor::{MonitorHandle, VideoModeHandle}, monitor::{MonitorHandle, VideoMode},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@@ -20,6 +22,27 @@ impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
/// Additional methods on [`Window`] that are specific to iOS. /// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS { pub trait WindowExtIOS {
/// Returns a pointer to the [`UIWindow`] that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
fn ui_window(&self) -> *mut c_void;
/// Returns a pointer to the [`UIViewController`] that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc
fn ui_view_controller(&self) -> *mut c_void;
/// Returns a pointer to the [`UIView`] that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn ui_view(&self) -> *mut c_void;
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
/// ///
/// The default value is device dependent, and it's recommended GLES or Metal applications set /// The default value is device dependent, and it's recommended GLES or Metal applications set
@@ -66,96 +89,53 @@ pub trait WindowExtIOS {
/// ///
/// The default is to prefer showing the status bar. /// The default is to prefer showing the status bar.
/// ///
/// This sets the value of the /// This changes the value returned by
/// [`prefersStatusBarHidden`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc) /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc),
/// property. /// and then calls
/// /// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc).
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
/// is also called for you.
fn set_prefers_status_bar_hidden(&self, hidden: bool); fn set_prefers_status_bar_hidden(&self, hidden: bool);
/// Sets the preferred status bar style for the [`Window`].
///
/// The default is system-defined.
///
/// This sets the value of the
/// [`preferredStatusBarStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc)
/// property.
///
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
/// is also called for you.
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle);
/// Sets whether the [`Window`] should recognize pinch gestures.
///
/// The default is to not recognize gestures.
fn recognize_pinch_gesture(&self, should_recognize: bool);
/// Sets whether the [`Window`] should recognize double tap gestures.
///
/// The default is to not recognize gestures.
fn recognize_doubletap_gesture(&self, should_recognize: bool);
/// Sets whether the [`Window`] should recognize rotation gestures.
///
/// The default is to not recognize gestures.
fn recognize_rotation_gesture(&self, should_recognize: bool);
} }
impl WindowExtIOS for Window { impl WindowExtIOS for Window {
#[inline]
fn ui_window(&self) -> *mut c_void {
self.window.ui_window()
}
#[inline]
fn ui_view_controller(&self) -> *mut c_void {
self.window.ui_view_controller()
}
#[inline]
fn ui_view(&self) -> *mut c_void {
self.window.ui_view()
}
#[inline] #[inline]
fn set_scale_factor(&self, scale_factor: f64) { fn set_scale_factor(&self, scale_factor: f64) {
self.window self.window.set_scale_factor(scale_factor)
.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
} }
#[inline] #[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window self.window.set_valid_orientations(valid_orientations)
.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
} }
#[inline] #[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) { fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.window self.window.set_prefers_home_indicator_hidden(hidden)
.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
} }
#[inline] #[inline]
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
self.window.maybe_queue_on_main(move |w| { self.window
w.set_preferred_screen_edges_deferring_system_gestures(edges) .set_preferred_screen_edges_deferring_system_gestures(edges)
})
} }
#[inline] #[inline]
fn set_prefers_status_bar_hidden(&self, hidden: bool) { fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.window self.window.set_prefers_status_bar_hidden(hidden)
.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
}
#[inline]
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
self.window
.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
}
#[inline]
fn recognize_pinch_gesture(&self, should_recognize: bool) {
self.window
.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
}
#[inline]
fn recognize_doubletap_gesture(&self, should_recognize: bool) {
self.window
.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
}
#[inline]
fn recognize_rotation_gesture(&self, should_recognize: bool) {
self.window
.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
} }
} }
@@ -168,7 +148,7 @@ pub trait WindowBuilderExtIOS {
/// ///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
fn with_scale_factor(self, scale_factor: f64) -> Self; fn with_scale_factor(self, scale_factor: f64) -> WindowBuilder;
/// Sets the valid orientations for the [`Window`]. /// Sets the valid orientations for the [`Window`].
/// ///
@@ -176,7 +156,7 @@ pub trait WindowBuilderExtIOS {
/// ///
/// This sets the initial value returned by /// This sets the initial value returned by
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc). /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> Self; fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
/// Sets whether the [`Window`] prefers the home indicator hidden. /// Sets whether the [`Window`] prefers the home indicator hidden.
/// ///
@@ -186,7 +166,7 @@ pub trait WindowBuilderExtIOS {
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc). /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
/// ///
/// This only has an effect on iOS 11.0+. /// This only has an effect on iOS 11.0+.
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> Self; fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder;
/// Sets the screen edges for which the system gestures will take a lower priority than the /// Sets the screen edges for which the system gestures will take a lower priority than the
/// application's touch handling. /// application's touch handling.
@@ -195,7 +175,10 @@ pub trait WindowBuilderExtIOS {
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc). /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
/// ///
/// This only has an effect on iOS 11.0+. /// This only has an effect on iOS 11.0+.
fn with_preferred_screen_edges_deferring_system_gestures(self, edges: ScreenEdge) -> Self; fn with_preferred_screen_edges_deferring_system_gestures(
self,
edges: ScreenEdge,
) -> WindowBuilder;
/// Sets whether the [`Window`] prefers the status bar hidden. /// Sets whether the [`Window`] prefers the status bar hidden.
/// ///
@@ -203,53 +186,41 @@ pub trait WindowBuilderExtIOS {
/// ///
/// This sets the initial value returned by /// This sets the initial value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc). /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self; fn with_prefers_status_bar_hidden(self, hidden: bool) -> WindowBuilder;
/// Sets the style of the [`Window`]'s status bar.
///
/// The default is system-defined.
///
/// This sets the initial value returned by
/// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc),
fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
} }
impl WindowBuilderExtIOS for WindowBuilder { impl WindowBuilderExtIOS for WindowBuilder {
#[inline] #[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> Self { fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder {
self.window.platform_specific.scale_factor = Some(scale_factor); self.platform_specific.scale_factor = Some(scale_factor);
self self
} }
#[inline] #[inline]
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self { fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> WindowBuilder {
self.window.platform_specific.valid_orientations = valid_orientations; self.platform_specific.valid_orientations = valid_orientations;
self self
} }
#[inline] #[inline]
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self { fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder {
self.window.platform_specific.prefers_home_indicator_hidden = hidden; self.platform_specific.prefers_home_indicator_hidden = hidden;
self self
} }
#[inline] #[inline]
fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self { fn with_preferred_screen_edges_deferring_system_gestures(
self.window mut self,
.platform_specific edges: ScreenEdge,
) -> WindowBuilder {
self.platform_specific
.preferred_screen_edges_deferring_system_gestures = edges; .preferred_screen_edges_deferring_system_gestures = edges;
self self
} }
#[inline] #[inline]
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self { fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder {
self.window.platform_specific.prefers_status_bar_hidden = hidden; self.platform_specific.prefers_status_bar_hidden = hidden;
self
}
#[inline]
fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
self.window.platform_specific.preferred_status_bar_style = status_bar_style;
self self
} }
} }
@@ -261,33 +232,30 @@ pub trait MonitorHandleExtIOS {
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc /// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
fn ui_screen(&self) -> *mut c_void; fn ui_screen(&self) -> *mut c_void;
/// Returns the preferred [`VideoModeHandle`] for this monitor. /// Returns the preferred [`VideoMode`] for this monitor.
/// ///
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc). /// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
fn preferred_video_mode(&self) -> VideoModeHandle; fn preferred_video_mode(&self) -> VideoMode;
} }
impl MonitorHandleExtIOS for MonitorHandle { impl MonitorHandleExtIOS for MonitorHandle {
#[inline] #[inline]
fn ui_screen(&self) -> *mut c_void { fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen Id::as_ptr(self.inner.ui_screen()) as *mut c_void
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() };
objc2::rc::Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
} }
#[inline] #[inline]
fn preferred_video_mode(&self) -> VideoModeHandle { fn preferred_video_mode(&self) -> VideoMode {
VideoModeHandle { VideoMode {
video_mode: self.inner.preferred_video_mode(), video_mode: self.inner.preferred_video_mode(),
} }
} }
} }
/// Valid orientations for a particular [`Window`]. /// Valid orientations for a particular [`Window`].
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug)]
pub enum ValidOrientations { pub enum ValidOrientations {
/// Excludes `PortraitUpsideDown` on iphone /// Excludes `PortraitUpsideDown` on iphone
#[default]
LandscapeAndPortrait, LandscapeAndPortrait,
Landscape, Landscape,
@@ -296,10 +264,17 @@ pub enum ValidOrientations {
Portrait, Portrait,
} }
impl Default for ValidOrientations {
#[inline]
fn default() -> ValidOrientations {
ValidOrientations::LandscapeAndPortrait
}
}
/// The device [idiom]. /// The device [idiom].
/// ///
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc /// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Idiom { pub enum Idiom {
Unspecified, Unspecified,
@@ -314,26 +289,18 @@ pub enum Idiom {
CarPlay, CarPlay,
} }
bitflags::bitflags! { bitflags! {
/// The [edges] of a screen. /// The [edges] of a screen.
/// ///
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc /// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Default)]
pub struct ScreenEdge: u8 { pub struct ScreenEdge: u8 {
const NONE = 0; const NONE = 0;
const TOP = 1 << 0; const TOP = 1 << 0;
const LEFT = 1 << 1; const LEFT = 1 << 1;
const BOTTOM = 1 << 2; const BOTTOM = 1 << 2;
const RIGHT = 1 << 3; const RIGHT = 1 << 3;
const ALL = ScreenEdge::TOP.bits() | ScreenEdge::LEFT.bits() const ALL = ScreenEdge::TOP.bits | ScreenEdge::LEFT.bits
| ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits(); | ScreenEdge::BOTTOM.bits | ScreenEdge::RIGHT.bits;
} }
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum StatusBarStyle {
#[default]
Default,
LightContent,
DarkContent,
}

View File

@@ -1,7 +1,6 @@
use std::os::raw::c_void; use std::os::raw::c_void;
#[cfg(feature = "serde")] use objc2::rc::Id;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget}, event_loop::{EventLoopBuilder, EventLoopWindowTarget},
@@ -11,6 +10,16 @@ use crate::{
/// Additional methods on [`Window`] that are specific to MacOS. /// Additional methods on [`Window`] that are specific to MacOS.
pub trait WindowExtMacOS { pub trait WindowExtMacOS {
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn ns_window(&self) -> *mut c_void;
/// Returns a pointer to the cocoa `NSView` that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn ns_view(&self) -> *mut c_void;
/// Returns whether or not the window is in simple fullscreen mode. /// Returns whether or not the window is in simple fullscreen mode.
fn simple_fullscreen(&self) -> bool; fn simple_fullscreen(&self) -> bool;
@@ -29,28 +38,6 @@ pub trait WindowExtMacOS {
/// Sets whether or not the window has shadow. /// Sets whether or not the window has shadow.
fn set_has_shadow(&self, has_shadow: bool); fn set_has_shadow(&self, has_shadow: bool);
/// Group windows together by using the same tabbing identifier.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
fn set_tabbing_identifier(&self, identifier: &str);
/// Returns the window's tabbing identifier.
fn tabbing_identifier(&self) -> String;
/// Select next tab.
fn select_next_tab(&self);
/// Select previous tab.
fn select_previous_tab(&self);
/// Select the tab with the given index.
///
/// Will no-op when the index is out of bounds.
fn select_tab_at_index(&self, index: usize);
/// Get the number of tabs in the window tab group.
fn num_tabs(&self) -> usize;
/// Get the window's edit state. /// Get the window's edit state.
/// ///
/// # Examples /// # Examples
@@ -83,97 +70,74 @@ pub trait WindowExtMacOS {
} }
impl WindowExtMacOS for Window { impl WindowExtMacOS for Window {
#[inline]
fn ns_window(&self) -> *mut c_void {
self.window.ns_window()
}
#[inline]
fn ns_view(&self) -> *mut c_void {
self.window.ns_view()
}
#[inline] #[inline]
fn simple_fullscreen(&self) -> bool { fn simple_fullscreen(&self) -> bool {
self.window.maybe_wait_on_main(|w| w.simple_fullscreen()) self.window.simple_fullscreen()
} }
#[inline] #[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window self.window.set_simple_fullscreen(fullscreen)
.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
} }
#[inline] #[inline]
fn has_shadow(&self) -> bool { fn has_shadow(&self) -> bool {
self.window.maybe_wait_on_main(|w| w.has_shadow()) self.window.has_shadow()
} }
#[inline] #[inline]
fn set_has_shadow(&self, has_shadow: bool) { fn set_has_shadow(&self, has_shadow: bool) {
self.window self.window.set_has_shadow(has_shadow)
.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
}
#[inline]
fn set_tabbing_identifier(&self, identifier: &str) {
self.window
.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
}
#[inline]
fn tabbing_identifier(&self) -> String {
self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
}
#[inline]
fn select_next_tab(&self) {
self.window.maybe_queue_on_main(|w| w.select_next_tab())
}
#[inline]
fn select_previous_tab(&self) {
self.window.maybe_queue_on_main(|w| w.select_previous_tab())
}
#[inline]
fn select_tab_at_index(&self, index: usize) {
self.window
.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
}
#[inline]
fn num_tabs(&self) -> usize {
self.window.maybe_wait_on_main(|w| w.num_tabs())
} }
#[inline] #[inline]
fn is_document_edited(&self) -> bool { fn is_document_edited(&self) -> bool {
self.window.maybe_wait_on_main(|w| w.is_document_edited()) self.window.is_document_edited()
} }
#[inline] #[inline]
fn set_document_edited(&self, edited: bool) { fn set_document_edited(&self, edited: bool) {
self.window self.window.set_document_edited(edited)
.maybe_queue_on_main(move |w| w.set_document_edited(edited))
} }
#[inline] #[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
self.window self.window.set_option_as_alt(option_as_alt)
.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
} }
#[inline] #[inline]
fn option_as_alt(&self) -> OptionAsAlt { fn option_as_alt(&self) -> OptionAsAlt {
self.window.maybe_wait_on_main(|w| w.option_as_alt()) self.window.option_as_alt()
} }
} }
/// Corresponds to `NSApplicationActivationPolicy`. /// Corresponds to `NSApplicationActivationPolicy`.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ActivationPolicy { pub enum ActivationPolicy {
/// Corresponds to `NSApplicationActivationPolicyRegular`. /// Corresponds to `NSApplicationActivationPolicyRegular`.
#[default]
Regular, Regular,
/// Corresponds to `NSApplicationActivationPolicyAccessory`. /// Corresponds to `NSApplicationActivationPolicyAccessory`.
Accessory, Accessory,
/// Corresponds to `NSApplicationActivationPolicyProhibited`. /// Corresponds to `NSApplicationActivationPolicyProhibited`.
Prohibited, Prohibited,
} }
impl Default for ActivationPolicy {
fn default() -> Self {
ActivationPolicy::Regular
}
}
/// 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: /// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method:
@@ -184,98 +148,90 @@ pub enum ActivationPolicy {
/// - `with_fullsize_content_view` /// - `with_fullsize_content_view`
pub trait WindowBuilderExtMacOS { pub trait WindowBuilderExtMacOS {
/// Enables click-and-drag behavior for the entire window, not just the titlebar. /// 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) -> Self; fn with_movable_by_window_background(self, movable_by_window_background: bool)
-> WindowBuilder;
/// Makes the titlebar transparent and allows the content to appear behind it. /// Makes the titlebar transparent and allows the content to appear behind it.
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self; fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder;
/// Hides the window title. /// Hides the window title.
fn with_title_hidden(self, title_hidden: bool) -> Self; fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder;
/// Hides the window titlebar. /// Hides the window titlebar.
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self; fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder;
/// Hides the window titlebar buttons. /// Hides the window titlebar buttons.
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self; fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
/// Makes the window content appear behind the titlebar. /// Makes the window content appear behind the titlebar.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self; fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self; fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
fn with_has_shadow(self, has_shadow: bool) -> Self; fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
/// Window accepts click-through mouse events. /// Window accepts click-through mouse events.
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self; fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
/// Defines the window tabbing identifier.
/// /// Set whether the `OptionAsAlt` key is interpreted as the `Alt` modifier.
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
fn with_tabbing_identifier(self, identifier: &str) -> Self;
/// Set how the <kbd>Option</kbd> keys are interpreted.
/// ///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set. /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self; fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> WindowBuilder;
} }
impl WindowBuilderExtMacOS for WindowBuilder { impl WindowBuilderExtMacOS for WindowBuilder {
#[inline] #[inline]
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self { fn with_movable_by_window_background(
self.window.platform_specific.movable_by_window_background = movable_by_window_background; mut self,
movable_by_window_background: bool,
) -> WindowBuilder {
self.platform_specific.movable_by_window_background = movable_by_window_background;
self self
} }
#[inline] #[inline]
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self { fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder {
self.window.platform_specific.titlebar_transparent = titlebar_transparent; self.platform_specific.titlebar_transparent = titlebar_transparent;
self self
} }
#[inline] #[inline]
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self { fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder {
self.window.platform_specific.titlebar_hidden = titlebar_hidden; self.platform_specific.titlebar_hidden = titlebar_hidden;
self self
} }
#[inline] #[inline]
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self { fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder {
self.window.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden; self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
self self
} }
#[inline] #[inline]
fn with_title_hidden(mut self, title_hidden: bool) -> Self { fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder {
self.window.platform_specific.title_hidden = title_hidden; self.platform_specific.title_hidden = title_hidden;
self self
} }
#[inline] #[inline]
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self { fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder {
self.window.platform_specific.fullsize_content_view = fullsize_content_view; self.platform_specific.fullsize_content_view = fullsize_content_view;
self self
} }
#[inline] #[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self { fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
self.window.platform_specific.disallow_hidpi = disallow_hidpi; self.platform_specific.disallow_hidpi = disallow_hidpi;
self self
} }
#[inline] #[inline]
fn with_has_shadow(mut self, has_shadow: bool) -> Self { fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder {
self.window.platform_specific.has_shadow = has_shadow; self.platform_specific.has_shadow = has_shadow;
self self
} }
#[inline] #[inline]
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self { fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> WindowBuilder {
self.window.platform_specific.accepts_first_mouse = accepts_first_mouse; self.platform_specific.accepts_first_mouse = accepts_first_mouse;
self self
} }
#[inline] #[inline]
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self { fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
self.window self.platform_specific.option_as_alt = option_as_alt;
.platform_specific
.tabbing_identifier
.replace(tabbing_identifier.to_string());
self
}
#[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
self.window.platform_specific.option_as_alt = option_as_alt;
self self
} }
} }
@@ -367,11 +323,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
} }
fn ns_screen(&self) -> Option<*mut c_void> { fn ns_screen(&self) -> Option<*mut c_void> {
// SAFETY: We only use the marker to get a pointer self.inner.ns_screen().map(|s| Id::as_ptr(&s) as _)
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() };
self.inner
.ns_screen(mtm)
.map(|s| objc2::rc::Id::as_ptr(&s) as _)
} }
} }
@@ -381,15 +333,9 @@ pub trait EventLoopWindowTargetExtMacOS {
fn hide_application(&self); fn hide_application(&self);
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H. /// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
fn hide_other_applications(&self); fn hide_other_applications(&self);
/// Set whether the system can automatically organize windows into tabs.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1646657-allowsautomaticwindowtabbing>
fn set_allows_automatic_window_tabbing(&self, enabled: bool);
/// Returns whether the system can automatically organize windows into tabs.
fn allows_automatic_window_tabbing(&self) -> bool;
} }
impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
fn hide_application(&self) { fn hide_application(&self) {
self.p.hide_application() self.p.hide_application()
} }
@@ -397,20 +343,12 @@ impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget {
fn hide_other_applications(&self) { fn hide_other_applications(&self) {
self.p.hide_other_applications() self.p.hide_other_applications()
} }
fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
self.p.set_allows_automatic_window_tabbing(enabled);
}
fn allows_automatic_window_tabbing(&self) -> bool {
self.p.allows_automatic_window_tabbing()
}
} }
/// Option as alt behavior. /// Option as alt behavior.
/// ///
/// The default is `None`. /// The default is `None`.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OptionAsAlt { pub enum OptionAsAlt {
/// The left `Option` key is treated as `Alt`. /// The left `Option` key is treated as `Alt`.
@@ -423,6 +361,11 @@ pub enum OptionAsAlt {
Both, Both,
/// No special handling is applied for `Option` key. /// No special handling is applied for `Option` key.
#[default]
None, None,
} }
impl Default for OptionAsAlt {
fn default() -> Self {
OptionAsAlt::None
}
}

43
src/platform/mod.rs Normal file
View File

@@ -0,0 +1,43 @@
//! Contains traits with platform-specific methods in them.
//!
//! Contains the follow OS-specific modules:
//!
//! - `android`
//! - `ios`
//! - `macos`
//! - `unix`
//! - `windows`
//! - `web`
//!
//! And the following platform-specific module:
//!
//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`)
//!
//! However only the module corresponding to the platform you're compiling to will be available.
#[cfg(android_platform)]
pub mod android;
#[cfg(ios_platform)]
pub mod ios;
#[cfg(macos_platform)]
pub mod macos;
#[cfg(orbital_platform)]
pub mod orbital;
#[cfg(wayland_platform)]
pub mod wayland;
#[cfg(wasm_platform)]
pub mod web;
#[cfg(windows_platform)]
pub mod windows;
#[cfg(x11_platform)]
pub mod x11;
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform,
orbital_platform
))]
pub mod run_return;

View File

@@ -0,0 +1,53 @@
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,22 +1,50 @@
use std::os::raw;
use crate::{ use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget}, event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle, monitor::MonitorHandle,
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
use crate::platform_impl::{
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
};
pub use crate::window::Theme; pub use crate::window::Theme;
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Wayland. /// Additional methods on [`EventLoopWindowTarget`] that are specific to Wayland.
pub trait EventLoopWindowTargetExtWayland { pub trait EventLoopWindowTargetExtWayland {
/// True if the [`EventLoopWindowTarget`] uses Wayland. /// True if the [`EventLoopWindowTarget`] uses Wayland.
fn is_wayland(&self) -> bool; fn is_wayland(&self) -> bool;
/// Returns a pointer to the `wl_display` object of wayland that is used by this
/// [`EventLoopWindowTarget`].
///
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
///
/// [`EventLoop`]: crate::event_loop::EventLoop
fn wayland_display(&self) -> Option<*mut raw::c_void>;
} }
impl EventLoopWindowTargetExtWayland for EventLoopWindowTarget { impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
#[inline] #[inline]
fn is_wayland(&self) -> bool { fn is_wayland(&self) -> bool {
self.p.is_wayland() self.p.is_wayland()
} }
#[inline]
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(x11_platform)]
_ => None,
}
}
} }
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland. /// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
@@ -34,7 +62,7 @@ pub trait EventLoopBuilderExtWayland {
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> { impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
#[inline] #[inline]
fn with_wayland(&mut self) -> &mut Self { fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland); self.platform_specific.forced_backend = Some(Backend::Wayland);
self self
} }
@@ -46,9 +74,41 @@ impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
} }
/// Additional methods on [`Window`] that are specific to Wayland. /// Additional methods on [`Window`] that are specific to Wayland.
pub trait WindowExtWayland {} pub trait WindowExtWayland {
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
impl WindowExtWayland for Window {} /// Returns a pointer to the `wl_display` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl WindowExtWayland for Window {
#[inline]
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}
}
}
/// Additional methods on [`WindowBuilder`] that are specific to Wayland. /// Additional methods on [`WindowBuilder`] that are specific to Wayland.
pub trait WindowBuilderExtWayland { pub trait WindowBuilderExtWayland {
@@ -65,10 +125,7 @@ pub trait WindowBuilderExtWayland {
impl WindowBuilderExtWayland for WindowBuilder { impl WindowBuilderExtWayland for WindowBuilder {
#[inline] #[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self { fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.window.platform_specific.name = Some(crate::platform_impl::ApplicationName::new( self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
general.into(),
instance.into(),
));
self self
} }
} }

89
src/platform/web.rs Normal file
View File

@@ -0,0 +1,89 @@
//! 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.
use crate::event::Event;
use crate::event_loop::ControlFlow;
use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget;
use crate::window::WindowBuilder;
use web_sys::HtmlCanvasElement;
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

@@ -5,6 +5,7 @@ use crate::{
event::DeviceId, event::DeviceId,
event_loop::EventLoopBuilder, event_loop::EventLoopBuilder,
monitor::MonitorHandle, monitor::MonitorHandle,
platform_impl::WinIcon,
window::{BadIcon, Icon, Window, WindowBuilder}, window::{BadIcon, Icon, Window, WindowBuilder},
}; };
@@ -14,92 +15,8 @@ pub type HWND = isize;
pub type HMENU = isize; pub type HMENU = isize;
/// Monitor Handle type used by Win32 API /// Monitor Handle type used by Win32 API
pub type HMONITOR = isize; pub type HMONITOR = isize;
/// Instance Handle type used by Win32 API
/// Describes a system-drawn backdrop material of a window. pub type HINSTANCE = isize;
///
/// For a detailed explanation, see [`DWM_SYSTEMBACKDROP_TYPE docs`].
///
/// [`DWM_SYSTEMBACKDROP_TYPE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BackdropType {
/// Corresponds to `DWMSBT_AUTO`.
///
/// Usually draws a default backdrop effect on the title bar.
#[default]
Auto = 0,
/// Corresponds to `DWMSBT_NONE`.
None = 1,
/// Corresponds to `DWMSBT_MAINWINDOW`.
///
/// Draws the Mica backdrop material.
MainWindow = 2,
/// Corresponds to `DWMSBT_TRANSIENTWINDOW`.
///
/// Draws the Background Acrylic backdrop material.
TransientWindow = 3,
/// Corresponds to `DWMSBT_TABBEDWINDOW`.
///
/// Draws the Alt Mica backdrop material.
TabbedWindow = 4,
}
/// Describes a color used by Windows
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Color(u32);
impl Color {
/// Use the system's default color
pub const SYSTEM_DEFAULT: Color = Color(0xFFFFFFFF);
//Special constant only valid for the window border and therefore modeled using Option<Color> for user facing code
const NONE: Color = Color(0xFFFFFFFE);
/// Create a new color from the given RGB values
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self((r as u32) | ((g as u32) << 8) | ((b as u32) << 16))
}
}
impl Default for Color {
fn default() -> Self {
Self::SYSTEM_DEFAULT
}
}
/// Describes how the corners of a window should look like.
///
/// For a detailed explanation, see [`DWM_WINDOW_CORNER_PREFERENCE docs`].
///
/// [`DWM_WINDOW_CORNER_PREFERENCE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
#[repr(i32)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CornerPreference {
/// Corresponds to `DWMWCP_DEFAULT`.
///
/// Let the system decide when to round window corners.
#[default]
Default = 0,
/// Corresponds to `DWMWCP_DONOTROUND`.
///
/// Never round window corners.
DoNotRound = 1,
/// Corresponds to `DWMWCP_ROUND`.
///
/// Round the corners, if appropriate.
Round = 2,
/// Corresponds to `DWMWCP_ROUNDSMALL`.
///
/// Round the corners if appropriate, with a small radius.
RoundSmall = 3,
}
/// Additional methods on `EventLoop` that are specific to Windows. /// Additional methods on `EventLoop` that are specific to Windows.
pub trait EventLoopBuilderExtWindows { pub trait EventLoopBuilderExtWindows {
@@ -194,6 +111,13 @@ impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
/// Additional methods on `Window` that are specific to Windows. /// Additional methods on `Window` that are specific to Windows.
pub trait WindowExtWindows { pub trait WindowExtWindows {
/// Returns the HINSTANCE of the window
fn hinstance(&self) -> HINSTANCE;
/// Returns the native handle that is used by this window.
///
/// The pointer will become invalid when the native window was destroyed.
fn hwnd(&self) -> HWND;
/// Enables or disables mouse and keyboard input to the specified window. /// Enables or disables mouse and keyboard input to the specified window.
/// ///
/// A window must be enabled before it can be activated. /// A window must be enabled before it can be activated.
@@ -219,34 +143,19 @@ pub trait WindowExtWindows {
/// ///
/// Enabling the shadow causes a thin 1px line to appear on the top of the window. /// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn set_undecorated_shadow(&self, shadow: bool); fn set_undecorated_shadow(&self, shadow: bool);
/// Sets system-drawn backdrop type.
///
/// Requires Windows 11 build 22523+.
fn set_system_backdrop(&self, backdrop_type: BackdropType);
/// Sets the color of the window border.
///
/// Supported starting with Windows 11 Build 22000.
fn set_border_color(&self, color: Option<Color>);
/// Sets the background color of the title bar.
///
/// Supported starting with Windows 11 Build 22000.
fn set_title_background_color(&self, color: Option<Color>);
/// Sets the color of the window title.
///
/// Supported starting with Windows 11 Build 22000.
fn set_title_text_color(&self, color: Color);
/// Sets the preferred style of the window corners.
///
/// Supported starting with Windows 11 Build 22000.
fn set_corner_preference(&self, preference: CornerPreference);
} }
impl WindowExtWindows for Window { impl WindowExtWindows for Window {
#[inline]
fn hinstance(&self) -> HINSTANCE {
self.window.hinstance()
}
#[inline]
fn hwnd(&self) -> HWND {
self.window.hwnd()
}
#[inline] #[inline]
fn set_enable(&self, enabled: bool) { fn set_enable(&self, enabled: bool) {
self.window.set_enable(enabled) self.window.set_enable(enabled)
@@ -266,38 +175,9 @@ impl WindowExtWindows for Window {
fn set_undecorated_shadow(&self, shadow: bool) { fn set_undecorated_shadow(&self, shadow: bool) {
self.window.set_undecorated_shadow(shadow) self.window.set_undecorated_shadow(shadow)
} }
#[inline]
fn set_system_backdrop(&self, backdrop_type: BackdropType) {
self.window.set_system_backdrop(backdrop_type)
}
#[inline]
fn set_border_color(&self, color: Option<Color>) {
self.window.set_border_color(color.unwrap_or(Color::NONE))
}
#[inline]
fn set_title_background_color(&self, color: Option<Color>) {
// The windows docs don't mention NONE as a valid options but it works in practice and is useful
// to circumvent the Windows option "Show accent color on title bars and window borders"
self.window
.set_title_background_color(color.unwrap_or(Color::NONE))
}
#[inline]
fn set_title_text_color(&self, color: Color) {
self.window.set_title_text_color(color)
}
#[inline]
fn set_corner_preference(&self, preference: CornerPreference) {
self.window.set_corner_preference(preference)
}
} }
/// Additional methods on `WindowBuilder` that are specific to Windows. /// Additional methods on `WindowBuilder` that are specific to Windows.
#[allow(rustdoc::broken_intra_doc_links)]
pub trait WindowBuilderExtWindows { pub trait WindowBuilderExtWindows {
/// Set an owner to the window to be created. Can be used to create a dialog box, for example. /// Set an owner to the window to be created. Can be used to create a dialog box, for example.
/// This only works when [`WindowBuilder::with_parent_window`] isn't called or set to `None`. /// This only works when [`WindowBuilder::with_parent_window`] isn't called or set to `None`.
@@ -310,7 +190,7 @@ pub trait WindowBuilderExtWindows {
/// - An owned window is hidden when its owner is minimized. /// - 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> /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
fn with_owner_window(self, parent: HWND) -> Self; fn with_owner_window(self, parent: HWND) -> WindowBuilder;
/// Sets a menu on the window to be created. /// Sets a menu on the window to be created.
/// ///
@@ -321,21 +201,14 @@ pub trait WindowBuilderExtWindows {
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look. /// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect. /// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
/// ///
#[cfg_attr( /// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu
platform_windows, fn with_menu(self, menu: HMENU) -> WindowBuilder;
doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
)]
#[cfg_attr(
not(platform_windows),
doc = "[`CreateMenu`]: #only-available-on-windows"
)]
fn with_menu(self, menu: HMENU) -> Self;
/// This sets `ICON_BIG`. A good ceiling here is 256x256. /// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> Self; fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
/// This sets `WS_EX_NOREDIRECTIONBITMAP`. /// This sets `WS_EX_NOREDIRECTIONBITMAP`.
fn with_no_redirection_bitmap(self, flag: bool) -> Self; fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
/// Enables or disables drag and drop support (enabled by default). Will interfere with other crates /// 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 /// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of
@@ -343,131 +216,58 @@ pub trait WindowBuilderExtWindows {
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future. /// 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. /// 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. /// 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) -> Self; fn with_drag_and_drop(self, flag: bool) -> WindowBuilder;
/// Whether show or hide the window icon in the taskbar. /// Whether show or hide the window icon in the taskbar.
fn with_skip_taskbar(self, skip: bool) -> Self; fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
/// Customize the window class name.
fn with_class_name<S: Into<String>>(self, class_name: S) -> Self;
/// Shows or hides the background drop shadow for undecorated windows. /// Shows or hides the background drop shadow for undecorated windows.
/// ///
/// The shadow is hidden by default. /// The shadow is hidden by default.
/// Enabling the shadow causes a thin 1px line to appear on the top of the window. /// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn with_undecorated_shadow(self, shadow: bool) -> Self; fn with_undecorated_shadow(self, shadow: bool) -> WindowBuilder;
/// Sets system-drawn backdrop type.
///
/// Requires Windows 11 build 22523+.
fn with_system_backdrop(self, backdrop_type: BackdropType) -> Self;
/// This sets or removes `WS_CLIPCHILDREN` style.
fn with_clip_children(self, flag: bool) -> Self;
/// Sets the color of the window border.
///
/// Supported starting with Windows 11 Build 22000.
fn with_border_color(self, color: Option<Color>) -> Self;
/// Sets the background color of the title bar.
///
/// Supported starting with Windows 11 Build 22000.
fn with_title_background_color(self, color: Option<Color>) -> Self;
/// Sets the color of the window title.
///
/// Supported starting with Windows 11 Build 22000.
fn with_title_text_color(self, color: Color) -> Self;
/// Sets the preferred style of the window corners.
///
/// Supported starting with Windows 11 Build 22000.
fn with_corner_preference(self, corners: CornerPreference) -> Self;
} }
impl WindowBuilderExtWindows for WindowBuilder { impl WindowBuilderExtWindows for WindowBuilder {
#[inline] #[inline]
fn with_owner_window(mut self, parent: HWND) -> Self { fn with_owner_window(mut self, parent: HWND) -> WindowBuilder {
self.window.platform_specific.owner = Some(parent); self.platform_specific.owner = Some(parent);
self self
} }
#[inline] #[inline]
fn with_menu(mut self, menu: HMENU) -> Self { fn with_menu(mut self, menu: HMENU) -> WindowBuilder {
self.window.platform_specific.menu = Some(menu); self.platform_specific.menu = Some(menu);
self self
} }
#[inline] #[inline]
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self { fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> WindowBuilder {
self.window.platform_specific.taskbar_icon = taskbar_icon; self.platform_specific.taskbar_icon = taskbar_icon;
self self
} }
#[inline] #[inline]
fn with_no_redirection_bitmap(mut self, flag: bool) -> Self { fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder {
self.window.platform_specific.no_redirection_bitmap = flag; self.platform_specific.no_redirection_bitmap = flag;
self self
} }
#[inline] #[inline]
fn with_drag_and_drop(mut self, flag: bool) -> Self { fn with_drag_and_drop(mut self, flag: bool) -> WindowBuilder {
self.window.platform_specific.drag_and_drop = flag; self.platform_specific.drag_and_drop = flag;
self self
} }
#[inline] #[inline]
fn with_skip_taskbar(mut self, skip: bool) -> Self { fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
self.window.platform_specific.skip_taskbar = skip; self.platform_specific.skip_taskbar = skip;
self self
} }
#[inline] #[inline]
fn with_class_name<S: Into<String>>(mut self, class_name: S) -> Self { fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder {
self.window.platform_specific.class_name = class_name.into(); self.platform_specific.decoration_shadow = shadow;
self
}
#[inline]
fn with_undecorated_shadow(mut self, shadow: bool) -> Self {
self.window.platform_specific.decoration_shadow = shadow;
self
}
#[inline]
fn with_system_backdrop(mut self, backdrop_type: BackdropType) -> Self {
self.window.platform_specific.backdrop_type = backdrop_type;
self
}
#[inline]
fn with_clip_children(mut self, flag: bool) -> Self {
self.window.platform_specific.clip_children = flag;
self
}
#[inline]
fn with_border_color(mut self, color: Option<Color>) -> Self {
self.window.platform_specific.border_color = Some(color.unwrap_or(Color::NONE));
self
}
#[inline]
fn with_title_background_color(mut self, color: Option<Color>) -> Self {
self.window.platform_specific.title_background_color = Some(color.unwrap_or(Color::NONE));
self
}
#[inline]
fn with_title_text_color(mut self, color: Color) -> Self {
self.window.platform_specific.title_text_color = Some(color);
self
}
#[inline]
fn with_corner_preference(mut self, corners: CornerPreference) -> Self {
self.window.platform_specific.corner_preference = Some(corners);
self self
} }
} }
@@ -504,7 +304,7 @@ pub trait DeviceIdExtWindows {
impl DeviceIdExtWindows for DeviceId { impl DeviceIdExtWindows for DeviceId {
#[inline] #[inline]
fn persistent_identifier(&self) -> Option<String> { fn persistent_identifier(&self) -> Option<String> {
crate::platform_impl::persistent_identifier(*self) self.0.persistent_identifier()
} }
} }
@@ -535,12 +335,12 @@ impl IconExtWindows for Icon {
path: P, path: P,
size: Option<PhysicalSize<u32>>, size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> { ) -> Result<Self, BadIcon> {
let win_icon = crate::platform_impl::WinIcon::from_path(path, size)?; let win_icon = WinIcon::from_path(path, size)?;
Ok(Icon { inner: win_icon }) Ok(Icon { inner: win_icon })
} }
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> { fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?; let win_icon = WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon }) Ok(Icon { inner: win_icon })
} }
} }

233
src/platform/x11.rs Normal file
View File

@@ -0,0 +1,233 @@
use std::os::raw;
use std::ptr;
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
use crate::dpi::Size;
use crate::platform_impl::{
x11::ffi::XVisualInfo, ApplicationName, Backend, Window as LinuxWindow, XLIB_ERROR_HOOKS,
};
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
/// 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
pub type XlibErrorHook =
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
/// 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`].
///
/// **Be aware that your hook is always invoked and returning `true` from it will
/// prevent `winit` from getting the error itself. It's wise to always return
/// `false` if you're not initiated the `Sync`.**
///
/// [`unsafe`]: https://www.remlab.net/op/xlib.shtml
#[inline]
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook.
unsafe {
XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
}
}
/// Additional methods on [`EventLoopWindowTarget`] that are specific to X11.
pub trait EventLoopWindowTargetExtX11 {
/// True if the [`EventLoopWindowTarget`] uses X11.
fn is_x11(&self) -> bool;
}
impl<T> EventLoopWindowTargetExtX11 for EventLoopWindowTarget<T> {
#[inline]
fn is_x11(&self) -> bool {
!self.p.is_wayland()
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to X11.
pub trait EventLoopBuilderExtX11 {
/// Force using X11.
fn with_x11(&mut self) -> &mut Self;
/// 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.
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
#[inline]
fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::X);
self
}
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
}
/// Additional methods on [`Window`] that are specific to X11.
pub trait WindowExtX11 {
/// Returns the ID of the [`Window`] xlib object that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
fn xlib_window(&self) -> Option<raw::c_ulong>;
/// Returns a pointer to the `Display` object of xlib that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn xlib_display(&self) -> Option<*mut raw::c_void>;
fn xlib_screen_id(&self) -> Option<raw::c_int>;
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
}
impl WindowExtX11 for Window {
#[inline]
fn xlib_window(&self) -> Option<raw::c_ulong> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_window()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xlib_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_display()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
#[cfg(wayland_platform)]
_ => None,
}
}
}
/// Additional methods on [`WindowBuilder`] that are specific to X11.
pub trait WindowBuilderExtX11 {
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
fn with_x11_screen(self, screen_id: i32) -> Self;
/// Build window with the given `general` and `instance` names.
///
/// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
///
/// 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.
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 base size hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder;
/// # use winit::platform::x11::WindowBuilderExtX11;
/// // 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));
/// ```
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
}
impl WindowBuilderExtX11 for WindowBuilder {
#[inline]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
{
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
}
self
}
#[inline]
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()));
self
}
#[inline]
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.override_redirect = override_redirect;
self
}
#[inline]
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11_window_types = x11_window_types;
self
}
#[inline]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self
}
}
/// Additional methods on `MonitorHandle` that are specific to X11.
pub trait MonitorHandleExtX11 {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorHandleExtX11 for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,9 @@
use std::{ use std::{
cell::{RefCell, RefMut}, cell::{RefCell, RefMut},
collections::HashSet, collections::HashSet,
fmt, mem, mem,
os::raw::c_void, os::raw::c_void,
ptr, ptr,
sync::{Arc, Mutex},
time::Instant, time::Instant,
}; };
@@ -16,20 +15,23 @@ use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
}; };
use icrate::Foundation::{ use objc2::foundation::{CGRect, CGSize, NSInteger, NSProcessInfo};
CGRect, CGSize, MainThreadMarker, NSInteger, NSOperatingSystemVersion, NSProcessInfo, use objc2::rc::{Id, Shared};
}; use objc2::runtime::Object;
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{msg_send, sel}; use objc2::{msg_send, sel};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use super::uikit::UIView; use super::uikit::UIView;
use super::view::WinitUIWindow; use super::view::WinitUIWindow;
use crate::{ use crate::{
dpi::PhysicalSize, dpi::LogicalSize,
event::{Event, InnerSizeWriter, StartCause, WindowEvent}, event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget}, event_loop::ControlFlow,
platform_impl::platform::{
event_loop::{EventHandler, EventProxy, EventWrapper, Never},
ffi::NSOperatingSystemVersion,
},
window::WindowId as RootWindowId,
}; };
macro_rules! bug { macro_rules! bug {
@@ -44,46 +46,9 @@ macro_rules! bug_assert {
}; };
} }
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;
pub(crate) struct EventLoopHandler {
#[allow(clippy::type_complexity)]
pub(crate) handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootEventLoopWindowTarget)>,
pub(crate) event_loop: RootEventLoopWindowTarget,
}
impl fmt::Debug for EventLoopHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopHandler")
.field("handler", &"...")
.field("event_loop", &self.event_loop)
.finish()
}
}
impl EventLoopHandler {
fn handle_event(&mut self, event: Event<HandlePendingUserEvents>) {
(self.handler)(event, &self.event_loop)
}
}
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event<HandlePendingUserEvents>),
ScaleFactorChanged(ScaleFactorChanged),
}
#[derive(Debug)]
pub struct ScaleFactorChanged {
pub(super) window: Id<WinitUIWindow>,
pub(super) suggested_size: PhysicalSize<u32>,
pub(super) scale_factor: f64,
}
enum UserCallbackTransitionResult<'a> { enum UserCallbackTransitionResult<'a> {
Success { Success {
handler: EventLoopHandler, event_handler: Box<dyn EventHandler>,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
processing_redraws: bool, processing_redraws: bool,
}, },
@@ -92,14 +57,10 @@ enum UserCallbackTransitionResult<'a> {
}, },
} }
fn is_redraw(event: &Event<HandlePendingUserEvents>) -> bool { impl Event<'static, Never> {
matches!( fn is_redraw(&self) -> bool {
event, matches!(self, Event::RedrawRequested(_))
Event::WindowEvent { }
event: WindowEvent::RedrawRequested,
..
}
)
} }
// this is the state machine for the app lifecycle // this is the state machine for the app lifecycle
@@ -107,41 +68,41 @@ fn is_redraw(event: &Event<HandlePendingUserEvents>) -> bool {
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] #[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl { enum AppStateImpl {
NotLaunched { NotLaunched {
queued_windows: Vec<Id<WinitUIWindow>>, queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_events: Vec<EventWrapper>, queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
}, },
Launching { Launching {
queued_windows: Vec<Id<WinitUIWindow>>, queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_events: Vec<EventWrapper>, queued_events: Vec<EventWrapper>,
queued_handler: EventLoopHandler, queued_event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
}, },
ProcessingEvents { ProcessingEvents {
handler: EventLoopHandler, event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
}, },
// special state to deal with reentrancy and prevent mutable aliasing. // special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback { InUserCallback {
queued_events: Vec<EventWrapper>, queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
}, },
ProcessingRedraws { ProcessingRedraws {
handler: EventLoopHandler, event_handler: Box<dyn EventHandler>,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
}, },
Waiting { Waiting {
waiting_handler: EventLoopHandler, waiting_event_handler: Box<dyn EventHandler>,
start: Instant, start: Instant,
}, },
PollFinished { PollFinished {
waiting_handler: EventLoopHandler, waiting_event_handler: Box<dyn EventHandler>,
}, },
Terminated, Terminated,
} }
pub(crate) struct AppState { struct AppState {
// This should never be `None`, except for briefly during a state transition. // This should never be `None`, except for briefly during a state transition.
app_state: Option<AppStateImpl>, app_state: Option<AppStateImpl>,
control_flow: ControlFlow, control_flow: ControlFlow,
@@ -149,18 +110,24 @@ pub(crate) struct AppState {
} }
impl AppState { impl AppState {
pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> { // requires main thread
unsafe fn get_mut() -> RefMut<'static, AppState> {
// basically everything in UIKit requires the main thread, so it's pointless to use the // basically everything in UIKit requires the main thread, so it's pointless to use the
// std::sync APIs. // std::sync APIs.
// must be mut because plain `static` requires `Sync` // must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None); static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
let mut guard = unsafe { APP_STATE.borrow_mut() }; if cfg!(debug_assertions) {
assert_main_thread!(
"bug in winit: `AppState::get_mut()` can only be called on the main thread"
);
}
let mut guard = APP_STATE.borrow_mut();
if guard.is_none() { if guard.is_none() {
#[inline(never)] #[inline(never)]
#[cold] #[cold]
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) { unsafe fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() }); let waker = EventLoopWaker::new(CFRunLoopGetMain());
**guard = Some(AppState { **guard = Some(AppState {
app_state: Some(AppStateImpl::NotLaunched { app_state: Some(AppStateImpl::NotLaunched {
queued_windows: Vec::new(), queued_windows: Vec::new(),
@@ -171,7 +138,7 @@ impl AppState {
waker, waker,
}); });
} }
init_guard(&mut guard); init_guard(&mut guard)
} }
RefMut::map(guard, |state| state.as_mut().unwrap()) RefMut::map(guard, |state| state.as_mut().unwrap())
} }
@@ -220,11 +187,7 @@ impl AppState {
) )
} }
fn has_terminated(&self) -> bool { fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
matches!(self.state(), AppStateImpl::Terminated)
}
fn will_launch_transition(&mut self, queued_handler: EventLoopHandler) {
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() { let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::NotLaunched { AppStateImpl::NotLaunched {
queued_windows, queued_windows,
@@ -236,29 +199,31 @@ impl AppState {
self.set_state(AppStateImpl::Launching { self.set_state(AppStateImpl::Launching {
queued_windows, queued_windows,
queued_events, queued_events,
queued_handler, queued_event_handler,
queued_gpu_redraws, queued_gpu_redraws,
}); });
} }
fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) { fn did_finish_launching_transition(
let (windows, events, handler, queued_gpu_redraws) = match self.take_state() { &mut self,
) -> (Vec<Id<WinitUIWindow, Shared>>, Vec<EventWrapper>) {
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching { AppStateImpl::Launching {
queued_windows, queued_windows,
queued_events, queued_events,
queued_handler, queued_event_handler,
queued_gpu_redraws, queued_gpu_redraws,
} => ( } => (
queued_windows, queued_windows,
queued_events, queued_events,
queued_handler, queued_event_handler,
queued_gpu_redraws, queued_gpu_redraws,
), ),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingEvents { self.set_state(AppStateImpl::ProcessingEvents {
handler, event_handler,
active_control_flow: self.control_flow, active_control_flow: ControlFlow::Poll,
queued_gpu_redraws, queued_gpu_redraws,
}); });
(windows, events) (windows, events)
@@ -267,23 +232,28 @@ impl AppState {
fn wakeup_transition(&mut self) -> Option<EventWrapper> { fn wakeup_transition(&mut self) -> Option<EventWrapper> {
// before `AppState::did_finish_launching` is called, pretend there is no running // before `AppState::did_finish_launching` is called, pretend there is no running
// event loop. // event loop.
if !self.has_launched() || self.has_terminated() { if !self.has_launched() {
return None; return None;
} }
let (handler, event) = match (self.control_flow, self.take_state()) { let (event_handler, event) = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => ( (
waiting_handler, ControlFlow::Poll,
AppStateImpl::PollFinished {
waiting_event_handler,
},
) => (
waiting_event_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)), EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)),
), ),
( (
ControlFlow::Wait, ControlFlow::Wait,
AppStateImpl::Waiting { AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}, },
) => ( ) => (
waiting_handler, waiting_event_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start, start,
requested_resume: None, requested_resume: None,
@@ -292,7 +262,7 @@ impl AppState {
( (
ControlFlow::WaitUntil(requested_resume), ControlFlow::WaitUntil(requested_resume),
AppStateImpl::Waiting { AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}, },
) => { ) => {
@@ -307,13 +277,14 @@ impl AppState {
requested_resume: Some(requested_resume), requested_resume: Some(requested_resume),
})) }))
}; };
(waiting_handler, event) (waiting_event_handler, event)
} }
(ControlFlow::ExitWithCode(_), _) => bug!("unexpected `ControlFlow` `Exit`"),
s => bug!("`EventHandler` unexpectedly woke up {:?}", s), s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingEvents { self.set_state(AppStateImpl::ProcessingEvents {
handler, event_handler,
queued_gpu_redraws: Default::default(), queued_gpu_redraws: Default::default(),
active_control_flow: self.control_flow, active_control_flow: self.control_flow,
}); });
@@ -358,20 +329,25 @@ impl AppState {
} }
} }
let (handler, queued_gpu_redraws, active_control_flow, processing_redraws) = let (event_handler, queued_gpu_redraws, active_control_flow, processing_redraws) =
match self.take_state() { match self.take_state() {
AppStateImpl::Launching { .. } AppStateImpl::Launching { .. }
| AppStateImpl::NotLaunched { .. } | AppStateImpl::NotLaunched { .. }
| AppStateImpl::InUserCallback { .. } => unreachable!(), | AppStateImpl::InUserCallback { .. } => unreachable!(),
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents {
handler, event_handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
} => (handler, queued_gpu_redraws, active_control_flow, false), } => (
AppStateImpl::ProcessingRedraws { event_handler,
handler, queued_gpu_redraws,
active_control_flow, active_control_flow,
} => (handler, Default::default(), active_control_flow, true), false,
),
AppStateImpl::ProcessingRedraws {
event_handler,
active_control_flow,
} => (event_handler, Default::default(), active_control_flow, true),
AppStateImpl::PollFinished { .. } AppStateImpl::PollFinished { .. }
| AppStateImpl::Waiting { .. } | AppStateImpl::Waiting { .. }
| AppStateImpl::Terminated => unreachable!(), | AppStateImpl::Terminated => unreachable!(),
@@ -381,46 +357,49 @@ impl AppState {
queued_gpu_redraws, queued_gpu_redraws,
}); });
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success {
handler, event_handler,
active_control_flow, active_control_flow,
processing_redraws, processing_redraws,
} }
} }
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> { fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow, Shared>> {
let (handler, queued_gpu_redraws, active_control_flow) = match self.take_state() { let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents {
handler, event_handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
} => (handler, queued_gpu_redraws, active_control_flow), } => (event_handler, queued_gpu_redraws, active_control_flow),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingRedraws { self.set_state(AppStateImpl::ProcessingRedraws {
handler, event_handler,
active_control_flow, active_control_flow,
}); });
queued_gpu_redraws queued_gpu_redraws
} }
fn events_cleared_transition(&mut self) { fn events_cleared_transition(&mut self) {
if !self.has_launched() || self.has_terminated() { if !self.has_launched() {
return; return;
} }
let (waiting_handler, old) = match self.take_state() { let (waiting_event_handler, old) = match self.take_state() {
AppStateImpl::ProcessingRedraws { AppStateImpl::ProcessingRedraws {
handler, event_handler,
active_control_flow, active_control_flow,
} => (handler, active_control_flow), } => (event_handler, active_control_flow),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
let new = self.control_flow; let new = self.control_flow;
match (old, new) { match (old, new) {
(ControlFlow::Poll, ControlFlow::Poll) => self.set_state(AppStateImpl::PollFinished {
waiting_event_handler,
}),
(ControlFlow::Wait, ControlFlow::Wait) => { (ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}); });
} }
@@ -429,14 +408,14 @@ impl AppState {
{ {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}); });
} }
(_, ControlFlow::Wait) => { (_, ControlFlow::Wait) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}); });
self.waker.stop() self.waker.stop()
@@ -444,37 +423,41 @@ impl AppState {
(_, ControlFlow::WaitUntil(new_instant)) => { (_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_handler, waiting_event_handler,
start, start,
}); });
self.waker.start_at(new_instant) self.waker.start_at(new_instant)
} }
// Unlike on macOS, handle Poll to Poll transition here to call the waker
(_, ControlFlow::Poll) => { (_, ControlFlow::Poll) => {
self.set_state(AppStateImpl::PollFinished { waiting_handler }); self.set_state(AppStateImpl::PollFinished {
waiting_event_handler,
});
self.waker.start() self.waker.start()
} }
(_, ControlFlow::ExitWithCode(_)) => {
// 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");
self.control_flow = old
}
} }
} }
fn terminated_transition(&mut self) -> EventLoopHandler { fn terminated_transition(&mut self) -> Box<dyn EventHandler> {
match self.replace_state(AppStateImpl::Terminated) { match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { handler, .. } => handler, AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler,
s => bug!("`LoopExiting` happened while not processing events {:?}", s), s => bug!(
"`LoopDestroyed` happened while not processing events {:?}",
s
),
} }
} }
pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) {
self.control_flow = control_flow;
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow
}
} }
pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id<WinitUIWindow>) { // requires main thread and window is a UIWindow
let mut this = AppState::get_mut(mtm); // retains window
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
let mut this = AppState::get_mut();
match this.state_mut() { match this.state_mut() {
&mut AppStateImpl::NotLaunched { &mut AppStateImpl::NotLaunched {
ref mut queued_windows, ref mut queued_windows,
@@ -494,8 +477,10 @@ pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id<WinitUIWindow>)
window.makeKeyAndVisible(); window.makeKeyAndVisible();
} }
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUIWindow>) { // requires main thread and window is a UIWindow
let mut this = AppState::get_mut(mtm); // retains window
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow, Shared>) {
let mut this = AppState::get_mut();
match this.state_mut() { match this.state_mut() {
&mut AppStateImpl::NotLaunched { &mut AppStateImpl::NotLaunched {
ref mut queued_gpu_redraws, ref mut queued_gpu_redraws,
@@ -524,17 +509,24 @@ pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUI
} }
} }
pub(crate) fn will_launch(mtm: MainThreadMarker, queued_handler: EventLoopHandler) { // requires main thread
AppState::get_mut(mtm).will_launch_transition(queued_handler) pub unsafe fn will_launch(queued_event_handler: Box<dyn EventHandler>) {
AppState::get_mut().will_launch_transition(queued_event_handler)
} }
pub fn did_finish_launching(mtm: MainThreadMarker) { // requires main thread
let mut this = AppState::get_mut(mtm); pub unsafe fn did_finish_launching() {
let mut this = AppState::get_mut();
let windows = match this.state_mut() { let windows = match this.state_mut() {
AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows), AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
// start waking up the event loop now!
bug_assert!(
this.control_flow == ControlFlow::Poll,
"unexpectedly not setup to `Poll` on launch!"
);
this.waker.start(); this.waker.start();
// have to drop RefMut because the window setup code below can trigger new events // have to drop RefMut because the window setup code below can trigger new events
@@ -552,7 +544,7 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
// completed. This may result in incorrect visual appearance. // completed. This may result in incorrect visual appearance.
// ``` // ```
let screen = window.screen(); let screen = window.screen();
let _: () = unsafe { msg_send![&window, setScreen: ptr::null::<AnyObject>()] }; let _: () = msg_send![&window, setScreen: ptr::null::<Object>()];
window.setScreen(&screen); window.setScreen(&screen);
let controller = window.rootViewController(); let controller = window.rootViewController();
@@ -562,13 +554,13 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
window.makeKeyAndVisible(); window.makeKeyAndVisible();
} }
let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition(); let (windows, events) = AppState::get_mut().did_finish_launching_transition();
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents( let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init, StartCause::Init,
))) )))
.chain(events); .chain(events);
handle_nonuser_events(mtm, events); handle_nonuser_events(events);
// the above window dance hack, could possibly trigger new windows to be created. // the above window dance hack, could possibly trigger new windows to be created.
// we can just set those windows up normally, as they were created after didFinishLaunching // we can just set those windows up normally, as they were created after didFinishLaunching
@@ -577,64 +569,63 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
} }
} }
// requires main thread
// AppState::did_finish_launching handles the special transition `Init` // AppState::did_finish_launching handles the special transition `Init`
pub fn handle_wakeup_transition(mtm: MainThreadMarker) { pub unsafe fn handle_wakeup_transition() {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut();
let wakeup_event = match this.wakeup_transition() { let wakeup_event = match this.wakeup_transition() {
None => return, None => return,
Some(wakeup_event) => wakeup_event, Some(wakeup_event) => wakeup_event,
}; };
drop(this); drop(this);
handle_nonuser_event(mtm, wakeup_event) handle_nonuser_event(wakeup_event)
} }
pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) { // requires main thread
handle_nonuser_events(mtm, std::iter::once(event)) pub(crate) unsafe fn handle_nonuser_event(event: EventWrapper) {
handle_nonuser_events(std::iter::once(event))
} }
pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>( // requires main thread
mtm: MainThreadMarker, pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
events: I, let mut this = AppState::get_mut();
) { let (mut event_handler, active_control_flow, processing_redraws) =
let mut this = AppState::get_mut(mtm);
if this.has_terminated() {
return;
}
let (mut handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() { match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
queued_events.extend(events); queued_events.extend(events);
return; return;
} }
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success {
handler, event_handler,
active_control_flow, active_control_flow,
processing_redraws, processing_redraws,
} => (handler, active_control_flow, processing_redraws), } => (event_handler, active_control_flow, processing_redraws),
}; };
let mut control_flow = this.control_flow;
drop(this); drop(this);
for wrapper in events { for wrapper in events {
match wrapper { match wrapper {
EventWrapper::StaticEvent(event) => { EventWrapper::StaticEvent(event) => {
if !processing_redraws && is_redraw(&event) { if !processing_redraws && event.is_redraw() {
log::info!("processing `RedrawRequested` during the main event loop"); log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !is_redraw(&event) { } else if processing_redraws && !event.is_redraw() {
log::warn!( log::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}", "processing non `RedrawRequested` event after the main event loop: {:#?}",
event event
); );
} }
handler.handle_event(event) event_handler.handle_nonuser_event(event, &mut control_flow)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
} }
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
} }
} }
loop { loop {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut();
let queued_events = match this.state_mut() { let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { &mut AppStateImpl::InUserCallback {
ref mut queued_events, ref mut queued_events,
@@ -656,16 +647,17 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
"redraw queued while processing redraws" "redraw queued while processing redraws"
); );
AppStateImpl::ProcessingRedraws { AppStateImpl::ProcessingRedraws {
handler, event_handler,
active_control_flow, active_control_flow,
} }
} else { } else {
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents {
handler, event_handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
} }
}); });
this.control_flow = control_flow;
break; break;
} }
drop(this); drop(this);
@@ -673,44 +665,48 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
for wrapper in queued_events { for wrapper in queued_events {
match wrapper { match wrapper {
EventWrapper::StaticEvent(event) => { EventWrapper::StaticEvent(event) => {
if !processing_redraws && is_redraw(&event) { if !processing_redraws && event.is_redraw() {
log::info!("processing `RedrawRequested` during the main event loop"); log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !is_redraw(&event) { } else if processing_redraws && !event.is_redraw() {
log::warn!( log::warn!(
"processing non-`RedrawRequested` event after the main event loop: {:#?}", "processing non-`RedrawRequested` event after the main event loop: {:#?}",
event event
); );
} }
handler.handle_event(event) event_handler.handle_nonuser_event(event, &mut control_flow)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
} }
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
} }
} }
} }
} }
fn handle_user_events(mtm: MainThreadMarker) { // requires main thread
let mut this = AppState::get_mut(mtm); unsafe fn handle_user_events() {
let (mut handler, active_control_flow, processing_redraws) = let mut this = AppState::get_mut();
let mut control_flow = this.control_flow;
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() { match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => { UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
bug!("unexpected attempted to process an event") bug!("unexpected attempted to process an event")
} }
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success {
handler, event_handler,
active_control_flow, active_control_flow,
processing_redraws, processing_redraws,
} => (handler, active_control_flow, processing_redraws), } => (event_handler, active_control_flow, processing_redraws),
}; };
if processing_redraws { if processing_redraws {
bug!("user events attempted to be sent out while `ProcessingRedraws`"); bug!("user events attempted to be sent out while `ProcessingRedraws`");
} }
drop(this); drop(this);
handler.handle_event(Event::UserEvent(HandlePendingUserEvents)); event_handler.handle_user_events(&mut control_flow);
loop { loop {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut();
let queued_events = match this.state_mut() { let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { &mut AppStateImpl::InUserCallback {
ref mut queued_events, ref mut queued_events,
@@ -727,28 +723,33 @@ fn handle_user_events(mtm: MainThreadMarker) {
_ => unreachable!(), _ => unreachable!(),
}; };
this.app_state = Some(AppStateImpl::ProcessingEvents { this.app_state = Some(AppStateImpl::ProcessingEvents {
handler, event_handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
}); });
this.control_flow = control_flow;
break; break;
} }
drop(this); drop(this);
for wrapper in queued_events { for wrapper in queued_events {
match wrapper { match wrapper {
EventWrapper::StaticEvent(event) => handler.handle_event(event), EventWrapper::StaticEvent(event) => {
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event), event_handler.handle_nonuser_event(event, &mut control_flow)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
}
} }
} }
event_handler.handle_user_events(&mut control_flow);
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
} }
} }
pub fn handle_main_events_cleared(mtm: MainThreadMarker) { // requires main thread
let mut this = AppState::get_mut(mtm); pub unsafe fn handle_main_events_cleared() {
if !this.has_launched() || this.has_terminated() { let mut this = AppState::get_mut();
if !this.has_launched() {
return; return;
} }
match this.state_mut() { match this.state_mut() {
@@ -757,62 +758,84 @@ pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
}; };
drop(this); drop(this);
handle_user_events(mtm); // User events are always sent out at the end of the "MainEventLoop"
handle_user_events();
handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut();
let redraw_events: Vec<EventWrapper> = this let mut redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition() .main_events_cleared_transition()
.into_iter() .into_iter()
.map(|window| { .map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.id()))))
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
})
})
.collect(); .collect();
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
drop(this); drop(this);
handle_nonuser_events(mtm, redraw_events); handle_nonuser_events(redraw_events);
handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait));
} }
pub fn handle_events_cleared(mtm: MainThreadMarker) { // requires main thread
AppState::get_mut(mtm).events_cleared_transition(); pub unsafe fn handle_events_cleared() {
AppState::get_mut().events_cleared_transition();
} }
pub fn terminated(mtm: MainThreadMarker) { // requires main thread
let mut this = AppState::get_mut(mtm); pub unsafe fn terminated() {
let mut handler = this.terminated_transition(); let mut this = AppState::get_mut();
let mut event_handler = this.terminated_transition();
let mut control_flow = this.control_flow;
drop(this); drop(this);
handler.handle_event(Event::LoopExiting) event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow)
} }
fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) { fn handle_event_proxy(
let ScaleFactorChanged { event_handler: &mut Box<dyn EventHandler>,
suggested_size, control_flow: ControlFlow,
scale_factor, proxy: EventProxy,
window, ) {
} = event; match proxy {
let new_inner_size = Arc::new(Mutex::new(suggested_size)); EventProxy::DpiChangedProxy {
suggested_size,
scale_factor,
window,
} => handle_hidpi_proxy(
event_handler,
control_flow,
suggested_size,
scale_factor,
window,
),
}
}
fn handle_hidpi_proxy(
event_handler: &mut Box<dyn EventHandler>,
mut control_flow: ControlFlow,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
window: Id<WinitUIWindow, Shared>,
) {
let mut size = suggested_size.to_physical(scale_factor);
let new_inner_size = &mut size;
let event = Event::WindowEvent { let event = Event::WindowEvent {
window_id: window.id(), window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged { event: WindowEvent::ScaleFactorChanged {
scale_factor, scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), new_inner_size,
}, },
}; };
handler.handle_event(event); event_handler.handle_nonuser_event(event, &mut control_flow);
let (view, screen_frame) = get_view_and_screen_frame(&window); let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_inner_size.lock().unwrap(); let physical_size = *new_inner_size;
drop(new_inner_size);
let logical_size = physical_size.to_logical(scale_factor); let logical_size = physical_size.to_logical(scale_factor);
let size = CGSize::new(logical_size.width, logical_size.height); let size = CGSize::new(logical_size.width, logical_size.height);
let new_frame: CGRect = CGRect::new(screen_frame.origin, size); let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
view.setFrame(new_frame); view.setFrame(new_frame);
} }
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView>, CGRect) { fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView, Shared>, CGRect) {
let view_controller = window.rootViewController().unwrap(); let view_controller = window.rootViewController().unwrap();
let view = view_controller.view().unwrap(); let view = view_controller.view().unwrap();
let bounds = window.bounds(); let bounds = window.bounds();
@@ -901,7 +924,7 @@ macro_rules! os_capabilities {
impl From<NSOperatingSystemVersion> for OSCapabilities { impl From<NSOperatingSystemVersion> for OSCapabilities {
fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities { fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities {
$(let $name = meets_requirements(os_version, $major, $minor);)* $(let $name = os_version.meets_requirements($major, $minor);)*
OSCapabilities { $($name,)* os_version, } OSCapabilities { $($name,)* os_version, }
} }
} }
@@ -911,7 +934,7 @@ macro_rules! os_capabilities {
pub fn $error_name(&self, extra_msg: &str) { pub fn $error_name(&self, extra_msg: &str) {
log::warn!( log::warn!(
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"), concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion, $major, $minor, self.os_version.major, self.os_version.minor, self.os_version.patch,
extra_msg extra_msg
) )
} }
@@ -939,18 +962,16 @@ os_capabilities! {
force_touch: 9-0, force_touch: 9-0,
} }
fn meets_requirements( impl NSOperatingSystemVersion {
version: NSOperatingSystemVersion, fn meets_requirements(&self, required_major: NSInteger, required_minor: NSInteger) -> bool {
required_major: NSInteger, (self.major, self.minor) >= (required_major, required_minor)
required_minor: NSInteger, }
) -> bool {
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
} }
pub fn os_capabilities() -> OSCapabilities { pub fn os_capabilities() -> OSCapabilities {
static OS_CAPABILITIES: Lazy<OSCapabilities> = Lazy::new(|| { static OS_CAPABILITIES: Lazy<OSCapabilities> = Lazy::new(|| {
let version: NSOperatingSystemVersion = unsafe { let version: NSOperatingSystemVersion = unsafe {
let process_info = NSProcessInfo::processInfo(); let process_info = NSProcessInfo::process_info();
let atleast_ios_8: bool = msg_send![ let atleast_ios_8: bool = msg_send![
&process_info, &process_info,
respondsToSelector: sel!(operatingSystemVersion) respondsToSelector: sel!(operatingSystemVersion)
@@ -963,7 +984,7 @@ pub fn os_capabilities() -> OSCapabilities {
// //
// The minimum required iOS version is likely to grow in the future. // The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater"); assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
process_info.operatingSystemVersion() msg_send![&process_info, operatingSystemVersion]
}; };
version.into() version.into()
}); });

View File

@@ -1,6 +1,7 @@
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
ffi::c_void, ffi::c_void,
fmt::{self, Debug},
marker::PhantomData, marker::PhantomData,
ptr, ptr,
sync::mpsc::{self, Receiver, Sender}, sync::mpsc::{self, Receiver, Sender},
@@ -13,131 +14,69 @@ use core_foundation::runloop::{
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
}; };
use icrate::Foundation::{MainThreadMarker, NSString}; use objc2::foundation::{MainThreadMarker, NSString};
use objc2::rc::{Id, Shared};
use objc2::ClassType; use objc2::ClassType;
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen};
use super::view::WinitUIWindow;
use super::{app_state, monitor, view, MonitorHandle};
use crate::{ use crate::{
error::EventLoopError, dpi::LogicalSize,
event::Event, event::Event,
event_loop::{ event_loop::{
ControlFlow, DeviceEvents, EventLoopClosed, ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
EventLoopWindowTarget as RootEventLoopWindowTarget,
}, },
platform::ios::Idiom, platform::ios::Idiom,
platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents},
};
use super::{app_state, monitor, view, MonitorHandle};
use super::{
app_state::AppState,
uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen},
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct EventLoopWindowTarget { pub(crate) enum EventWrapper {
pub(super) mtm: MainThreadMarker, StaticEvent(Event<'static, Never>),
EventProxy(EventProxy),
} }
impl EventLoopWindowTarget { #[derive(Debug, PartialEq)]
pub(crate) enum EventProxy {
DpiChangedProxy {
window: Id<WinitUIWindow, Shared>,
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> { pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::uiscreens(self.mtm) monitor::uiscreens(MainThreadMarker::new().unwrap())
} }
pub fn primary_monitor(&self) -> Option<MonitorHandle> { pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(UIScreen::main(self.mtm))) Some(MonitorHandle::new(UIScreen::main(
MainThreadMarker::new().unwrap(),
)))
} }
#[inline] pub fn raw_display_handle(&self) -> RawDisplayHandle {
pub fn listen_device_events(&self, _allowed: DeviceEvents) {} RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
AppState::get_mut(self.mtm).set_control_flow(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
AppState::get_mut(self.mtm).control_flow()
}
pub(crate) fn exit(&self) {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically
log::warn!("`ControlFlow::Exit` ignored on iOS");
}
pub(crate) fn exiting(&self) -> bool {
false
}
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle
}
}
#[derive(Clone)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::UiKitDisplayHandle::empty().into()
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::UiKitDisplayHandle::new().into())
}
}
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootEventLoopWindowTarget),
receiver: mpsc::Receiver<T>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootEventLoopWindowTarget) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
}
}
} }
} }
pub struct EventLoop<T: 'static> { pub struct EventLoop<T: 'static> {
mtm: MainThreadMarker, window_target: RootEventLoopWindowTarget<T>,
sender: Sender<T>,
receiver: Receiver<T>,
window_target: RootEventLoopWindowTarget,
} }
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {} pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T: 'static> EventLoop<T> { impl<T: 'static> EventLoop<T> {
pub(crate) fn new( pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
_: &PlatformSpecificEventLoopAttributes, assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
) -> Result<EventLoop<T>, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread");
static mut SINGLETON_INIT: bool = false; static mut SINGLETON_INIT: bool = false;
unsafe { unsafe {
@@ -149,69 +88,57 @@ impl<T: 'static> EventLoop<T> {
SINGLETON_INIT = true; SINGLETON_INIT = true;
} }
let (sender, receiver) = mpsc::channel(); let (sender_to_clone, receiver) = mpsc::channel();
// this line sets up the main run loop before `UIApplicationMain` // this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers(); setup_control_flow_observers();
Ok(EventLoop { EventLoop {
mtm,
sender,
receiver,
window_target: RootEventLoopWindowTarget { window_target: RootEventLoopWindowTarget {
p: EventLoopWindowTarget { mtm }, p: EventLoopWindowTarget {
receiver,
sender_to_clone,
},
_marker: PhantomData, _marker: PhantomData,
}, },
}) }
} }
pub fn run<F>(self, handler: F) -> ! pub fn run<F>(self, event_handler: F) -> !
where where
F: FnMut(Event<T>, &RootEventLoopWindowTarget), F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{ {
let application = UIApplication::shared(self.mtm); unsafe {
assert!( let application = UIApplication::shared(MainThreadMarker::new().unwrap());
application.is_none(), assert!(
"\ application.is_none(),
"\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS", Note: `EventLoop::run` calls `UIApplicationMain` on iOS",
); );
app_state::will_launch(Box::new(EventLoopHandler {
f: event_handler,
event_loop: self.window_target,
}));
let handler = map_user_event(handler, self.receiver); // Ensure application delegate is initialized
view::WinitApplicationDelegate::class();
let handler = unsafe {
std::mem::transmute::<
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootEventLoopWindowTarget)>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootEventLoopWindowTarget)>,
>(Box::new(handler))
};
let handler = EventLoopHandler {
handler,
event_loop: self.window_target,
};
app_state::will_launch(self.mtm, handler);
// Ensure application delegate is initialized
view::WinitApplicationDelegate::class();
unsafe {
UIApplicationMain( UIApplicationMain(
0, 0,
ptr::null(), ptr::null(),
None, None,
Some(&NSString::from_str("WinitApplicationDelegate")), Some(&NSString::from_str("WinitApplicationDelegate")),
) );
}; unreachable!()
unreachable!() }
} }
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone()) EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
} }
pub fn window_target(&self) -> &RootEventLoopWindowTarget { pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
&self.window_target &self.window_target
} }
} }
@@ -219,7 +146,9 @@ impl<T: 'static> EventLoop<T> {
// EventLoopExtIOS // EventLoopExtIOS
impl<T: 'static> EventLoop<T> { impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom { pub fn idiom(&self) -> Idiom {
UIDevice::current(self.mtm).userInterfaceIdiom().into() UIDevice::current(MainThreadMarker::new().unwrap())
.userInterfaceIdiom()
.into()
} }
} }
@@ -297,20 +226,21 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
let mtm = MainThreadMarker::new().unwrap(); unsafe {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm), kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
_ => unreachable!(), _ => unreachable!(),
}
} }
} }
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in // Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end // `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send AboutToWait before RedrawRequested. This value was // priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw // 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 // 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 `AboutToWait`. // conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
// //
// The value of `0x1e8480` was determined by inspecting stack traces and the associated // 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. // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
@@ -321,12 +251,13 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
let mtm = MainThreadMarker::new().unwrap(); unsafe {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm), kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(),
kCFRunLoopExit => {} // may happen when running on macOS kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(), _ => unreachable!(),
}
} }
} }
@@ -336,12 +267,13 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
let mtm = MainThreadMarker::new().unwrap(); unsafe {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm), kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
kCFRunLoopExit => {} // may happen when running on macOS kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(), _ => unreachable!(),
}
} }
} }
@@ -378,3 +310,44 @@ fn setup_control_flow_observers() {
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode); CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
} }
} }
#[derive(Debug)]
pub enum Never {}
pub trait EventHandler: Debug {
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow);
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
}
struct EventLoopHandler<F, T: 'static> {
f: F,
event_loop: RootEventLoopWindowTarget<T>,
}
impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopHandler")
.field("event_loop", &self.event_loop)
.finish()
}
}
impl<F, T> EventHandler for EventLoopHandler<F, T>
where
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
T: 'static,
{
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
(self.f)(
event.map_nonuser_event().unwrap(),
&self.event_loop,
control_flow,
);
}
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
for event in self.event_loop.p.receiver.try_iter() {
(self.f)(Event::UserEvent(event), &self.event_loop, control_flow);
}
}
}

View File

@@ -2,11 +2,30 @@
use std::convert::TryInto; use std::convert::TryInto;
use icrate::Foundation::{NSInteger, NSUInteger};
use objc2::encode::{Encode, Encoding}; use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSInteger, NSUInteger};
use crate::platform::ios::{Idiom, ScreenEdge}; use crate::platform::ios::{Idiom, ScreenEdge};
#[repr(C)]
#[derive(Clone, Debug)]
pub struct NSOperatingSystemVersion {
pub major: NSInteger,
pub minor: NSInteger,
pub patch: NSInteger,
}
unsafe impl Encode for NSOperatingSystemVersion {
const ENCODING: Encoding = Encoding::Struct(
"NSOperatingSystemVersion",
&[
NSInteger::ENCODING,
NSInteger::ENCODING,
NSInteger::ENCODING,
],
);
}
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIUserInterfaceIdiom(NSInteger); pub struct UIUserInterfaceIdiom(NSInteger);
@@ -51,10 +70,6 @@ impl From<UIUserInterfaceIdiom> for Idiom {
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(NSUInteger); pub struct UIRectEdge(NSUInteger);
impl UIRectEdge {
pub(crate) const NONE: Self = Self(0);
}
unsafe impl Encode for UIRectEdge { unsafe impl Encode for UIRectEdge {
const ENCODING: Encoding = NSUInteger::ENCODING; const ENCODING: Encoding = NSUInteger::ENCODING;
} }

View File

@@ -48,16 +48,27 @@
//! //!
//! - applicationDidBecomeActive is Resumed //! - applicationDidBecomeActive is Resumed
//! - applicationWillResignActive is Suspended //! - applicationWillResignActive is Suspended
//! - applicationWillTerminate is LoopExiting //! - applicationWillTerminate is LoopDestroyed
//! //!
//! Keep in mind that after LoopExiting event is received every attempt to draw with //! Keep in mind that after LoopDestroyed event is received every attempt to draw with
//! opengl will result in segfault. //! opengl will result in segfault.
//! //!
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed. //! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
#![cfg(ios_platform)] #![cfg(ios_platform)]
#![allow(clippy::let_unit_value)] #![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)*) => {
if !::objc2::foundation::is_main_thread() {
panic!($($t)*);
}
};
}
mod app_state; mod app_state;
mod event_loop; mod event_loop;
mod ffi; mod ffi;
@@ -68,25 +79,33 @@ mod window;
use std::fmt; use std::fmt;
use crate::event::DeviceId;
pub(crate) use self::{ pub(crate) use self::{
event_loop::{ event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, OwnedDisplayHandle, EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
PlatformSpecificEventLoopAttributes,
}, },
monitor::{MonitorHandle, VideoModeHandle}, monitor::{MonitorHandle, VideoMode},
window::{PlatformSpecificWindowBuilderAttributes, Window}, window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
}; };
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder; use self::uikit::UIScreen;
pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen; pub(self) use crate::platform_impl::Fullscreen;
pub(crate) const DEVICE_ID: DeviceId = unsafe { DeviceId::dummy() }; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId {
uiscreen: *const UIScreen,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)] impl DeviceId {
pub struct KeyEventExtra {} pub const unsafe fn dummy() -> Self {
DeviceId {
uiscreen: std::ptr::null(),
}
}
}
unsafe impl Send for DeviceId {}
unsafe impl Sync for DeviceId {}
#[derive(Debug)] #[derive(Debug)]
pub enum OsError {} pub enum OsError {}

View File

@@ -0,0 +1,246 @@
#![allow(clippy::unnecessary_cast)]
use std::{
collections::{BTreeSet, VecDeque},
fmt,
ops::{Deref, DerefMut},
};
use objc2::foundation::{MainThreadMarker, NSInteger};
use objc2::rc::{Id, Shared};
use super::uikit::{UIScreen, UIScreenMode};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode as RootVideoMode,
platform_impl::platform::app_state,
};
// TODO(madsmtm): Remove or refactor this
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode, Shared>);
unsafe impl Send for ScreenModeSendSync {}
unsafe impl Sync for ScreenModeSendSync {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) screen_mode: ScreenModeSendSync,
pub(crate) monitor: MonitorHandle,
}
impl VideoMode {
fn new(uiscreen: Id<UIScreen, Shared>, screen_mode: Id<UIScreenMode, Shared>) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size = screen_mode.size();
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate_millihertz,
screen_mode: ScreenModeSendSync(screen_mode),
monitor: MonitorHandle::new(uiscreen),
}
}
pub fn size(&self) -> PhysicalSize<u32> {
self.size.into()
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Inner {
uiscreen: Id<UIScreen, Shared>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct MonitorHandle {
inner: Inner,
}
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 {
// TODO: Make a better ordering
(self as *const Self).cmp(&(other as *const Self))
}
}
impl Deref for MonitorHandle {
type Target = Inner;
fn deref(&self) -> &Inner {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
&self.inner
}
}
impl DerefMut for MonitorHandle {
fn deref_mut(&mut self) -> &mut Inner {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
&mut self.inner
}
}
unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {}
impl Drop for MonitorHandle {
fn drop(&mut self) {
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
}
}
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,
}
let monitor_id_proxy = MonitorHandle {
name: self.name(),
size: self.size(),
position: self.position(),
scale_factor: self.scale_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorHandle {
pub(crate) fn new(uiscreen: Id<UIScreen, Shared>) -> Self {
assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS");
Self {
inner: Inner { uiscreen },
}
}
}
impl Inner {
pub fn name(&self) -> Option<String> {
let main = UIScreen::main(MainThreadMarker::new().unwrap());
if self.uiscreen == main {
Some("Primary".to_string())
} else if self.uiscreen == main.mirroredScreen() {
Some("Mirrored".to_string())
} else {
UIScreen::screens(MainThreadMarker::new().unwrap())
.iter()
.position(|rhs| rhs == &*self.uiscreen)
.map(|idx| idx.to_string())
}
}
pub fn size(&self) -> PhysicalSize<u32> {
let bounds = self.uiscreen.nativeBounds();
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
}
pub fn position(&self) -> PhysicalPosition<i32> {
let bounds = self.uiscreen.nativeBounds();
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
pub fn scale_factor(&self) -> f64 {
self.uiscreen.nativeScale() as f64
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(refresh_rate_millihertz(&self.uiscreen))
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
// Use Ord impl of RootVideoMode
let modes: BTreeSet<_> = self
.uiscreen
.availableModes()
.into_iter()
.map(|mode| {
let mode: *const UIScreenMode = mode;
let mode = unsafe { Id::retain(mode as *mut UIScreenMode).unwrap() };
RootVideoMode {
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
}
})
.collect();
modes.into_iter().map(|mode| mode.video_mode)
}
}
fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
let refresh_rate_millihertz: NSInteger = {
let os_capabilities = app_state::os_capabilities();
if os_capabilities.maximum_frames_per_second {
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(crate) fn ui_screen(&self) -> &Id<UIScreen, Shared> {
&self.uiscreen
}
pub fn preferred_video_mode(&self) -> VideoMode {
VideoMode::new(
self.uiscreen.clone(),
self.uiscreen.preferredMode().unwrap(),
)
}
}
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
UIScreen::screens(mtm)
.into_iter()
.map(|screen| {
let screen: *const UIScreen = screen;
let screen = unsafe { Id::retain(screen as *mut UIScreen).unwrap() };
MonitorHandle::new(screen)
})
.collect()
}

View File

@@ -1,6 +1,6 @@
use icrate::Foundation::{CGRect, MainThreadMarker, NSArray, NSObject}; use objc2::foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
use objc2::rc::Id; use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UIResponder, UIWindow}; use super::{UIResponder, UIWindow};
@@ -11,21 +11,20 @@ extern_class!(
unsafe impl ClassType for UIApplication { unsafe impl ClassType for UIApplication {
#[inherits(NSObject)] #[inherits(NSObject)]
type Super = UIResponder; type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
} }
); );
extern_methods!( extern_methods!(
unsafe impl UIApplication { unsafe impl UIApplication {
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self>> { pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self, Shared>> {
unsafe { msg_send_id![Self::class(), sharedApplication] } unsafe { msg_send_id![Self::class(), sharedApplication] }
} }
pub fn windows(&self) -> Id<NSArray<UIWindow>> { pub fn windows(&self) -> Id<NSArray<UIWindow, Shared>, Shared> {
unsafe { msg_send_id![self, windows] } unsafe { msg_send_id![self, windows] }
} }
#[method(statusBarFrame)] #[sel(statusBarFrame)]
pub fn statusBarFrame(&self) -> CGRect; pub fn statusBarFrame(&self) -> CGRect;
} }
); );

View File

@@ -1,5 +1,5 @@
use icrate::Foundation::NSObject; use objc2::foundation::NSObject;
use objc2::{extern_class, mutability, ClassType}; use objc2::{extern_class, ClassType};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,6 +7,5 @@ extern_class!(
unsafe impl ClassType for UICoordinateSpace { unsafe impl ClassType for UICoordinateSpace {
type Super = NSObject; type Super = NSObject;
type Mutability = mutability::InteriorMutable;
} }
); );

View File

@@ -1,6 +1,6 @@
use icrate::Foundation::{MainThreadMarker, NSObject}; use objc2::foundation::{MainThreadMarker, NSObject};
use objc2::rc::Id; use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::super::ffi::UIUserInterfaceIdiom; use super::super::ffi::UIUserInterfaceIdiom;
@@ -10,17 +10,16 @@ extern_class!(
unsafe impl ClassType for UIDevice { unsafe impl ClassType for UIDevice {
type Super = NSObject; type Super = NSObject;
type Mutability = mutability::InteriorMutable;
} }
); );
extern_methods!( extern_methods!(
unsafe impl UIDevice { unsafe impl UIDevice {
pub fn current(_mtm: MainThreadMarker) -> Id<Self> { pub fn current(_mtm: MainThreadMarker) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), currentDevice] } unsafe { msg_send_id![Self::class(), currentDevice] }
} }
#[method(userInterfaceIdiom)] #[sel(userInterfaceIdiom)]
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom; pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom;
} }
); );

View File

@@ -1,5 +1,5 @@
use icrate::Foundation::NSObject; use objc2::foundation::NSObject;
use objc2::{extern_class, mutability, ClassType}; use objc2::{extern_class, ClassType};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,6 +7,5 @@ extern_class!(
unsafe impl ClassType for UIEvent { unsafe impl ClassType for UIEvent {
type Super = NSObject; type Super = NSObject;
type Mutability = mutability::InteriorMutable;
} }
); );

View File

@@ -1,19 +1,18 @@
#![deny(unsafe_op_in_unsafe_fn)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals)]
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
use icrate::Foundation::NSString; use objc2::foundation::NSString;
mod application; mod application;
mod coordinate_space; mod coordinate_space;
mod device; mod device;
mod event; mod event;
mod gesture_recognizer;
mod responder; mod responder;
mod screen; mod screen;
mod screen_mode; mod screen_mode;
mod status_bar_style;
mod touch; mod touch;
mod trait_collection; mod trait_collection;
mod view; mod view;
@@ -24,14 +23,9 @@ pub(crate) use self::application::UIApplication;
pub(crate) use self::coordinate_space::UICoordinateSpace; pub(crate) use self::coordinate_space::UICoordinateSpace;
pub(crate) use self::device::UIDevice; pub(crate) use self::device::UIDevice;
pub(crate) use self::event::UIEvent; pub(crate) use self::event::UIEvent;
pub(crate) use self::gesture_recognizer::{
UIGestureRecognizer, UIGestureRecognizerState, UIPinchGestureRecognizer,
UIRotationGestureRecognizer, UITapGestureRecognizer,
};
pub(crate) use self::responder::UIResponder; pub(crate) use self::responder::UIResponder;
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation}; pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
pub(crate) use self::screen_mode::UIScreenMode; pub(crate) use self::screen_mode::UIScreenMode;
pub(crate) use self::status_bar_style::UIStatusBarStyle;
pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType}; pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType};
pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection}; pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection};
#[allow(unused_imports)] #[allow(unused_imports)]

View File

@@ -1,5 +1,5 @@
use icrate::Foundation::NSObject; use objc2::foundation::NSObject;
use objc2::{extern_class, mutability, ClassType}; use objc2::{extern_class, ClassType};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,6 +7,5 @@ extern_class!(
unsafe impl ClassType for UIResponder { unsafe impl ClassType for UIResponder {
type Super = NSObject; type Super = NSObject;
type Mutability = mutability::InteriorMutable;
} }
); );

View File

@@ -1,7 +1,7 @@
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
use objc2::encode::{Encode, Encoding}; use objc2::encode::{Encode, Encoding};
use objc2::rc::Id; use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UICoordinateSpace, UIScreenMode}; use super::{UICoordinateSpace, UIScreenMode};
@@ -11,54 +11,53 @@ extern_class!(
unsafe impl ClassType for UIScreen { unsafe impl ClassType for UIScreen {
type Super = NSObject; type Super = NSObject;
type Mutability = mutability::InteriorMutable;
} }
); );
extern_methods!( extern_methods!(
unsafe impl UIScreen { unsafe impl UIScreen {
pub fn main(_mtm: MainThreadMarker) -> Id<Self> { pub fn main(_mtm: MainThreadMarker) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), mainScreen] } unsafe { msg_send_id![Self::class(), mainScreen] }
} }
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self>> { pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self, Shared>, Shared> {
unsafe { msg_send_id![Self::class(), screens] } unsafe { msg_send_id![Self::class(), screens] }
} }
#[method(bounds)] #[sel(bounds)]
pub fn bounds(&self) -> CGRect; pub fn bounds(&self) -> CGRect;
#[method(scale)] #[sel(scale)]
pub fn scale(&self) -> CGFloat; pub fn scale(&self) -> CGFloat;
#[method(nativeBounds)] #[sel(nativeBounds)]
pub fn nativeBounds(&self) -> CGRect; pub fn nativeBounds(&self) -> CGRect;
#[method(nativeScale)] #[sel(nativeScale)]
pub fn nativeScale(&self) -> CGFloat; pub fn nativeScale(&self) -> CGFloat;
#[method(maximumFramesPerSecond)] #[sel(maximumFramesPerSecond)]
pub fn maximumFramesPerSecond(&self) -> NSInteger; pub fn maximumFramesPerSecond(&self) -> NSInteger;
pub fn mirroredScreen(&self) -> Id<Self> { pub fn mirroredScreen(&self) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), mirroredScreen] } unsafe { msg_send_id![Self::class(), mirroredScreen] }
} }
pub fn preferredMode(&self) -> Option<Id<UIScreenMode>> { pub fn preferredMode(&self) -> Option<Id<UIScreenMode, Shared>> {
unsafe { msg_send_id![self, preferredMode] } unsafe { msg_send_id![self, preferredMode] }
} }
#[method(setCurrentMode:)] #[sel(setCurrentMode:)]
pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>); pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>);
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode>> { pub fn availableModes(&self) -> Id<NSArray<UIScreenMode, Shared>, Shared> {
unsafe { msg_send_id![self, availableModes] } unsafe { msg_send_id![self, availableModes] }
} }
#[method(setOverscanCompensation:)] #[sel(setOverscanCompensation:)]
pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation); pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation);
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace> { pub fn coordinateSpace(&self) -> Id<UICoordinateSpace, Shared> {
unsafe { msg_send_id![self, coordinateSpace] } unsafe { msg_send_id![self, coordinateSpace] }
} }
} }

View File

@@ -1,5 +1,5 @@
use icrate::Foundation::{CGSize, NSObject}; use objc2::foundation::{CGSize, NSObject};
use objc2::{extern_class, extern_methods, mutability, ClassType}; use objc2::{extern_class, extern_methods, ClassType};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,13 +7,12 @@ extern_class!(
unsafe impl ClassType for UIScreenMode { unsafe impl ClassType for UIScreenMode {
type Super = NSObject; type Super = NSObject;
type Mutability = mutability::InteriorMutable;
} }
); );
extern_methods!( extern_methods!(
unsafe impl UIScreenMode { unsafe impl UIScreenMode {
#[method(size)] #[sel(size)]
pub fn size(&self) -> CGSize; pub fn size(&self) -> CGSize;
} }
); );

View File

@@ -1,6 +1,6 @@
use icrate::Foundation::{CGFloat, CGPoint, NSInteger, NSObject};
use objc2::encode::{Encode, Encoding}; use objc2::encode::{Encode, Encoding};
use objc2::{extern_class, extern_methods, mutability, ClassType}; use objc2::foundation::{CGFloat, CGPoint, NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
use super::UIView; use super::UIView;
@@ -10,28 +10,27 @@ extern_class!(
unsafe impl ClassType for UITouch { unsafe impl ClassType for UITouch {
type Super = NSObject; type Super = NSObject;
type Mutability = mutability::InteriorMutable;
} }
); );
extern_methods!( extern_methods!(
unsafe impl UITouch { unsafe impl UITouch {
#[method(locationInView:)] #[sel(locationInView:)]
pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint; pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint;
#[method(type)] #[sel(type)]
pub fn type_(&self) -> UITouchType; pub fn type_(&self) -> UITouchType;
#[method(force)] #[sel(force)]
pub fn force(&self) -> CGFloat; pub fn force(&self) -> CGFloat;
#[method(maximumPossibleForce)] #[sel(maximumPossibleForce)]
pub fn maximumPossibleForce(&self) -> CGFloat; pub fn maximumPossibleForce(&self) -> CGFloat;
#[method(altitudeAngle)] #[sel(altitudeAngle)]
pub fn altitudeAngle(&self) -> CGFloat; pub fn altitudeAngle(&self) -> CGFloat;
#[method(phase)] #[sel(phase)]
pub fn phase(&self) -> UITouchPhase; pub fn phase(&self) -> UITouchPhase;
} }
); );

View File

@@ -1,6 +1,6 @@
use icrate::Foundation::{NSInteger, NSObject};
use objc2::encode::{Encode, Encoding}; use objc2::encode::{Encode, Encoding};
use objc2::{extern_class, extern_methods, mutability, ClassType}; use objc2::foundation::{NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@@ -8,13 +8,12 @@ extern_class!(
unsafe impl ClassType for UITraitCollection { unsafe impl ClassType for UITraitCollection {
type Super = NSObject; type Super = NSObject;
type Mutability = mutability::InteriorMutable;
} }
); );
extern_methods!( extern_methods!(
unsafe impl UITraitCollection { unsafe impl UITraitCollection {
#[method(forceTouchCapability)] #[sel(forceTouchCapability)]
pub fn forceTouchCapability(&self) -> UIForceTouchCapability; pub fn forceTouchCapability(&self) -> UIForceTouchCapability;
} }
); );

View File

@@ -1,9 +1,9 @@
use icrate::Foundation::{CGFloat, CGRect, NSObject};
use objc2::encode::{Encode, Encoding}; use objc2::encode::{Encode, Encoding};
use objc2::rc::Id; use objc2::foundation::{CGFloat, CGRect, NSObject};
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UICoordinateSpace, UIGestureRecognizer, UIResponder, UIViewController}; use super::{UICoordinateSpace, UIResponder, UIViewController};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@@ -12,65 +12,58 @@ extern_class!(
unsafe impl ClassType for UIView { unsafe impl ClassType for UIView {
#[inherits(NSObject)] #[inherits(NSObject)]
type Super = UIResponder; type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
} }
); );
extern_methods!( extern_methods!(
unsafe impl UIView { unsafe impl UIView {
#[method(bounds)] #[sel(bounds)]
pub fn bounds(&self) -> CGRect; pub fn bounds(&self) -> CGRect;
#[method(setBounds:)] #[sel(setBounds:)]
pub fn setBounds(&self, value: CGRect); pub fn setBounds(&self, value: CGRect);
#[method(frame)] #[sel(frame)]
pub fn frame(&self) -> CGRect; pub fn frame(&self) -> CGRect;
#[method(setFrame:)] #[sel(setFrame:)]
pub fn setFrame(&self, value: CGRect); pub fn setFrame(&self, value: CGRect);
#[method(contentScaleFactor)] #[sel(contentScaleFactor)]
pub fn contentScaleFactor(&self) -> CGFloat; pub fn contentScaleFactor(&self) -> CGFloat;
#[method(setContentScaleFactor:)] #[sel(setContentScaleFactor:)]
pub fn setContentScaleFactor(&self, val: CGFloat); pub fn setContentScaleFactor(&self, val: CGFloat);
#[method(setMultipleTouchEnabled:)] #[sel(setMultipleTouchEnabled:)]
pub fn setMultipleTouchEnabled(&self, val: bool); pub fn setMultipleTouchEnabled(&self, val: bool);
pub fn rootViewController(&self) -> Option<Id<UIViewController>> { pub fn rootViewController(&self) -> Option<Id<UIViewController, Shared>> {
unsafe { msg_send_id![self, rootViewController] } unsafe { msg_send_id![self, rootViewController] }
} }
#[method(setRootViewController:)] #[sel(setRootViewController:)]
pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>); pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>);
#[method(convertRect:toCoordinateSpace:)] #[sel(convertRect:toCoordinateSpace:)]
pub fn convertRect_toCoordinateSpace( pub fn convertRect_toCoordinateSpace(
&self, &self,
rect: CGRect, rect: CGRect,
coordinateSpace: &UICoordinateSpace, coordinateSpace: &UICoordinateSpace,
) -> CGRect; ) -> CGRect;
#[method(convertRect:fromCoordinateSpace:)] #[sel(convertRect:fromCoordinateSpace:)]
pub fn convertRect_fromCoordinateSpace( pub fn convertRect_fromCoordinateSpace(
&self, &self,
rect: CGRect, rect: CGRect,
coordinateSpace: &UICoordinateSpace, coordinateSpace: &UICoordinateSpace,
) -> CGRect; ) -> CGRect;
#[method(safeAreaInsets)] #[sel(safeAreaInsets)]
pub fn safeAreaInsets(&self) -> UIEdgeInsets; pub fn safeAreaInsets(&self) -> UIEdgeInsets;
#[method(setNeedsDisplay)] #[sel(setNeedsDisplay)]
pub fn setNeedsDisplay(&self); pub fn setNeedsDisplay(&self);
#[method(addGestureRecognizer:)]
pub fn addGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
#[method(removeGestureRecognizer:)]
pub fn removeGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
} }
); );

View File

@@ -1,7 +1,7 @@
use icrate::Foundation::{NSObject, NSUInteger};
use objc2::encode::{Encode, Encoding}; use objc2::encode::{Encode, Encoding};
use objc2::rc::Id; use objc2::foundation::{NSObject, NSUInteger};
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UIResponder, UIView}; use super::{UIResponder, UIView};
@@ -12,35 +12,33 @@ extern_class!(
unsafe impl ClassType for UIViewController { unsafe impl ClassType for UIViewController {
#[inherits(NSObject)] #[inherits(NSObject)]
type Super = UIResponder; type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
} }
); );
extern_methods!( extern_methods!(
unsafe impl UIViewController { unsafe impl UIViewController {
#[method(attemptRotationToDeviceOrientation)] #[sel(attemptRotationToDeviceOrientation)]
pub fn attemptRotationToDeviceOrientation(); pub fn attemptRotationToDeviceOrientation();
#[method(setNeedsStatusBarAppearanceUpdate)] #[sel(setNeedsStatusBarAppearanceUpdate)]
pub fn setNeedsStatusBarAppearanceUpdate(&self); pub fn setNeedsStatusBarAppearanceUpdate(&self);
#[method(setNeedsUpdateOfHomeIndicatorAutoHidden)] #[sel(setNeedsUpdateOfHomeIndicatorAutoHidden)]
pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self); pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self);
#[method(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)] #[sel(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self); pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self);
pub fn view(&self) -> Option<Id<UIView>> { pub fn view(&self) -> Option<Id<UIView, Shared>> {
unsafe { msg_send_id![self, view] } unsafe { msg_send_id![self, view] }
} }
#[method(setView:)] #[sel(setView:)]
pub fn setView(&self, view: Option<&UIView>); pub fn setView(&self, view: Option<&UIView>);
} }
); );
bitflags::bitflags! { bitflags! {
#[derive(Clone, Copy)]
pub struct UIInterfaceOrientationMask: NSUInteger { pub struct UIInterfaceOrientationMask: NSUInteger {
const Portrait = 1 << 1; const Portrait = 1 << 1;
const PortraitUpsideDown = 1 << 2; const PortraitUpsideDown = 1 << 2;

View File

@@ -1,6 +1,6 @@
use icrate::Foundation::NSObject; use objc2::foundation::NSObject;
use objc2::rc::Id; use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UIResponder, UIScreen, UIView}; use super::{UIResponder, UIScreen, UIView};
@@ -11,26 +11,25 @@ extern_class!(
unsafe impl ClassType for UIWindow { unsafe impl ClassType for UIWindow {
#[inherits(UIResponder, NSObject)] #[inherits(UIResponder, NSObject)]
type Super = UIView; type Super = UIView;
type Mutability = mutability::InteriorMutable;
} }
); );
extern_methods!( extern_methods!(
unsafe impl UIWindow { unsafe impl UIWindow {
pub fn screen(&self) -> Id<UIScreen> { pub fn screen(&self) -> Id<UIScreen, Shared> {
unsafe { msg_send_id![self, screen] } unsafe { msg_send_id![self, screen] }
} }
#[method(setScreen:)] #[sel(setScreen:)]
pub fn setScreen(&self, screen: &UIScreen); pub fn setScreen(&self, screen: &UIScreen);
#[method(setHidden:)] #[sel(setHidden:)]
pub fn setHidden(&self, flag: bool); pub fn setHidden(&self, flag: bool);
#[method(makeKeyAndVisible)] #[sel(makeKeyAndVisible)]
pub fn makeKeyAndVisible(&self); pub fn makeKeyAndVisible(&self);
#[method(isKeyWindow)] #[sel(isKeyWindow)]
pub fn isKeyWindow(&self) -> bool; pub fn isKeyWindow(&self) -> bool;
} }
); );

View File

@@ -0,0 +1,535 @@
#![allow(clippy::unnecessary_cast)]
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet};
use objc2::rc::{Id, Shared};
use objc2::runtime::Class;
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, ClassType};
use super::uikit::{
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask,
UIResponder, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView, UIViewController,
UIWindow,
};
use super::window::WindowId;
use crate::{
dpi::PhysicalPosition,
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::ValidOrientations,
platform_impl::platform::{
app_state,
event_loop::{EventProxy, EventWrapper},
ffi::{UIRectEdge, UIUserInterfaceIdiom},
window::PlatformSpecificWindowBuilderAttributes,
DeviceId, Fullscreen,
},
window::{WindowAttributes, WindowId as RootWindowId},
};
declare_class!(
pub(crate) struct WinitView {}
unsafe impl ClassType for WinitView {
#[inherits(UIResponder, NSObject)]
type Super = UIView;
const NAME: &'static str = "WinitUIView";
}
unsafe impl WinitView {
#[sel(drawRect:)]
fn draw_rect(&self, rect: CGRect) {
let window = self.window().unwrap();
unsafe {
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
RootWindowId(window.id()),
)))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::RedrawEventsCleared,
))),
);
}
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
}
#[sel(layoutSubviews)]
fn layout_subviews(&self) {
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
let window = self.window().unwrap();
let window_bounds = window.bounds();
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = self.convertRect_toCoordinateSpace(window_bounds, &screen_space);
let scale_factor = screen.scale();
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 = self.frame();
if view_frame != window_bounds {
self.setFrame(window_bounds);
}
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Resized(size),
}));
}
}
#[sel(setContentScaleFactor:)]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
let _: () =
unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] };
// `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow
// makeKeyAndVisible]` at window creation time (either manually or internally by
// UIKit when the `UIView` is first created), in which case we send no events here
let window = match self.window() {
Some(window) => window,
None => return,
};
// `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 = self.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",
);
let scale_factor = scale_factor as f64;
let bounds = self.bounds();
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
let window_id = RootWindowId(window.id());
unsafe {
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window,
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
}
#[sel(touchesBegan:withEvent:)]
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[sel(touchesMoved:withEvent:)]
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[sel(touchesEnded:withEvent:)]
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[sel(touchesCancelled:withEvent:)]
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
}
);
extern_methods!(
#[allow(non_snake_case)]
unsafe impl WinitView {
fn window(&self) -> Option<Id<WinitUIWindow, Shared>> {
unsafe { msg_send_id![self, window] }
}
unsafe fn traitCollection(&self) -> Id<UITraitCollection, Shared> {
msg_send_id![self, traitCollection]
}
// TODO: Allow the user to customize this
#[sel(layerClass)]
pub(crate) fn layerClass() -> &'static Class;
}
);
impl WinitView {
pub(crate) fn new(
_mtm: MainThreadMarker,
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
) -> Id<Self, Shared> {
let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithFrame: frame] };
this.setMultipleTouchEnabled(true);
if let Some(scale_factor) = platform_attributes.scale_factor {
this.setContentScaleFactor(scale_factor as _);
}
this
}
fn handle_touches(&self, touches: &NSSet<UITouch>) {
let window = self.window().unwrap();
let uiscreen = window.screen();
let mut touch_events = Vec::new();
let os_supports_force = app_state::os_capabilities().force_touch;
for touch in touches {
let logical_location = touch.locationInView(None);
let touch_type = touch.type_();
let force = if os_supports_force {
let trait_collection = unsafe { self.traitCollection() };
let touch_capability = trait_collection.forceTouchCapability();
// Both the OS _and_ the device need to be checked for force touch support.
if touch_capability == UIForceTouchCapability::Available {
let force = touch.force();
let max_possible_force = touch.maximumPossibleForce();
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
let angle = touch.altitudeAngle();
Some(angle as _)
} else {
None
};
Some(Force::Calibrated {
force: force as _,
max_possible_force: max_possible_force as _,
altitude_angle,
})
} else {
None
}
} else {
None
};
let touch_id = touch as *const UITouch as u64;
let phase = touch.phase();
let phase = match phase {
UITouchPhase::Began => TouchPhase::Started,
UITouchPhase::Moved => TouchPhase::Moved,
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended => TouchPhase::Ended,
UITouchPhase::Cancelled => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
let physical_location = {
let scale_factor = self.contentScaleFactor();
PhysicalPosition::from_logical::<(f64, f64), f64>(
(logical_location.x as _, logical_location.y as _),
scale_factor as f64,
)
};
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId {
uiscreen: Id::as_ptr(&uiscreen),
}),
id: touch_id,
location: physical_location,
force,
phase,
}),
}));
}
unsafe {
app_state::handle_nonuser_events(touch_events);
}
}
}
declare_class!(
pub(crate) struct WinitViewController {
_prefers_status_bar_hidden: bool,
_prefers_home_indicator_auto_hidden: bool,
_supported_orientations: UIInterfaceOrientationMask,
_preferred_screen_edges_deferring_system_gestures: UIRectEdge,
}
unsafe impl ClassType for WinitViewController {
#[inherits(UIResponder, NSObject)]
type Super = UIViewController;
const NAME: &'static str = "WinitUIViewController";
}
unsafe impl WinitViewController {
#[sel(shouldAutorotate)]
fn should_autorotate(&self) -> bool {
true
}
}
unsafe impl WinitViewController {
#[sel(prefersStatusBarHidden)]
fn prefers_status_bar_hidden(&self) -> bool {
*self._prefers_status_bar_hidden
}
#[sel(setPrefersStatusBarHidden:)]
fn set_prefers_status_bar_hidden(&mut self, val: bool) {
*self._prefers_status_bar_hidden = val;
self.setNeedsStatusBarAppearanceUpdate();
}
#[sel(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool {
*self._prefers_home_indicator_auto_hidden
}
#[sel(setPrefersHomeIndicatorAutoHidden:)]
fn set_prefers_home_indicator_auto_hidden(&mut self, val: bool) {
*self._prefers_home_indicator_auto_hidden = val;
let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else {
os_capabilities.home_indicator_hidden_err_msg("ignoring")
}
}
#[sel(supportedInterfaceOrientations)]
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
*self._supported_orientations
}
#[sel(setSupportedInterfaceOrientations:)]
fn set_supported_orientations(&mut self, val: UIInterfaceOrientationMask) {
*self._supported_orientations = val;
UIViewController::attemptRotationToDeviceOrientation();
}
#[sel(preferredScreenEdgesDeferringSystemGestures)]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
*self._preferred_screen_edges_deferring_system_gestures
}
#[sel(setPreferredScreenEdgesDeferringSystemGestures:)]
fn set_preferred_screen_edges_deferring_system_gestures(&mut self, val: UIRectEdge) {
*self._preferred_screen_edges_deferring_system_gestures = val;
let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else {
os_capabilities.defer_system_gestures_err_msg("ignoring")
}
}
}
);
extern_methods!(
#[allow(non_snake_case)]
unsafe impl WinitViewController {
#[sel(setPrefersStatusBarHidden:)]
pub(crate) fn setPrefersStatusBarHidden(&self, flag: bool);
#[sel(setSupportedInterfaceOrientations:)]
pub(crate) fn setSupportedInterfaceOrientations(&self, val: UIInterfaceOrientationMask);
#[sel(setPrefersHomeIndicatorAutoHidden:)]
pub(crate) fn setPrefersHomeIndicatorAutoHidden(&self, val: bool);
#[sel(setPreferredScreenEdgesDeferringSystemGestures:)]
pub(crate) fn setPreferredScreenEdgesDeferringSystemGestures(&self, val: UIRectEdge);
}
);
impl WinitViewController {
pub(crate) fn set_supported_interface_orientations(
&self,
mtm: MainThreadMarker,
valid_orientations: ValidOrientations,
) {
let mask = match (
valid_orientations,
UIDevice::current(mtm).userInterfaceIdiom(),
) {
(ValidOrientations::LandscapeAndPortrait, UIUserInterfaceIdiom::Phone) => {
UIInterfaceOrientationMask::AllButUpsideDown
}
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
(ValidOrientations::Portrait, UIUserInterfaceIdiom::Phone) => {
UIInterfaceOrientationMask::Portrait
}
(ValidOrientations::Portrait, _) => {
UIInterfaceOrientationMask::Portrait
| UIInterfaceOrientationMask::PortraitUpsideDown
}
};
self.setSupportedInterfaceOrientations(mask);
}
pub(crate) fn new(
mtm: MainThreadMarker,
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: &UIView,
) -> Id<Self, Shared> {
let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], init] };
this.setPrefersStatusBarHidden(platform_attributes.prefers_status_bar_hidden);
this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations);
this.setPrefersHomeIndicatorAutoHidden(platform_attributes.prefers_home_indicator_hidden);
this.setPreferredScreenEdgesDeferringSystemGestures(
platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into(),
);
this.setView(Some(view));
this
}
}
declare_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct WinitUIWindow {}
unsafe impl ClassType for WinitUIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIWindow;
}
unsafe impl WinitUIWindow {
#[sel(becomeKeyWindow)]
fn become_key_window(&self) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(true),
}));
}
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
}
#[sel(resignKeyWindow)]
fn resign_key_window(&self) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(false),
}));
}
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
}
}
);
impl WinitUIWindow {
pub(crate) fn new(
_mtm: MainThreadMarker,
window_attributes: &WindowAttributes,
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
view_controller: &UIViewController,
) -> Id<Self, Shared> {
let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithFrame: frame] };
this.setRootViewController(Some(view_controller));
match window_attributes.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let monitor = video_mode.monitor();
let screen = monitor.ui_screen();
screen.setCurrentMode(Some(&video_mode.screen_mode.0));
this.setScreen(screen);
}
Some(Fullscreen::Borderless(Some(ref monitor))) => {
let screen = monitor.ui_screen();
this.setScreen(screen);
}
_ => (),
}
this
}
pub(crate) fn id(&self) -> WindowId {
(self as *const Self as usize as u64).into()
}
}
declare_class!(
pub struct WinitApplicationDelegate {}
unsafe impl ClassType for WinitApplicationDelegate {
type Super = NSObject;
}
// UIApplicationDelegate protocol
unsafe impl WinitApplicationDelegate {
#[sel(application:didFinishLaunchingWithOptions:)]
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
unsafe {
app_state::did_finish_launching();
}
true
}
#[sel(applicationDidBecomeActive:)]
fn did_become_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
}
#[sel(applicationWillResignActive:)]
fn will_resign_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
}
#[sel(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, _application: &UIApplication) {}
#[sel(applicationDidEnterBackground:)]
fn did_enter_background(&self, _application: &UIApplication) {}
#[sel(applicationWillTerminate:)]
fn will_terminate(&self, application: &UIApplication) {
let mut events = Vec::new();
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Destroyed,
}));
}
}
unsafe {
app_state::handle_nonuser_events(events);
app_state::terminated();
}
}
}
);

View File

@@ -1,35 +1,41 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::collections::VecDeque; use std::{
collections::VecDeque,
ffi::c_void,
ops::{Deref, DerefMut},
};
use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker}; use objc2::foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadMarker};
use log::{debug, warn}; use objc2::rc::{Id, Shared};
use objc2::rc::Id; use objc2::runtime::Object;
use objc2::{class, msg_send}; use objc2::{class, msg_send};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
use super::app_state::EventWrapper;
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation}; use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController}; use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{ use crate::{
cursor::Cursor,
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent}, event::{Event, WindowEvent},
icon::Icon, icon::Icon,
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}, platform::ios::{ScreenEdge, ValidOrientations},
platform_impl::platform::{ platform_impl::platform::{
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, app_state,
event_loop::{EventProxy, EventWrapper},
ffi::UIRectEdge,
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
}, },
window::{ window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowButtons, WindowId, WindowLevel, WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
}, },
}; };
pub struct Inner { pub struct Inner {
window: Id<WinitUIWindow>, pub(crate) window: Id<WinitUIWindow, Shared>,
view_controller: Id<WinitViewController>, pub(crate) view_controller: Id<WinitViewController, Shared>,
view: Id<WinitView>, pub(crate) view: Id<WinitView, Shared>,
gl_or_metal_backed: bool, gl_or_metal_backed: bool,
} }
@@ -42,10 +48,6 @@ impl Inner {
debug!("`Window::set_transparent` is ignored on iOS") debug!("`Window::set_transparent` is ignored on iOS")
} }
pub fn set_blur(&self, _blur: bool) {
debug!("`Window::set_blur` is ignored on iOS")
}
pub fn set_visible(&self, visible: bool) { pub fn set_visible(&self, visible: bool) {
self.window.setHidden(!visible) self.window.setHidden(!visible)
} }
@@ -56,81 +58,90 @@ impl Inner {
} }
pub fn request_redraw(&self) { pub fn request_redraw(&self) {
if self.gl_or_metal_backed { unsafe {
let mtm = MainThreadMarker::new().unwrap(); if self.gl_or_metal_backed {
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer. // `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using // Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
// raw or gl/metal for drawing this work is completely avoided. // raw or gl/metal for drawing this work is completely avoided.
// //
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via // The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
// testing. // testing.
// //
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc // https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(mtm, self.window.clone()); app_state::queue_gl_or_metal_redraw(self.window.clone());
} else { } else {
self.view.setNeedsDisplay(); self.view.setNeedsDisplay();
}
} }
} }
pub fn pre_present_notify(&self) {}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> { pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
let safe_area = self.safe_area_screen_space(); unsafe {
let position = LogicalPosition { let safe_area = self.safe_area_screen_space();
x: safe_area.origin.x as f64, let position = LogicalPosition {
y: safe_area.origin.y as f64, x: safe_area.origin.x as f64,
}; y: safe_area.origin.y as f64,
let scale_factor = self.scale_factor(); };
Ok(position.to_physical(scale_factor)) let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
} }
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> { pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
let screen_frame = self.screen_frame(); unsafe {
let position = LogicalPosition { let screen_frame = self.screen_frame();
x: screen_frame.origin.x as f64, let position = LogicalPosition {
y: screen_frame.origin.y as f64, x: screen_frame.origin.x as f64,
}; y: screen_frame.origin.y as f64,
let scale_factor = self.scale_factor(); };
Ok(position.to_physical(scale_factor)) let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
} }
pub fn set_outer_position(&self, physical_position: Position) { pub fn set_outer_position(&self, physical_position: Position) {
let scale_factor = self.scale_factor(); unsafe {
let position = physical_position.to_logical::<f64>(scale_factor); let scale_factor = self.scale_factor();
let screen_frame = self.screen_frame(); let position = physical_position.to_logical::<f64>(scale_factor);
let new_screen_frame = CGRect { let screen_frame = self.screen_frame();
origin: CGPoint { let new_screen_frame = CGRect {
x: position.x as _, origin: CGPoint {
y: position.y as _, x: position.x as _,
}, y: position.y as _,
size: screen_frame.size, },
}; size: screen_frame.size,
let bounds = self.rect_from_screen_space(new_screen_frame); };
self.window.setBounds(bounds); let bounds = self.rect_from_screen_space(new_screen_frame);
self.window.setBounds(bounds);
}
} }
pub fn inner_size(&self) -> PhysicalSize<u32> { pub fn inner_size(&self) -> PhysicalSize<u32> {
let scale_factor = self.scale_factor(); unsafe {
let safe_area = self.safe_area_screen_space(); let scale_factor = self.scale_factor();
let size = LogicalSize { let safe_area = self.safe_area_screen_space();
width: safe_area.size.width as f64, let size = LogicalSize {
height: safe_area.size.height as f64, width: safe_area.size.width as f64,
}; height: safe_area.size.height as f64,
size.to_physical(scale_factor) };
size.to_physical(scale_factor)
}
} }
pub fn outer_size(&self) -> PhysicalSize<u32> { pub fn outer_size(&self) -> PhysicalSize<u32> {
let scale_factor = self.scale_factor(); unsafe {
let screen_frame = self.screen_frame(); let scale_factor = self.scale_factor();
let size = LogicalSize { let screen_frame = self.screen_frame();
width: screen_frame.size.width as f64, let size = LogicalSize {
height: screen_frame.size.height as f64, width: screen_frame.size.width as f64,
}; height: screen_frame.size.height as f64,
size.to_physical(scale_factor) };
size.to_physical(scale_factor)
}
} }
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> { pub fn set_inner_size(&self, _size: Size) {
Some(self.inner_size()) warn!("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<Size>) {
@@ -174,8 +185,8 @@ impl Inner {
self.view.contentScaleFactor() as _ self.view.contentScaleFactor() as _
} }
pub fn set_cursor(&self, _cursor: Cursor) { pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
debug!("`Window::set_cursor` ignored on iOS") debug!("`Window::set_cursor_icon` ignored on iOS")
} }
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> { pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
@@ -198,9 +209,6 @@ impl Inner {
Err(ExternalError::NotSupported(NotSupportedError::new())) Err(ExternalError::NotSupported(NotSupportedError::new()))
} }
#[inline]
pub fn show_window_menu(&self, _position: Position) {}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> { pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new())) Err(ExternalError::NotSupported(NotSupportedError::new()))
} }
@@ -224,17 +232,14 @@ impl Inner {
} }
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) { pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
let mtm = MainThreadMarker::new().unwrap();
let uiscreen = match &monitor { let uiscreen = match &monitor {
Some(Fullscreen::Exclusive(video_mode)) => { Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.monitor.ui_screen(mtm); let uiscreen = video_mode.monitor.ui_screen();
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm))); uiscreen.setCurrentMode(Some(&video_mode.screen_mode.0));
uiscreen.clone() uiscreen.clone()
} }
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(), Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen().clone(),
Some(Fullscreen::Borderless(None)) => { Some(Fullscreen::Borderless(None)) => self.current_monitor_inner().ui_screen().clone(),
self.current_monitor_inner().ui_screen(mtm).clone()
}
None => { None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS"); warn!("`Window::set_fullscreen(None)` ignored on iOS");
return; return;
@@ -257,21 +262,22 @@ impl Inner {
} }
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> { pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
let mtm = MainThreadMarker::new().unwrap(); unsafe {
let monitor = self.current_monitor_inner(); let monitor = self.current_monitor_inner();
let uiscreen = monitor.ui_screen(mtm); let uiscreen = monitor.ui_screen();
let screen_space_bounds = self.screen_frame(); let screen_space_bounds = self.screen_frame();
let screen_bounds = uiscreen.bounds(); let screen_bounds = uiscreen.bounds();
// TODO: track fullscreen instead of relying on brittle float comparisons // TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds.origin.x == screen_bounds.origin.x if screen_space_bounds.origin.x == screen_bounds.origin.x
&& screen_space_bounds.origin.y == screen_bounds.origin.y && screen_space_bounds.origin.y == screen_bounds.origin.y
&& screen_space_bounds.size.width == screen_bounds.size.width && screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height && screen_space_bounds.size.height == screen_bounds.size.height
{ {
Some(Fullscreen::Borderless(Some(monitor))) Some(Fullscreen::Borderless(Some(monitor)))
} else { } else {
None None
}
} }
} }
@@ -289,8 +295,8 @@ impl Inner {
warn!("`Window::set_window_icon` is ignored on iOS") warn!("`Window::set_window_icon` is ignored on iOS")
} }
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) { pub fn set_ime_position(&self, _position: Position) {
warn!("`Window::set_ime_cursor_area` is ignored on iOS") warn!("`Window::set_ime_position` is ignored on iOS")
} }
pub fn set_ime_allowed(&self, _allowed: bool) { pub fn set_ime_allowed(&self, _allowed: bool) {
@@ -332,38 +338,16 @@ impl Inner {
self.window.id() self.window.id()
} }
#[cfg(feature = "rwh_04")] pub fn raw_window_handle(&self) -> RawWindowHandle {
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = UiKitWindowHandle::empty();
let mut window_handle = rwh_04::UiKitHandle::empty();
window_handle.ui_window = Id::as_ptr(&self.window) as _; window_handle.ui_window = Id::as_ptr(&self.window) as _;
window_handle.ui_view = Id::as_ptr(&self.view) as _; window_handle.ui_view = Id::as_ptr(&self.view) as _;
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _; window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
rwh_04::RawWindowHandle::UiKit(window_handle) RawWindowHandle::UiKit(window_handle)
} }
#[cfg(feature = "rwh_05")] pub fn raw_display_handle(&self) -> RawDisplayHandle {
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
let mut window_handle = rwh_05::UiKitWindowHandle::empty();
window_handle.ui_window = Id::as_ptr(&self.window) as _;
window_handle.ui_view = Id::as_ptr(&self.view) as _;
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
rwh_05::RawWindowHandle::UiKit(window_handle)
}
#[cfg(feature = "rwh_05")]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
let mut window_handle = rwh_06::UiKitWindowHandle::new({
let ui_view = Id::as_ptr(&self.view) as _;
std::ptr::NonNull::new(ui_view).expect("Id<T> should never be null")
});
window_handle.ui_view_controller =
std::ptr::NonNull::new(Id::as_ptr(&self.view_controller) as _);
rwh_06::RawWindowHandle::UiKit(window_handle)
} }
pub fn theme(&self) -> Option<Theme> { pub fn theme(&self) -> Option<Theme> {
@@ -371,8 +355,6 @@ impl Inner {
None None
} }
pub fn set_content_protected(&self, _protected: bool) {}
pub fn has_focus(&self) -> bool { pub fn has_focus(&self) -> bool {
self.window.isKeyWindow() self.window.isKeyWindow()
} }
@@ -386,22 +368,44 @@ impl Inner {
warn!("`Window::title` is ignored on iOS"); warn!("`Window::title` is ignored on iOS");
String::new() String::new()
} }
pub fn reset_dead_keys(&self) {
// Noop
}
} }
pub struct Window { pub struct Window {
inner: MainThreadBound<Inner>, pub inner: Inner,
}
impl Drop for Window {
fn drop(&mut self) {
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
}
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
impl Deref for Window {
type Target = Inner;
fn deref(&self) -> &Inner {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
&self.inner
}
}
impl DerefMut for Window {
fn deref_mut(&mut self) -> &mut Inner {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
&mut self.inner
}
} }
impl Window { impl Window {
pub(crate) fn new( pub(crate) fn new<T>(
event_loop: &EventLoopWindowTarget, _event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes, window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> { ) -> Result<Window, RootOsError> {
let mtm = event_loop.mtm; let mtm = MainThreadMarker::new().unwrap();
if window_attributes.min_inner_size.is_some() { if window_attributes.min_inner_size.is_some() {
warn!("`WindowAttributes::min_inner_size` is ignored on iOS"); warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
@@ -415,8 +419,8 @@ impl Window {
let main_screen = UIScreen::main(mtm); let main_screen = UIScreen::main(mtm);
let fullscreen = window_attributes.fullscreen.clone().map(Into::into); let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
let screen = match fullscreen { let screen = match fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm), Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm), Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(),
Some(Fullscreen::Borderless(None)) | None => &main_screen, Some(Fullscreen::Borderless(None)) | None => &main_screen,
}; };
@@ -437,7 +441,7 @@ impl Window {
None => screen_bounds, None => screen_bounds,
}; };
let view = WinitView::new(mtm, &window_attributes, frame); let view = WinitView::new(mtm, &window_attributes, &platform_attributes, frame);
let gl_or_metal_backed = unsafe { let gl_or_metal_backed = unsafe {
let layer_class = WinitView::layerClass(); let layer_class = WinitView::layerClass();
@@ -446,10 +450,17 @@ impl Window {
is_metal || is_gl is_metal || is_gl
}; };
let view_controller = WinitViewController::new(mtm, &window_attributes, &view); let view_controller =
let window = WinitUIWindow::new(mtm, &window_attributes, frame, &view_controller); WinitViewController::new(mtm, &window_attributes, &platform_attributes, &view);
let window = WinitUIWindow::new(
mtm,
&window_attributes,
&platform_attributes,
frame,
&view_controller,
);
app_state::set_key_window(mtm, &window); unsafe { 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 `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0 // event on window creation if the DPI factor != 1.0
@@ -461,73 +472,50 @@ impl Window {
let screen_space = screen.coordinateSpace(); let screen_space = screen.coordinateSpace();
let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space); let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = crate::dpi::LogicalSize { let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as f64, width: screen_frame.size.width as _,
height: screen_frame.size.height as f64, height: screen_frame.size.height as _,
}; };
let window_id = window.id(); let window_id = RootWindowId(window.id());
app_state::handle_nonuser_events( unsafe {
mtm, app_state::handle_nonuser_events(
std::iter::once(EventWrapper::ScaleFactorChanged( std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
app_state::ScaleFactorChanged {
window: window.clone(), window: window.clone(),
scale_factor, scale_factor,
suggested_size: size.to_physical(scale_factor), suggested_size: size,
}, }))
)) .chain(std::iter::once(EventWrapper::StaticEvent(
.chain(std::iter::once(EventWrapper::StaticEvent( Event::WindowEvent {
Event::WindowEvent { window_id,
window_id, event: WindowEvent::Resized(size.to_physical(scale_factor)),
event: WindowEvent::Resized(size.to_physical(scale_factor)), },
}, ))),
))), );
); }
} }
let inner = Inner {
window,
view_controller,
view,
gl_or_metal_backed,
};
Ok(Window { Ok(Window {
inner: MainThreadBound::new(inner, mtm), inner: Inner {
window,
view_controller,
view,
gl_or_metal_backed,
},
}) })
} }
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) {
// For now, don't actually do queuing, since it may be less predictable
self.maybe_wait_on_main(f)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
self.inner.get_on_main(|inner| f(inner))
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_window_handle_rwh_06(
&self,
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
if let Some(mtm) = MainThreadMarker::new() {
Ok(self.inner.get(mtm).raw_window_handle_rwh_06())
} else {
Err(rwh_06::HandleError::Unavailable)
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
}
} }
// WindowExtIOS // WindowExtIOS
impl Inner { impl Inner {
pub fn ui_window(&self) -> *mut c_void {
Id::as_ptr(&self.window) as *mut c_void
}
pub fn ui_view_controller(&self) -> *mut c_void {
Id::as_ptr(&self.view_controller) as *mut c_void
}
pub fn ui_view(&self) -> *mut c_void {
Id::as_ptr(&self.view) as *mut c_void
}
pub fn set_scale_factor(&self, scale_factor: f64) { pub fn set_scale_factor(&self, scale_factor: f64) {
assert!( assert!(
dpi::validate_scale_factor(scale_factor), dpi::validate_scale_factor(scale_factor),
@@ -546,54 +534,42 @@ impl Inner {
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) { pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.view_controller self.view_controller
.set_prefers_home_indicator_auto_hidden(hidden); .setPrefersHomeIndicatorAutoHidden(hidden);
} }
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into();
self.view_controller self.view_controller
.set_preferred_screen_edges_deferring_system_gestures(edges.into()); .setPreferredScreenEdgesDeferringSystemGestures(edges);
} }
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) { pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.view_controller.set_prefers_status_bar_hidden(hidden); self.view_controller.setPrefersStatusBarHidden(hidden);
}
pub fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
self.view_controller
.set_preferred_status_bar_style(status_bar_style.into());
}
pub fn recognize_pinch_gesture(&self, should_recognize: bool) {
self.view.recognize_pinch_gesture(should_recognize);
}
pub fn recognize_doubletap_gesture(&self, should_recognize: bool) {
self.view.recognize_doubletap_gesture(should_recognize);
}
pub fn recognize_rotation_gesture(&self, should_recognize: bool) {
self.view.recognize_rotation_gesture(should_recognize);
} }
} }
impl Inner { impl Inner {
fn screen_frame(&self) -> CGRect { // requires main thread
unsafe fn screen_frame(&self) -> CGRect {
self.rect_to_screen_space(self.window.bounds()) self.rect_to_screen_space(self.window.bounds())
} }
fn rect_to_screen_space(&self, rect: CGRect) -> CGRect { // requires main thread
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace(); let screen_space = self.window.screen().coordinateSpace();
self.window self.window
.convertRect_toCoordinateSpace(rect, &screen_space) .convertRect_toCoordinateSpace(rect, &screen_space)
} }
fn rect_from_screen_space(&self, rect: CGRect) -> CGRect { // requires main thread
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace(); let screen_space = self.window.screen().coordinateSpace();
self.window self.window
.convertRect_fromCoordinateSpace(rect, &screen_space) .convertRect_fromCoordinateSpace(rect, &screen_space)
} }
fn safe_area_screen_space(&self) -> CGRect { // requires main thread
unsafe fn safe_area_screen_space(&self) -> CGRect {
let bounds = self.window.bounds(); let bounds = self.window.bounds();
if app_state::os_capabilities().safe_area { if app_state::os_capabilities().safe_area {
let safe_area = self.window.safeAreaInsets(); let safe_area = self.window.safeAreaInsets();
@@ -611,7 +587,7 @@ impl Inner {
} else { } else {
let screen_frame = self.rect_to_screen_space(bounds); let screen_frame = self.rect_to_screen_space(bounds);
let status_bar_frame = { let status_bar_frame = {
let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect( let app = UIApplication::shared(MainThreadMarker::new().unwrap_unchecked()).expect(
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS", "`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS",
); );
app.statusBarFrame() app.statusBarFrame()
@@ -638,12 +614,49 @@ impl Inner {
} }
} }
#[derive(Clone, Debug, Default)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId {
window: *mut WinitUIWindow,
}
impl WindowId {
pub const 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 {}
impl From<&Object> for WindowId {
fn from(window: &Object) -> WindowId {
WindowId {
window: window as *const _ as _,
}
}
}
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes { pub struct PlatformSpecificWindowBuilderAttributes {
pub scale_factor: Option<f64>, pub scale_factor: Option<f64>,
pub valid_orientations: ValidOrientations, pub valid_orientations: ValidOrientations,
pub prefers_home_indicator_hidden: bool, pub prefers_home_indicator_hidden: bool,
pub prefers_status_bar_hidden: bool, pub prefers_status_bar_hidden: bool,
pub preferred_status_bar_style: StatusBarStyle,
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge, pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
} }

View File

@@ -3,47 +3,58 @@
#[cfg(all(not(x11_platform), not(wayland_platform)))] #[cfg(all(not(x11_platform), not(wayland_platform)))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; #[cfg(wayland_platform)]
use std::sync::Arc; use std::error::Error;
use std::time::Duration;
use std::{collections::VecDeque, env, fmt}; use std::{collections::VecDeque, env, fmt};
#[cfg(x11_platform)] #[cfg(x11_platform)]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex}; use std::{
ffi::CStr,
mem::MaybeUninit,
os::raw::*,
sync::{Arc, Mutex},
};
#[cfg(x11_platform)] #[cfg(x11_platform)]
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use smol_str::SmolStr; use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
#[cfg(x11_platform)] #[cfg(x11_platform)]
use self::x11::{X11Error, XConnection, XError, XNotSupported}; pub use self::x11::XNotSupported;
#[cfg(x11_platform)] #[cfg(x11_platform)]
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook}; use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
#[cfg(x11_platform)]
use crate::platform::x11::XlibErrorHook;
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size}, dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::Event,
event_loop::{ event_loop::{
AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed, ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
EventLoopWindowTarget as RootELW,
}, },
icon::Icon, icon::Icon,
keyboard::Key,
platform::pump_events::PumpStatus,
window::{ window::{
ActivationToken, Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, WindowAttributes, WindowButtons, WindowLevel,
}, },
}; };
pub(crate) use self::common::keymap::{physicalkey_to_scancode, scancode_to_physicalkey};
pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::RgbaIcon as PlatformIcon; pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen; pub(self) use crate::platform_impl::Fullscreen;
pub(crate) mod common;
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
pub(crate) mod wayland; pub mod wayland;
#[cfg(x11_platform)] #[cfg(x11_platform)]
pub(crate) mod x11; pub mod x11;
/// Environment variable specifying which backend should be used on unix platform.
///
/// Legal values are x11 and wayland. If this variable is set only the named backend
/// will be tried by winit. If it is not set, winit will try to connect to a wayland connection,
/// and if it fails will fallback on x11.
///
/// If this variable is set with any other value, winit will panic.
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum Backend { pub(crate) enum Backend {
@@ -71,41 +82,35 @@ impl ApplicationName {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes { pub struct PlatformSpecificWindowBuilderAttributes {
pub name: Option<ApplicationName>, pub name: Option<ApplicationName>,
pub activation_token: Option<ActivationToken>,
#[cfg(x11_platform)] #[cfg(x11_platform)]
pub x11: X11WindowBuilderAttributes, pub visual_infos: Option<XVisualInfo>,
} #[cfg(x11_platform)]
#[derive(Clone, Debug)]
#[cfg(x11_platform)]
pub struct X11WindowBuilderAttributes {
pub visual_id: Option<x11rb::protocol::xproto::Visualid>,
pub screen_id: Option<i32>, pub screen_id: Option<i32>,
#[cfg(x11_platform)]
pub base_size: Option<Size>, pub base_size: Option<Size>,
#[cfg(x11_platform)]
pub override_redirect: bool, pub override_redirect: bool,
#[cfg(x11_platform)]
pub x11_window_types: Vec<XWindowType>, pub x11_window_types: Vec<XWindowType>,
/// The parent window to embed this window into.
pub embed_window: Option<x11rb::protocol::xproto::Window>,
} }
impl Default for PlatformSpecificWindowBuilderAttributes { impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self { fn default() -> Self {
Self { Self {
name: None, name: None,
activation_token: None,
#[cfg(x11_platform)] #[cfg(x11_platform)]
x11: X11WindowBuilderAttributes { visual_infos: None,
visual_id: None, #[cfg(x11_platform)]
screen_id: None, screen_id: None,
base_size: None, #[cfg(x11_platform)]
override_redirect: false, base_size: None,
x11_window_types: vec![XWindowType::Normal], #[cfg(x11_platform)]
embed_window: None, override_redirect: false,
}, #[cfg(x11_platform)]
x11_window_types: vec![XWindowType::Normal],
} }
} }
} }
@@ -116,21 +121,23 @@ pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum OsError { pub enum OsError {
Misc(&'static str),
#[cfg(x11_platform)] #[cfg(x11_platform)]
XError(Arc<X11Error>), XError(XError),
#[cfg(x11_platform)]
XMisc(&'static str),
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
WaylandError(Arc<wayland::WaylandError>), WaylandMisc(&'static str),
} }
impl fmt::Display for OsError { impl fmt::Display for OsError {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match *self { match *self {
OsError::Misc(e) => _f.pad(e),
#[cfg(x11_platform)] #[cfg(x11_platform)]
OsError::XError(ref e) => fmt::Display::fmt(e, _f), OsError::XError(ref e) => _f.pad(&e.description),
#[cfg(x11_platform)]
OsError::XMisc(e) => _f.pad(e),
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f), OsError::WaylandMisc(e) => _f.pad(e),
} }
} }
} }
@@ -142,6 +149,44 @@ pub(crate) enum Window {
Wayland(wayland::Window), Wayland(wayland::Window),
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(u64);
impl From<WindowId> for u64 {
fn from(window_id: WindowId) -> Self {
window_id.0
}
}
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self(raw_id)
}
}
impl WindowId {
pub const unsafe fn dummy() -> Self {
Self(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeviceId {
#[cfg(x11_platform)]
X(x11::DeviceId),
#[cfg(wayland_platform)]
Wayland(wayland::DeviceId),
}
impl DeviceId {
pub const unsafe fn dummy() -> Self {
#[cfg(wayland_platform)]
return DeviceId::Wayland(wayland::DeviceId::dummy());
#[cfg(all(not(wayland_platform), x11_platform))]
return DeviceId::X(x11::DeviceId::dummy());
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum MonitorHandle { pub enum MonitorHandle {
#[cfg(x11_platform)] #[cfg(x11_platform)]
@@ -210,70 +255,68 @@ impl MonitorHandle {
} }
#[inline] #[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> { pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes())) x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum VideoModeHandle { pub enum VideoMode {
#[cfg(x11_platform)] #[cfg(x11_platform)]
X(x11::VideoModeHandle), X(x11::VideoMode),
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
Wayland(wayland::VideoModeHandle), Wayland(wayland::VideoMode),
} }
impl VideoModeHandle { impl VideoMode {
#[inline] #[inline]
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
x11_or_wayland!(match self; VideoModeHandle(m) => m.size()) x11_or_wayland!(match self; VideoMode(m) => m.size())
} }
#[inline] #[inline]
pub fn bit_depth(&self) -> u16 { pub fn bit_depth(&self) -> u16 {
x11_or_wayland!(match self; VideoModeHandle(m) => m.bit_depth()) x11_or_wayland!(match self; VideoMode(m) => m.bit_depth())
} }
#[inline] #[inline]
pub fn refresh_rate_millihertz(&self) -> u32 { pub fn refresh_rate_millihertz(&self) -> u32 {
x11_or_wayland!(match self; VideoModeHandle(m) => m.refresh_rate_millihertz()) x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate_millihertz())
} }
#[inline] #[inline]
pub fn monitor(&self) -> MonitorHandle { pub fn monitor(&self) -> MonitorHandle {
x11_or_wayland!(match self; VideoModeHandle(m) => m.monitor(); as MonitorHandle) x11_or_wayland!(match self; VideoMode(m) => m.monitor())
} }
} }
impl Window { impl Window {
#[inline] #[inline]
pub(crate) fn new( pub(crate) fn new<T>(
window_target: &EventLoopWindowTarget, window_target: &EventLoopWindowTarget<T>,
attribs: WindowAttributes, attribs: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> { ) -> Result<Self, RootOsError> {
match *window_target { match *window_target {
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(ref window_target) => { EventLoopWindowTarget::Wayland(ref window_target) => {
wayland::Window::new(window_target, attribs).map(Window::Wayland) wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland)
} }
#[cfg(x11_platform)] #[cfg(x11_platform)]
EventLoopWindowTarget::X(ref window_target) => { EventLoopWindowTarget::X(ref window_target) => {
x11::Window::new(window_target, attribs).map(Window::X) x11::Window::new(window_target, attribs, pl_attribs).map(Window::X)
} }
} }
} }
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
f(self)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
f(self)
}
#[inline] #[inline]
pub fn id(&self) -> crate::window::WindowId { pub fn id(&self) -> WindowId {
x11_or_wayland!(match self; Window(w) => w.id()) match self {
#[cfg(wayland_platform)]
Self::Wayland(window) => window.id(),
#[cfg(x11_platform)]
Self::X(window) => window.id(),
}
} }
#[inline] #[inline]
@@ -286,11 +329,6 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_transparent(transparent)); x11_or_wayland!(match self; Window(w) => w.set_transparent(transparent));
} }
#[inline]
pub fn set_blur(&self, blur: bool) {
x11_or_wayland!(match self; Window(w) => w.set_blur(blur));
}
#[inline] #[inline]
pub fn set_visible(&self, visible: bool) { pub fn set_visible(&self, visible: bool) {
x11_or_wayland!(match self; Window(w) => w.set_visible(visible)) x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
@@ -327,13 +365,8 @@ impl Window {
} }
#[inline] #[inline]
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> { pub fn set_inner_size(&self, size: Size) {
x11_or_wayland!(match self; Window(w) => w.request_inner_size(size)) x11_or_wayland!(match self; Window(w) => w.set_inner_size(size))
}
#[inline]
pub(crate) fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
x11_or_wayland!(match self; Window(w) => w.request_activation_token())
} }
#[inline] #[inline]
@@ -377,8 +410,8 @@ impl Window {
} }
#[inline] #[inline]
pub fn set_cursor(&self, cursor: Cursor) { pub fn set_cursor_icon(&self, cursor: CursorIcon) {
x11_or_wayland!(match self; Window(w) => w.set_cursor(cursor)) x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
} }
#[inline] #[inline]
@@ -401,11 +434,6 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction)) x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction))
} }
#[inline]
pub fn show_window_menu(&self, position: Position) {
x11_or_wayland!(match self; Window(w) => w.show_window_menu(position))
}
#[inline] #[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest)) x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
@@ -462,23 +490,28 @@ impl Window {
} }
#[inline] #[inline]
pub fn set_window_level(&self, level: WindowLevel) { pub fn set_window_level(&self, _level: WindowLevel) {
x11_or_wayland!(match self; Window(w) => w.set_window_level(level)) match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.set_window_level(_level),
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
} }
#[inline] #[inline]
pub fn set_window_icon(&self, window_icon: Option<Icon>) { pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
x11_or_wayland!(match self; Window(w) => w.set_window_icon(window_icon.map(|icon| icon.inner))) match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.set_window_icon(_window_icon),
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
} }
#[inline] #[inline]
pub fn set_ime_cursor_area(&self, position: Position, size: Size) { pub fn set_ime_position(&self, position: Position) {
x11_or_wayland!(match self; Window(w) => w.set_ime_cursor_area(position, size)) x11_or_wayland!(match self; Window(w) => w.set_ime_position(position))
}
#[inline]
pub fn reset_dead_keys(&self) {
common::xkb_state::reset_dead_keys()
} }
#[inline] #[inline]
@@ -493,10 +526,20 @@ impl Window {
#[inline] #[inline]
pub fn focus_window(&self) { pub fn focus_window(&self) {
x11_or_wayland!(match self; Window(w) => w.focus_window()) match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.focus_window(),
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
} }
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) { pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type)) match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.request_user_attention(request_type),
#[cfg(wayland_platform)]
Window::Wayland(ref w) => w.request_user_attention(request_type),
}
} }
#[inline] #[inline]
@@ -504,14 +547,20 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.request_redraw()) x11_or_wayland!(match self; Window(w) => w.request_redraw())
} }
#[inline]
pub fn pre_present_notify(&self) {
x11_or_wayland!(match self; Window(w) => w.pre_present_notify())
}
#[inline] #[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> { pub fn current_monitor(&self) -> Option<MonitorHandle> {
Some(x11_or_wayland!(match self; Window(w) => w.current_monitor()?; as MonitorHandle)) match self {
#[cfg(x11_platform)]
Window::X(ref window) => {
let current_monitor = MonitorHandle::X(window.current_monitor());
Some(current_monitor)
}
#[cfg(wayland_platform)]
Window::Wayland(ref window) => {
let current_monitor = MonitorHandle::Wayland(window.current_monitor()?);
Some(current_monitor)
}
}
} }
#[inline] #[inline]
@@ -534,39 +583,25 @@ impl Window {
#[inline] #[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> { pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(x11_or_wayland!(match self; Window(w) => w.primary_monitor()?; as MonitorHandle)) match self {
#[cfg(x11_platform)]
Window::X(ref window) => {
let primary_monitor = MonitorHandle::X(window.primary_monitor());
Some(primary_monitor)
}
#[cfg(wayland_platform)]
Window::Wayland(ref window) => window.primary_monitor(),
}
} }
#[cfg(feature = "rwh_04")]
#[inline] #[inline]
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { pub fn raw_window_handle(&self) -> RawWindowHandle {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_04()) x11_or_wayland!(match self; Window(window) => window.raw_window_handle())
} }
#[cfg(feature = "rwh_05")]
#[inline] #[inline]
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { pub fn raw_display_handle(&self) -> RawDisplayHandle {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_05()) x11_or_wayland!(match self; Window(window) => window.raw_display_handle())
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_05())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_06())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_06())
} }
#[inline] #[inline]
@@ -579,10 +614,6 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.theme()) x11_or_wayland!(match self; Window(window) => window.theme())
} }
pub fn set_content_protected(&self, protected: bool) {
x11_or_wayland!(match self; Window(window) => window.set_content_protected(protected))
}
#[inline] #[inline]
pub fn has_focus(&self) -> bool { pub fn has_focus(&self) -> bool {
x11_or_wayland!(match self; Window(window) => window.has_focus()) x11_or_wayland!(match self; Window(window) => window.has_focus())
@@ -593,35 +624,6 @@ impl Window {
} }
} }
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>,
pub key_without_modifiers: Key,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) enum PlatformCustomCursor {
#[cfg(wayland_platform)]
Wayland(wayland::CustomCursor),
#[cfg(x11_platform)]
X(x11::CustomCursor),
}
impl PlatformCustomCursor {
pub(crate) fn build(
builder: PlatformCustomCursorBuilder,
p: &EventLoopWindowTarget,
) -> PlatformCustomCursor {
match p {
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(_) => {
Self::Wayland(wayland::CustomCursor::build(builder, p))
}
#[cfg(x11_platform)]
EventLoopWindowTarget::X(p) => Self::X(x11::CustomCursor::build(builder, p)),
}
}
}
/// Hooks for X11 errors. /// Hooks for X11 errors.
#[cfg(x11_platform)] #[cfg(x11_platform)]
pub(crate) static mut XLIB_ERROR_HOOKS: Lazy<Mutex<Vec<XlibErrorHook>>> = pub(crate) static mut XLIB_ERROR_HOOKS: Lazy<Mutex<Vec<XlibErrorHook>>> =
@@ -636,36 +638,31 @@ unsafe extern "C" fn x_error_callback(
if let Ok(ref xconn) = *xconn_lock { if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks. // Call all the hooks.
let mut error_handled = false; let mut error_handled = false;
for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() { for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
error_handled |= hook(display as *mut _, event as *mut _); error_handled |= hook(display as *mut _, event as *mut _);
} }
// `assume_init` is safe here because the array consists of `MaybeUninit` values, // `assume_init` is safe here because the array consists of `MaybeUninit` values,
// which do not require initialization. // which do not require initialization.
let mut buf: [MaybeUninit<c_char>; 1024] = unsafe { MaybeUninit::uninit().assume_init() }; let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
unsafe { (xconn.xlib.XGetErrorText)(
(xconn.xlib.XGetErrorText)( display,
display, (*event).error_code as c_int,
(*event).error_code as c_int, buf.as_mut_ptr() as *mut c_char,
buf.as_mut_ptr() as *mut c_char, buf.len() as c_int,
buf.len() as c_int, );
) let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy();
};
let description =
unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy();
let error = unsafe { let error = XError {
XError { description: description.into_owned(),
description: description.into_owned(), error_code: (*event).error_code,
error_code: (*event).error_code, request_code: (*event).request_code,
request_code: (*event).request_code, minor_code: (*event).minor_code,
minor_code: (*event).minor_code,
}
}; };
// Don't log error. // Don't log error.
if !error_handled { if !error_handled {
log::error!("X11 error: {:#?}", error); error!("X11 error: {:#?}", error);
// XXX only update the error, if it wasn't handled by any of the hooks. // XXX only update the error, if it wasn't handled by any of the hooks.
*xconn.latest_error.lock().unwrap() = Some(error); *xconn.latest_error.lock().unwrap() = Some(error);
} }
@@ -695,9 +692,7 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
} }
impl<T: 'static> EventLoop<T> { impl<T: 'static> EventLoop<T> {
pub(crate) fn new( pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
if !attributes.any_thread && !is_main_thread() { if !attributes.any_thread && !is_main_thread() {
panic!( panic!(
"Initializing the event loop outside of the main thread is a significant \ "Initializing the event loop outside of the main thread is a significant \
@@ -707,60 +702,73 @@ impl<T: 'static> EventLoop<T> {
); );
} }
// NOTE: Wayland first because of X11 could be present under Wayland as well. Empty #[cfg(x11_platform)]
// variables are also treated as not set. if attributes.forced_backend == Some(Backend::X) {
let backend = match ( // TODO: Propagate
attributes.forced_backend, return EventLoop::new_x11_any_thread().unwrap();
env::var("WAYLAND_DISPLAY") }
.ok()
.filter(|var| !var.is_empty()) #[cfg(wayland_platform)]
.or_else(|| env::var("WAYLAND_SOCKET").ok()) if attributes.forced_backend == Some(Backend::Wayland) {
.filter(|var| !var.is_empty()) // TODO: Propagate
.is_some(), return EventLoop::new_wayland_any_thread().expect("failed to open Wayland connection");
env::var("DISPLAY") }
.map(|var| !var.is_empty())
.unwrap_or(false), if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
) { match env_var.as_str() {
// User is forcing a backend. "x11" => {
(Some(backend), _, _) => backend, // TODO: propagate
// Wayland is present. #[cfg(x11_platform)]
#[cfg(wayland_platform)] return EventLoop::new_x11_any_thread()
(None, true, _) => Backend::Wayland, .expect("Failed to initialize X11 backend");
// X11 is present. #[cfg(not(x11_platform))]
#[cfg(x11_platform)] panic!("x11 feature is not enabled")
(None, _, true) => Backend::X, }
// No backend is present. "wayland" => {
(_, wayland_display, x11_display) => { #[cfg(wayland_platform)]
let msg = if wayland_display && !cfg!(wayland_platform) { return EventLoop::new_wayland_any_thread()
"DISPLAY is not set; note: enable the `winit/wayland` feature to support Wayland" .expect("Failed to initialize Wayland backend");
} else if x11_display && !cfg!(x11_platform) { #[cfg(not(wayland_platform))]
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the `winit/x11` feature to support X11" panic!("wayland feature is not enabled");
} else { }
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set." _ => panic!(
}; "Unknown environment variable value for {BACKEND_PREFERENCE_ENV_VAR}, try one of `x11`,`wayland`",
return Err(EventLoopError::Os(os_error!(OsError::Misc(msg)))); ),
} }
}
#[cfg(wayland_platform)]
let wayland_err = match EventLoop::new_wayland_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
}; };
// Create the display based on the backend. #[cfg(x11_platform)]
match backend { let x11_err = match EventLoop::new_x11_any_thread() {
#[cfg(wayland_platform)] Ok(event_loop) => return event_loop,
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into), Err(err) => err,
#[cfg(x11_platform)] };
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
} #[cfg(not(wayland_platform))]
let wayland_err = "backend disabled";
#[cfg(not(x11_platform))]
let x11_err = "backend disabled";
panic!(
"Failed to initialize any backend! Wayland status: {wayland_err:?} X11 status: {x11_err:?}",
);
} }
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
fn new_wayland_any_thread() -> Result<EventLoop<T>, EventLoopError> { fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp))) wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
} }
#[cfg(x11_platform)] #[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> { fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
let xconn = match X11_BACKEND.lock().unwrap().as_ref() { let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
Ok(xconn) => xconn.clone(), Ok(xconn) => xconn.clone(),
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())), Err(err) => return Err(err.clone()),
}; };
Ok(EventLoop::X(x11::EventLoop::new(xconn))) Ok(EventLoop::X(x11::EventLoop::new(xconn)))
@@ -770,58 +778,39 @@ impl<T: 'static> EventLoop<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
} }
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError> pub fn run_return<F>(&mut self, callback: F) -> i32
where where
F: FnMut(crate::event::Event<T>, &RootELW), F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{ {
self.run_on_demand(callback) x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_return(callback))
} }
pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError> pub fn run<F>(self, callback: F) -> !
where where
F: FnMut(crate::event::Event<T>, &RootELW), F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{ {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback)) x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback))
} }
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
}
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target()) x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target())
} }
} }
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd())
}
}
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd())
}
}
impl<T: 'static> EventLoopProxy<T> { impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event)) x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event))
} }
} }
pub enum EventLoopWindowTarget { pub enum EventLoopWindowTarget<T> {
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
Wayland(wayland::EventLoopWindowTarget), Wayland(wayland::EventLoopWindowTarget<T>),
#[cfg(x11_platform)] #[cfg(x11_platform)]
X(x11::EventLoopWindowTarget), X(x11::EventLoopWindowTarget<T>),
} }
impl EventLoopWindowTarget { impl<T> EventLoopWindowTarget<T> {
#[inline] #[inline]
pub fn is_wayland(&self) -> bool { pub fn is_wayland(&self) -> bool {
match *self { match *self {
@@ -838,152 +827,69 @@ impl EventLoopWindowTarget {
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(ref evlp) => evlp EventLoopWindowTarget::Wayland(ref evlp) => evlp
.available_monitors() .available_monitors()
.into_iter()
.map(MonitorHandle::Wayland) .map(MonitorHandle::Wayland)
.collect(), .collect(),
#[cfg(x11_platform)] #[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => { EventLoopWindowTarget::X(ref evlp) => evlp
evlp.available_monitors().map(MonitorHandle::X).collect() .x_connection()
} .available_monitors()
.into_iter()
.map(MonitorHandle::X)
.collect(),
} }
} }
#[inline] #[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> { pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some( match *self {
x11_or_wayland!(match self; EventLoopWindowTarget(evlp) => evlp.primary_monitor()?; as MonitorHandle),
)
}
#[inline]
pub fn listen_device_events(&self, allowed: DeviceEvents) {
x11_or_wayland!(match self; Self(evlp) => evlp.listen_device_events(allowed))
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_06())
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_control_flow(control_flow))
}
pub(crate) fn control_flow(&self) -> ControlFlow {
x11_or_wayland!(match self; Self(evlp) => evlp.control_flow())
}
pub(crate) fn clear_exit(&self) {
x11_or_wayland!(match self; Self(evlp) => evlp.clear_exit())
}
pub(crate) fn exit(&self) {
x11_or_wayland!(match self; Self(evlp) => evlp.exit())
}
pub(crate) fn exiting(&self) -> bool {
x11_or_wayland!(match self; Self(evlp) => evlp.exiting())
}
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
match self {
#[cfg(x11_platform)]
Self::X(conn) => OwnedDisplayHandle::X(conn.x_connection().clone()),
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
Self::Wayland(conn) => OwnedDisplayHandle::Wayland(conn.connection.clone()), EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(),
}
}
fn set_exit_code(&self, code: i32) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code))
}
fn exit_code(&self) -> Option<i32> {
x11_or_wayland!(match self; Self(evlp) => evlp.exit_code())
}
}
#[derive(Clone)]
#[allow(dead_code)]
pub(crate) enum OwnedDisplayHandle {
#[cfg(x11_platform)]
X(Arc<XConnection>),
#[cfg(wayland_platform)]
Wayland(wayland_client::Connection),
}
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
match self {
#[cfg(x11_platform)] #[cfg(x11_platform)]
Self::X(xconn) => { EventLoopWindowTarget::X(ref evlp) => {
let mut xlib_handle = rwh_05::XlibDisplayHandle::empty(); let primary_monitor = MonitorHandle::X(evlp.x_connection().primary_monitor());
xlib_handle.display = xconn.display.cast(); Some(primary_monitor)
xlib_handle.screen = xconn.default_screen_index() as _;
xlib_handle.into()
}
#[cfg(wayland_platform)]
Self::Wayland(conn) => {
use sctk::reexports::client::Proxy;
let mut wayland_handle = rwh_05::WaylandDisplayHandle::empty();
wayland_handle.display = conn.display().id().as_ptr() as *mut _;
wayland_handle.into()
} }
} }
} }
#[cfg(feature = "rwh_06")]
#[inline] #[inline]
pub fn raw_display_handle_rwh_06( pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
&self, match *self {
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
use std::ptr::NonNull;
match self {
#[cfg(x11_platform)]
Self::X(xconn) => Ok(rwh_06::XlibDisplayHandle::new(
NonNull::new(xconn.display.cast()),
xconn.default_screen_index() as _,
)
.into()),
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
Self::Wayland(conn) => { EventLoopWindowTarget::Wayland(_) => (),
use sctk::reexports::client::Proxy; #[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => evlp.set_device_event_filter(_filter),
Ok(rwh_06::WaylandDisplayHandle::new(
NonNull::new(conn.display().id().as_ptr().cast()).unwrap(),
)
.into())
}
} }
} }
pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle())
}
} }
/// Returns the minimum `Option<Duration>`, taking into account that `None` fn sticky_exit_callback<T, F>(
/// equates to an infinite timeout, not a zero timeout (so can't just use evt: Event<'_, T>,
/// `Option::min`) target: &RootELW<T>,
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> { control_flow: &mut ControlFlow,
a.map_or(b, |a_timeout| { callback: &mut F,
b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) ) where
}) F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
// make ControlFlow::ExitWithCode sticky by providing a dummy
// control flow reference if it is already ExitWithCode.
if let ControlFlow::ExitWithCode(code) = *control_flow {
callback(evt, target, &mut ControlFlow::ExitWithCode(code))
} else {
callback(evt, target, control_flow)
}
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn is_main_thread() -> bool { fn is_main_thread() -> bool {
rustix::thread::gettid() == rustix::process::getpid() use libc::{c_long, getpid, syscall, SYS_gettid};
unsafe { syscall(SYS_gettid) == getpid() as c_long }
} }
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]

View File

@@ -0,0 +1,183 @@
//! 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::reexports::protocols::viewporter::client::wp_viewporter::WpViewporter;
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;
use crate::platform_impl::wayland::protocols::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
/// 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,
WpFractionalScaleManagerV1 => fractional_scale_manager,
WpViewporter => viewporter,
],
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>,
fractional_scale_manager: SimpleGlobal<WpFractionalScaleManagerV1>,
viewporter: SimpleGlobal<WpViewporter>,
}
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();
// Fractional surface scaling.
let fractional_scale_manager = SimpleGlobal::new();
// Surface resizing (used for fractional scaling).
let viewporter = SimpleGlobal::new();
Self {
seats,
outputs,
shm,
compositor,
subcompositor,
shell,
decoration_manager,
relative_pointer_manager,
pointer_constraints,
text_input_manager,
xdg_activation,
fractional_scale_manager,
viewporter,
}
}
}
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)
}
}

View File

@@ -0,0 +1,601 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::io::Result as IOResult;
use std::mem;
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, WindowCompositorUpdate, WindowUserRequest};
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> {
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// 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>,
/// 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 |_, _, state| {
// Drain events here as well to account for application doing batch event processing
// on RedrawEventsCleared.
shim::handle_window_requests(state);
})?;
let event_loop_handle = event_loop.handle();
let window_map = HashMap::new();
let event_sink = EventSink::new();
let window_user_requests = HashMap::new();
let window_compositor_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_user_requests,
window_compositor_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_compositor_updates: Vec<(WindowId, WindowCompositorUpdate)> = Vec::new();
let mut window_user_requests: Vec<(WindowId, WindowUserRequest)> = 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(x11_platform)]
_ => 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 from compositor.
self.with_state(|state| {
window_compositor_updates.clear();
window_compositor_updates.extend(
state
.window_compositor_updates
.iter_mut()
.map(|(wid, window_update)| (*wid, mem::take(window_update))),
);
});
for (window_id, window_compositor_update) in window_compositor_updates.iter_mut() {
if let Some(scale_factor) = window_compositor_update.scale_factor {
let mut physical_size = self.with_state(|state| {
let window_handle = state.window_map.get(window_id).unwrap();
*window_handle.scale_factor.lock().unwrap() = scale_factor;
let mut size = window_handle.size.lock().unwrap();
// Update the new logical size if it was changed.
let window_size = window_compositor_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_compositor_update.size = Some(new_logical_size);
}
if let Some(size) = window_compositor_update.size.take() {
let physical_size = self.with_state(|state| {
let window_handle = state.window_map.get_mut(window_id).unwrap();
if let Some(fs_state) = window_handle.fractional_scaling_state.as_ref() {
// If we have a viewport then we support fractional scaling. As per the
// protocol, we have to set the viewport size of the size prior scaling.
fs_state
.viewport
.set_destination(size.width as _, size.height as _);
}
let mut window_size = window_handle.size.lock().unwrap();
// Always issue resize event on scale factor change.
let physical_size = if window_compositor_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 = window_handle.scale_factor();
let physical_size = size.to_physical(scale_factor);
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();
// Update the opaque region.
window_handle.set_transparent(window_handle.transparent.get());
// Mark that refresh isn't required, since we've done it right now.
state
.window_user_requests
.get_mut(window_id)
.unwrap()
.refresh_frame = false;
// Queue redraw requested.
state
.window_user_requests
.get_mut(window_id)
.unwrap()
.redraw_requested = true;
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 the close is requested, send it here.
if window_compositor_update.close_window {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
// 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,
);
// Apply user requests, so every event required resize and latter surface commit will
// be applied right before drawing. This will also ensure that every `RedrawRequested`
// event will be delivered in time.
self.with_state(|state| {
shim::handle_window_requests(state);
});
// Process 'new' pending updates from compositor.
self.with_state(|state| {
window_user_requests.clear();
window_user_requests.extend(
state
.window_user_requests
.iter_mut()
.map(|(wid, window_request)| (*wid, mem::take(window_request))),
);
});
// Handle RedrawRequested events.
for (window_id, mut window_request) in window_user_requests.iter() {
// Handle refresh of the frame.
if window_request.refresh_frame {
self.with_state(|state| {
let window_handle = state.window_map.get_mut(window_id).unwrap();
window_handle.window.refresh();
});
// In general refreshing the frame requires surface commit, those force user
// to redraw.
window_request.redraw_requested = true;
}
// Handle redraw request.
if window_request.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(x11_platform)]
_ => 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(x11_platform)]
_ => unreachable!(),
};
self.event_loop
.dispatch(timeout, state)
.map_err(|error| error.into())
}
}

View File

@@ -0,0 +1,36 @@
//! 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

@@ -0,0 +1,31 @@
//! A state that we pass around in a dispatch.
use std::collections::HashMap;
use super::EventSink;
use crate::platform_impl::wayland::window::shim::{
WindowCompositorUpdate, WindowHandle, WindowUserRequest,
};
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 comming from the user requests. Those are separatelly dispatched right after
/// `MainEventsCleared`.
pub window_user_requests: HashMap<WindowId, WindowUserRequest>,
/// 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_compositor_updates: HashMap<WindowId, WindowCompositorUpdate>,
/// 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,29 @@
#![cfg(wayland_platform)]
use sctk::reexports::client::protocol::wl_surface::WlSurface;
pub use crate::platform_impl::platform::WindowId;
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use output::{MonitorHandle, VideoMode};
pub use window::Window;
mod env;
mod event_loop;
mod output;
mod protocols;
mod seat;
mod window;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub const unsafe fn dummy() -> Self {
DeviceId
}
}
#[inline]
fn make_wid(surface: &WlSurface) -> WindowId {
WindowId(surface.as_ref().c_ptr() as u64)
}

View File

@@ -0,0 +1,247 @@
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::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 = PlatformVideoMode> {
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| {
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) -> PlatformMonitorHandle {
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<PlatformMonitorHandle> {
// There's no primary monitor on Wayland.
None
}
}

View File

@@ -0,0 +1,13 @@
#![allow(dead_code, non_camel_case_types, unused_unsafe, unused_variables)]
#![allow(non_upper_case_globals, non_snake_case, unused_imports)]
#![allow(missing_docs, clippy::all)]
use wayland_client::protocol::wl_surface;
use wayland_client::sys;
use wayland_client::{AnonymousObject, Attached, Main, Proxy, ProxyMap};
use wayland_commons::map::{Object, ObjectMetadata};
use wayland_commons::smallvec;
use wayland_commons::wire::{Argument, ArgumentType, Message, MessageDesc};
use wayland_commons::{Interface, MessageGroup};
include!(concat!(env!("OUT_DIR"), "/fractional_scale_v1.rs"));

View File

@@ -0,0 +1,165 @@
//! Handling of various keyboard events.
use std::sync::atomic::Ordering;
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);
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
window_handle.has_focus.store(true, Ordering::Relaxed);
// 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, .. } => {
// Reset the id.
inner.target_window_id = None;
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 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_handle.has_focus.store(false, Ordering::Relaxed);
// Window lost focus.
event_sink.push_window_event(WindowEvent::Focused(false), window_id);
}
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

@@ -0,0 +1,192 @@
//! 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,
}
}

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