Compare commits

..

17 Commits

Author SHA1 Message Date
John Nunley
d28c867b61 chore: Rebase on latest master
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:41 -08:00
John Nunley
cfd35d2708 ci: Missed a spot
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:11 -08:00
John Nunley
1b9c576f53 ci: Use -p option in CI
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:10 -08:00
John Nunley
52532d2579 chore: Fix failing CI
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:10 -08:00
John Nunley
4b28f10470 chore: fmt and doc fixes
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:10 -08:00
John Nunley
daf1304879 chore: Housekeeping
- Update CHANGELOG
- Add documentation to items I forgot to document
- Trait implementations

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:08 -08:00
John Nunley
c6e57430bb chore: Move over ControlFlow
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:01:49 -08:00
John Nunley
284f4c0558 feat: Move Event to winit-core
This requires a lot of restructuring on the backend side, as we need to
accommodate for backends not being able to construct these types
implicitly. In addition, we also need to expose some new APIs on these
types to make them usable.

Several breaking changes occurred in this commit.

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:01:49 -08:00
John Nunley
859a6f109c feat: Move DeviceId to winit-core
This requires a similar restructuring as with WindowId.

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:01:49 -08:00
John Nunley
a232f7bc77 feat: Move WindowId to winit-core
This commit moves WindowId to winit-core and replaces its inner type
with a platform-agnostic `u64`. This required some changes to many of
the backend in order to accommodate this new system.

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:01:46 -08:00
John Nunley
48831f7910 feat: Re-export cursor types in winit-core/window
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:06 -08:00
John Nunley
114e6999d3 chore: Move parts of window.rs to winit-core
As winit no longer owns some of the types this requires some adjustments
in the Wayland backend.

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:06 -08:00
John Nunley
2f197315d9 chore: Move most of keyboard.rs to winit-core
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:06 -08:00
John Nunley
557464489e chore: Move dpi.rs to winit-core wholesale
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:06 -08:00
John Nunley
623b3e5070 chore: Move part of error.rs to winit-core
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:06 -08:00
John Nunley
a97255e0fc chore: Bring up winit-core crate
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:05 -08:00
John Nunley
3a817647d5 chore: Move winit files to winit subfolder
This will allow us to split off crates from the main winit crate.

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:58:48 -08:00
254 changed files with 9415 additions and 8674 deletions

View File

@@ -35,7 +35,7 @@ jobs:
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } - { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' } - { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' } - { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } - { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--features=android-native-activity', cmd: 'apk --' }
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, } - { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, } - { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, } - { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
@@ -89,7 +89,7 @@ jobs:
- name: Generate lockfile - name: Generate lockfile
# Also updates the crates.io index # Also updates the crates.io index
run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0 run: cargo generate-lockfile
- name: Install GCC Multilib - name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
@@ -125,13 +125,13 @@ jobs:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}' RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}'
- name: Build crate - name: Build crate
run: cargo $CMD build $OPTIONS run: cargo $CMD build -p winit $OPTIONS
- name: Build tests - name: Build tests
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.toolchain != '1.70.0'
run: cargo $CMD test --no-run $OPTIONS run: cargo $CMD test -p winit --no-run $OPTIONS
- name: Run tests - name: Run tests
if: > if: >
@@ -150,7 +150,7 @@ jobs:
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.toolchain != '1.70.0'
run: cargo $CMD test --no-run $OPTIONS --features serde run: cargo $CMD test --no-run $OPTIONS -p winit --features serde
- name: Run tests with serde enabled - name: Run tests with serde enabled
if: > if: >

View File

@@ -11,20 +11,15 @@ Unreleased` header.
# Unreleased # Unreleased
- Move `dpi` types to its own crate, and re-export it from the root crate. - On X11, fix swapped instance and general class names.
- Implement `Sync` for `EventLoopProxy<T: Send>`.
- **Breaking:** Move `Window::new` to `ActiveEventLoop::create_window` and `EventLoop::create_window` (with the latter being deprecated).
- **Breaking:** Rename `EventLoopWindowTarget` to `ActiveEventLoop`.
- **Breaking:** Remove `Deref` implementation for `EventLoop` that gave `EventLoopWindowTarget`.
- **Breaking**: Remove `WindowBuilder` in favor of `WindowAttributes`.
- **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`. - **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`.
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example. - On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
- **Breaking:** Remove `Window::set_cursor_icon` - **Breaking:** Remove `Window::set_cursor_icon`
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor` - 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 `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs. - Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s. - Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s.
- Add `{Active,}EventLoop::create_custom_cursor` to load custom cursor image sources.
- On macOS, add services menu. - On macOS, add services menu.
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation. - **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
- On Web, fix setting cursor icon overriding cursor visibility. - On Web, fix setting cursor icon overriding cursor visibility.
@@ -41,47 +36,27 @@ Unreleased` header.
- on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`. - 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: 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, 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. - **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`. - 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:** 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`. - **Breaking:** Removed `EventLoopBuilder::with_user_event`, the functionality is now available in `EventLoop::with_user_event`.
- Add `Window::default_attributes` to get default `WindowAttributes`. - Add `Window::builder`, which is intended to replace the (now deprecated) `WindowBuilder::new`.
# 0.29.11
- Fix compatibility with 32-bit platforms without 64-bit atomics.
- On macOS, fix incorrect IME cursor rect origin.
- On Windows, fixed a race condition when sending an event through the loop proxy.
- On X11, fix swapped instance and general class names.
- On X11, don't require XIM to run.
- On X11, fix xkb state not being updated correctly sometimes leading to wrong input.
- On X11, reload dpi on `_XSETTINGS_SETTINGS` update. - On X11, reload dpi on `_XSETTINGS_SETTINGS` update.
- On X11, fix deadlock when adjusting DPI and resizing at the same time. - On X11, fix deadlock when adjusting DPI and resizing at the same time.
- On Wayland, disable `Occluded` event handling.
- On Wayland, fix DeviceEvent::Motion not being sent
- On Wayland, fix `Focused(false)` being send when other seats still have window focused. - 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 Wayland, fix `Window::set_{min,max}_inner_size` not always applied.
- On Wayland, fix title in CSD not updated from `AboutToWait`.
- On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop. - On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop.
- On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform. - On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
- On Orbital, fix `logical_key` and `text` not reported in `KeyEvent`. - **Breaking:** Move some types to the `winit-core` crate. Most types are
- On Orbital, implement `KeyEventExtModifierSupplement`. re-exported verbatim; however:
- On Orbital, map keys to `NamedKey` when possible. - `Event`, `WindowEvent` and `KeyEvent` now take a generic that `winit`
- On Orbital, implement `set_cursor_grab`. implements.
- On Orbital, implement `set_cursor_visible`. - `ActivationToken` and `InnerSizeWriter` expose new methods to make them
- On Orbital, implement `drag_window`. useful.
- On Orbital, implement `drag_resize_window`. - `InnerSizeWriter::request_inner_size` returns an `InnerSizeIgnored` error
- On Orbital, implement `set_transparent`. instead of an `ExternalError`.
- On Orbital, implement `set_visible`.
- On Orbital, implement `is_visible`.
- On Orbital, implement `set_resizable`.
- On Orbital, implement `is_resizable`.
- On Orbital, implement `set_maximized`.
- On Orbital, implement `is_maximized`.
- On Orbital, implement `set_decorations`.
- On Orbital, implement `is_decorated`.
- On Orbital, implement `set_window_level`.
- On Orbital, emit `DeviceEvent::MouseMotion`.
# 0.29.10 # 0.29.10
@@ -143,7 +118,7 @@ Unreleased` header.
# 0.29.3 # 0.29.3
- On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible. - On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible.
- On Wayland, fix `RedrawRequested` being always sent without decorations and `sctk-adwaita` feature. - 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, 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 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, fix deadlock when accessing the state during `Cursor{Enter,Leave}`.
@@ -395,7 +370,7 @@ Unreleased` header.
- **Breaking:**: Removed deprecated method `platform::unix::WindowExtUnix::is_ready`. - **Breaking:**: Removed deprecated method `platform::unix::WindowExtUnix::is_ready`.
- Removed `parking_lot` dependency. - Removed `parking_lot` dependency.
- **Breaking:** On macOS, add support for two-finger touchpad magnification and rotation gestures with new events `WindowEvent::TouchpadMagnify` and `WindowEvent::TouchpadRotate`. Also add support for touchpad smart-magnification gesture with a new event `WindowEvent::SmartMagnify`. - **Breaking:** On macOS, add support for two-finger touchpad magnification and rotation gestures with new events `WindowEvent::TouchpadMagnify` and `WindowEvent::TouchpadRotate`. Also add support for touchpad smart-magnification gesture with a new event `WindowEvent::SmartMagnify`.
- **Breaking:** On Web, the `WindowBuilderExtWebSys::with_prevent_default` setting (enabled by default), now additionally prevents scrolling of the webpage in mobile browsers, previously it only disabled scrolling on desktop. - **Breaking:** On web, the `WindowBuilderExtWebSys::with_prevent_default` setting (enabled by default), now additionally prevents scrolling of the webpage in mobile browsers, previously it only disabled scrolling on desktop.
- On Wayland, `wayland-csd-adwaita` now uses `ab_glyph` instead of `crossfont` to render the title for decorations. - On Wayland, `wayland-csd-adwaita` now uses `ab_glyph` instead of `crossfont` to render the title for decorations.
- On Wayland, a new `wayland-csd-adwaita-crossfont` feature was added to use `crossfont` instead of `ab_glyph` for decorations. - On Wayland, a new `wayland-csd-adwaita-crossfont` feature was added to use `crossfont` instead of `ab_glyph` for decorations.
- On Wayland, if not otherwise specified use upstream automatic CSD theme selection. - On Wayland, if not otherwise specified use upstream automatic CSD theme selection.
@@ -415,7 +390,7 @@ Unreleased` header.
- Added `Window::set_transparent` to provide a hint about transparency of the window on Wayland and macOS. - Added `Window::set_transparent` to provide a hint about transparency of the window on Wayland and macOS.
- On macOS, fix the mouse buttons other than left/right/middle being reported as middle. - On macOS, fix the mouse buttons other than left/right/middle being reported as middle.
- On Wayland, support fractional scaling via the wp-fractional-scale protocol. - On Wayland, support fractional scaling via the wp-fractional-scale protocol.
- On Web, fix removal of mouse event listeners from the global object upon window destruction. - On web, fix removal of mouse event listeners from the global object upon window distruction.
- Add WindowAttributes getter to WindowBuilder to allow introspection of default values. - Add WindowAttributes getter to WindowBuilder to allow introspection of default values.
- Added `Window::set_ime_purpose` for setting the IME purpose, currently implemented on Wayland only. - Added `Window::set_ime_purpose` for setting the IME purpose, currently implemented on Wayland only.
@@ -521,7 +496,7 @@ Unreleased` header.
- On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated. - On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated.
- On Windows, fix focus events being sent to inactive windows. - On Windows, fix focus events being sent to inactive windows.
- **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`. - **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`.
- On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors coming from Xlib. - On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors comming from Xlib.
- On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`. - On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`.
- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability. - All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability.
- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down. - **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down.
@@ -820,7 +795,7 @@ Unreleased` header.
- On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`. - On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`.
- On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode. - On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode.
- On iOS, fix panic upon closing the app. - On iOS, fix panic upon closing the app.
- On X11, allow setting multiple `XWindowType`s. - On X11, allow setting mulitple `XWindowType`s.
- On iOS, fix null window on initial `HiDpiFactorChanged` event. - On iOS, fix null window on initial `HiDpiFactorChanged` event.
- On Windows, fix fullscreen window shrinking upon getting restored to a normal window. - On Windows, fix fullscreen window shrinking upon getting restored to a normal window.
- On macOS, fix events not being emitted during modal loops, such as when windows are being resized - On macOS, fix events not being emitted during modal loops, such as when windows are being resized
@@ -973,7 +948,7 @@ and `WindowEvent::HoveredFile`.
- On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on. - On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on.
- On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area. - On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area.
- On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled. - On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled.
- On Windows, ignore the AltGr key when populating the `ModifiersState` type. - On Windows, ignore the AltGr key when populating the `ModifersState` type.
# Version 0.18.1 (2018-12-30) # Version 0.18.1 (2018-12-30)
@@ -1253,7 +1228,7 @@ _Yanked_
# Version 0.8.2 (2017-09-28) # Version 0.8.2 (2017-09-28)
- Uniformize keyboard scancode values across Wayland and X11 (#297). - Uniformize keyboard scancode values accross Wayland and X11 (#297).
- Internal rework of the wayland event loop - Internal rework of the wayland event loop
- Added method `os::linux::WindowExt::is_ready` - Added method `os::linux::WindowExt::is_ready`
@@ -1267,7 +1242,7 @@ _Yanked_
- Added `Window::set_maximized`, `WindowAttributes::maximized` and `WindowBuilder::with_maximized`. - Added `Window::set_maximized`, `WindowAttributes::maximized` and `WindowBuilder::with_maximized`.
- Added `Window::set_fullscreen`. - Added `Window::set_fullscreen`.
- Changed `with_fullscreen` to take a `Option<MonitorId>` instead of a `MonitorId`. - Changed `with_fullscreen` to take a `Option<MonitorId>` instead of a `MonitorId`.
- Removed `MonitorId::get_native_identifier()` in favor of platform-specific traits in the `os` - Removed `MonitorId::get_native_identifer()` in favor of platform-specific traits in the `os`
module. module.
- Changed `get_available_monitors()` and `get_primary_monitor()` to be methods of `EventsLoop` - Changed `get_available_monitors()` and `get_primary_monitor()` to be methods of `EventsLoop`
instead of stand-alone methods. instead of stand-alone methods.

View File

@@ -42,9 +42,11 @@ Once your PR is deemed ready, the merging maintainer will take care of resolving
`CHANGELOG.md` (but you must resolve other conflicts yourself). Doing this requires that you check the `CHANGELOG.md` (but you must resolve other conflicts yourself). Doing this requires that you check the
"give contributors write access to the branch" checkbox when creating the PR. "give contributors write access to the branch" checkbox when creating the PR.
## Maintainers ## Maintainers & Testers
The current maintainers for each platform 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!
## Release process ## Release process

View File

@@ -1,277 +1,16 @@
[package]
name = "winit"
version = "0.29.11"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
keywords = ["windowing"]
readme = "README.md"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
[package.metadata.docs.rs]
features = [
"rwh_04",
"rwh_05",
"rwh_06",
"serde",
"mint",
# Enabled to get docs to compile
"android-native-activity",
]
# These are all tested in CI
targets = [
# Windows
"i686-pc-windows-msvc",
"x86_64-pc-windows-msvc",
# macOS
"x86_64-apple-darwin",
# Unix (X11 & Wayland)
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# iOS
"x86_64-apple-ios",
# Android
"aarch64-linux-android",
# Web
"wasm32-unknown-unknown",
]
rustdoc-args = ["--cfg", "docsrs"]
# Features are documented in either `lib.rs` or under `winit::platform`.
[features]
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
android-native-activity = ["android-activity/native-activity"]
android-game-activity = ["android-activity/game-activity"]
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde"]
mint = ["dpi/mint"]
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
[build-dependencies]
cfg_aliases = "0.2.0"
[dependencies]
bitflags = "2"
cursor-icon = "1.1.0"
log = "0.4"
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
serde = { workspace = true, optional = true }
smol_str = "0.2.0"
dpi = { path = "dpi" }
[dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] }
simple_logger = { version = "4.2.0", default_features = false }
winit = { path = ".", features = ["rwh_05"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
softbuffer = { version = "0.3.0", default-features = false, features = ["x11", "x11-dlopen", "wayland", "wayland-dlopen"] }
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.5.0"
ndk = { version = "0.8.0", default-features = false }
ndk-sys = "0.5.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
objc2 = "0.5.0"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1"
[target.'cfg(target_os = "macos")'.dependencies.icrate]
version = "0.1.0"
features = [
"dispatch",
"Foundation",
"Foundation_NSArray",
"Foundation_NSAttributedString",
"Foundation_NSMutableAttributedString",
"Foundation_NSData",
"Foundation_NSDictionary",
"Foundation_NSString",
"Foundation_NSProcessInfo",
"Foundation_NSThread",
"Foundation_NSNumber",
"AppKit",
"AppKit_NSAppearance",
"AppKit_NSApplication",
"AppKit_NSBitmapImageRep",
"AppKit_NSButton",
"AppKit_NSColor",
"AppKit_NSControl",
"AppKit_NSCursor",
"AppKit_NSEvent",
"AppKit_NSGraphicsContext",
"AppKit_NSImage",
"AppKit_NSImageRep",
"AppKit_NSMenu",
"AppKit_NSMenuItem",
"AppKit_NSPasteboard",
"AppKit_NSResponder",
"AppKit_NSScreen",
"AppKit_NSTextInputContext",
"AppKit_NSView",
"AppKit_NSWindow",
"AppKit_NSWindowTabGroup",
]
[target.'cfg(target_os = "ios")'.dependencies.icrate]
version = "0.1.0"
features = [
"dispatch",
"Foundation",
"Foundation_NSArray",
"Foundation_NSString",
"Foundation_NSProcessInfo",
"Foundation_NSThread",
"Foundation_NSSet",
]
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.48"
features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Media",
"Win32_System_Com_StructuredStorage",
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
"Win32_System_WindowsProgramming",
"Win32_UI_Accessibility",
"Win32_UI_Controls",
"Win32_UI_HiDpi",
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Input_Pointer",
"Win32_UI_Input_Touch",
"Win32_UI_Shell",
"Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging",
]
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.12.3"
libc = "0.2.64"
memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
x11-dl = { version = "2.18.5", optional = true }
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
xkbcommon-dl = "0.4.2"
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.4.1"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.64"
features = [
'AbortController',
'AbortSignal',
'Blob',
'console',
'CssStyleDeclaration',
'Document',
'DomException',
'DomRect',
'DomRectReadOnly',
'Element',
'Event',
'EventTarget',
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'HtmlImageElement',
'ImageBitmap',
'ImageBitmapOptions',
'ImageBitmapRenderingContext',
'ImageData',
'IntersectionObserver',
'IntersectionObserverEntry',
'KeyboardEvent',
'MediaQueryList',
'MessageChannel',
'MessagePort',
'Node',
'PageTransitionEvent',
'PointerEvent',
'PremultiplyAlpha',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'VisibilityState',
'Window',
'WheelEvent',
'Url',
]
[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.64"
pin-project = "1"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-time = "1"
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
atomic-waker = "1"
concurrent-queue = { version = "2", default-features = false }
[target.'cfg(target_family = "wasm")'.dev-dependencies]
console_log = "1"
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
[[example]]
doc-scrape-examples = true
name = "full"
[workspace] [workspace]
resolver = "2"
members = [ members = [
"dpi",
"run-wasm", "run-wasm",
"winit",
"winit-core"
] ]
resolver = "2"
[workspace.package]
rust-version = "1.70.0"
repository = "https://github.com/rust-windowing/winit"
license = "Apache-2.0"
edition = "2021"
[workspace.dependencies] [workspace.dependencies]
bitflags = "2"
cfg_aliases = "0.2.0"
cursor-icon = "1.1.0"
serde = { version = "1", features = ["serde_derive"] } serde = { version = "1", features = ["serde_derive"] }
mint = "0.5.6" smol_str = "0.2.0"
web-time = "1"
winit-core = { path = "./winit-core", default-features = false, features = ["std"] }

View File

@@ -3,8 +3,8 @@
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 - Windows 7+ (10+ is tested regularly)
- macOS - macOS 10.7+ (10.14+ is tested regularly)
- Unix - Unix
- via X11 - via X11
- via Wayland - via Wayland
@@ -13,6 +13,9 @@ be used to create both games and applications. It supports the following main gr
- iOS - iOS
- Android - Android
- Web - Web
- Chrome
- 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
@@ -148,6 +151,7 @@ If your PR makes notable changes to Winit's features, please update this section
* Setting the X11 parent window * Setting the X11 parent window
### iOS ### iOS
* `winit` has a minimum OS requirement of iOS 8
* Get the `UIScreen` object pointer * Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor * Setting the `UIView` hidpi factor
* Valid orientations * Valid orientations
@@ -160,6 +164,9 @@ If your PR makes notable changes to Winit's features, please update this section
### Web ### Web
* Get if the systems preferred color scheme is "dark" * Get if the systems preferred color scheme is "dark"
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
## Compatibility Matrix ## Compatibility Matrix
Legend: Legend:

128
README.md
View File

@@ -8,7 +8,7 @@
```toml ```toml
[dependencies] [dependencies]
winit = "0.29.11" winit = "0.29.10"
``` ```
## [Documentation](https://docs.rs/winit) ## [Documentation](https://docs.rs/winit)
@@ -21,7 +21,7 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
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 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).
The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings). The maintainers have a meeting every friday at UTC 14. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
## Usage ## Usage
@@ -33,6 +33,14 @@ Winit is designed to be a low-level brick in a hierarchy of libraries. Consequen
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.
### Cargo Features
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
* `x11` (enabled by default): On Unix platform, compiles with the X11 backend
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
* `mint`: Enables mint (math interoperability standard types) conversions.
## MSRV Policy ## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
@@ -63,4 +71,118 @@ same MSRV policy.
### Platform-specific usage ### Platform-specific usage
Check out the [`winit::platform`](https://rust-windowing.github.io/winit/winit/platform/index.html) module for platform-specific usage. #### Wayland
Note that windows don't appear on Wayland until you draw/present to them.
#### Web
To run the web example: `cargo run-wasm --example web`
Winit supports compiling to the `wasm32-unknown-unknown` target with `web-sys`.
On the web platform, a Winit window is backed by a `<canvas>` element. You can
either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
create a `<canvas>` element which you can then retrieve][web canvas getter] and
insert it into the DOM yourself.
For the example code using Winit on Web, check out the [web example]. For
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
book].
[web with_canvas]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowBuilderExtWebSys.html#tymethod.with_canvas
[web canvas getter]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowExtWebSys.html#tymethod.canvas
[web example]: ./examples/web.rs
[Rust and WebAssembly book]: https://rustwasm.github.io/book/
#### Android
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/latest/ndk/) crate.
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
various life-cycle events and synchronizing with the main JVM thread.
Winit uses the [android-activity](https://github.com/rib/android-activity) as a
glue crate (prior to `0.28` it used
[ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue)).
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
application's main entry point. If Cargo resolves multiple versions, they will
clash.
`winit` glue compatibility table:
| winit | ndk-glue |
| :---: | :--------------------------: |
| 0.29 | `android-activity = "0.5"` |
| 0.28 | `android-activity = "0.4"` |
| 0.27 | `ndk-glue = "0.7"` |
| 0.26 | `ndk-glue = "0.5"` |
| 0.25 | `ndk-glue = "0.3"` |
| 0.24 | `ndk-glue = "0.2"` |
The recommended way to avoid a conflict with the glue version is to avoid explicitly
depending on the `android-activity` crate, and instead consume the API that
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:
```toml
[lib]
name = "main"
crate-type = ["cdylib"]
```
All Android applications are based on an `Activity` subclass, and the
`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:
| 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.|
| [`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`]: https://developer.android.com/games/agdk/game-activity
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
[`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
[agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
[Gradle]: https://developer.android.com/studio/build
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity`
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
1. Remove `ndk-glue` from your `Cargo.toml`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.10", features = [ "android-native-activity" ] }`
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
#### MacOS
A lot of functionality expects the application to be ready before you start
doing anything; this includes creating windows, fetching monitors, drawing,
and so on, see issues [#2238], [#2051] and [#2087].
If you encounter problems, you should try doing your initialization inside
`Event::Resumed`.
#### iOS
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
inside `Event::Resumed`.
[#2238]: https://github.com/rust-windowing/winit/issues/2238
[#2051]: https://github.com/rust-windowing/winit/issues/2051
[#2087]: https://github.com/rust-windowing/winit/issues/2087
[#1705]: https://github.com/rust-windowing/winit/issues/1705
#### Redox OS
Redox OS has some functionality not yet present that will be implemented when
its orbital display server provides it.

View File

@@ -35,7 +35,9 @@ skip = [
{ name = "bitflags" }, # the ecosystem is in the process of migrating. { name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "libloading" }, # x11rb uses a different version until the next update { name = "libloading" }, # x11rb uses a different version until the next update
] ]
skip-tree = [] skip-tree = [
{ name = "run-wasm", version = "0.1.0" }
]
[licenses] [licenses]

View File

@@ -1,39 +0,0 @@
[package]
name = "dpi"
version = "0.0.0"
description = "Types for handling UI scaling"
keywords = ["DPI", "HiDPI", "scale-factor"]
categories = ["gui"]
rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
[features]
serde = ["dep:serde"]
mint = ["dep:mint"]
[dependencies]
serde = { workspace = true, optional = true }
mint = { workspace = true, optional = true }
[package.metadata.docs.rs]
features = ["serde", "mint"]
# These are all tested in CI
targets = [
# Windows
"i686-pc-windows-msvc",
"x86_64-pc-windows-msvc",
# macOS
"x86_64-apple-darwin",
# Unix (X11 & Wayland)
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# iOS
"x86_64-apple-ios",
# Android
"aarch64-linux-android",
# Web
"wasm32-unknown-unknown",
]
rustdoc-args = ["--cfg", "docsrs"]

View File

@@ -1 +0,0 @@
../LICENSE

View File

@@ -1,847 +0,0 @@
//! An example showcasing various functionality that Winit has.
//!
//! Often used by Winit developers to test certain functionality, may be a bit
//! verbose.
use std::collections::HashMap;
use std::error::Error;
#[cfg(not(any(android_platform, ios_platform)))]
use std::num::NonZeroU32;
use cursor_icon::CursorIcon;
#[cfg(not(any(android_platform, ios_platform)))]
use rwh_05::HasRawDisplayHandle;
#[cfg(not(any(android_platform, ios_platform)))]
use softbuffer::{Context, Surface};
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event::{DeviceEvent, DeviceId, ElementState, Event, Ime, KeyEvent, WindowEvent};
use winit::event::{MouseButton, MouseScrollDelta};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState};
use winit::window::{
Cursor, CursorGrabMode, CustomCursor, Fullscreen, Icon, ResizeDirection, Theme,
};
use winit::window::{Window, WindowId};
#[cfg(macos_platform)]
use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS};
#[cfg(any(x11_platform, wayland_platform))]
use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
};
fn main() -> Result<(), Box<dyn Error>> {
let event_loop = EventLoop::<UserEvent>::with_user_event().build()?;
let _event_loop_proxy = event_loop.create_proxy();
// Wire the user event from another thread.
#[cfg(not(web_platform))]
std::thread::spawn(move || {
// Wake up the `event_loop` once every second and dispatch a custom event
// from a different thread.
println!("Starting to send user event every second");
loop {
let _ = _event_loop_proxy.send_event(UserEvent::WakeUp);
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
let mut app = Application::new(&event_loop);
event_loop.run(move |event, event_loop| match event {
Event::NewEvents(_) => (),
Event::Resumed => {
println!("Resumed the event loop");
// Create initial window.
app.create_window(event_loop, None)
.expect("failed to create initial window");
app.print_help();
}
Event::AboutToWait => {
if app.windows.is_empty() {
println!("No windows left, exiting...");
event_loop.exit();
}
}
Event::WindowEvent { window_id, event } => {
app.handle_window_event(event_loop, window_id, event)
}
Event::DeviceEvent { device_id, event } => {
app.handle_device_event(event_loop, device_id, event)
}
Event::UserEvent(event) => {
println!("User event: {event:?}");
}
Event::Suspended | Event::LoopExiting | Event::MemoryWarning => (),
})?;
Ok(())
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
enum UserEvent {
WakeUp,
}
/// Application state and event handling.
struct Application {
/// Custom cursors assets.
custom_cursors: Vec<CustomCursor>,
/// Application icon.
icon: Icon,
windows: HashMap<WindowId, WindowState>,
/// Drawing context.
///
/// With OpenGL it could be EGLDisplay.
#[cfg(not(any(android_platform, ios_platform)))]
context: Context,
}
impl Application {
fn new<T>(event_loop: &EventLoop<T>) -> Self {
// SAFETY: the context is dropped inside the loop, since the state we're using
// is moved inside the closure.
#[cfg(not(any(android_platform, ios_platform)))]
let context = unsafe { Context::from_raw(event_loop.raw_display_handle()).unwrap() };
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
// since it seems to work well enough in most cases. Be careful about going too high, or
// you'll be bitten by the low-quality downscaling built into the WM.
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/data/icon.png");
// Load icon
let icon = {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
};
println!("Loading cursor assets");
let decode_cursor = |bytes| {
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
let samples = img.into_flat_samples();
let (_, w, h) = samples.extents();
let (w, h) = (w as u16, h as u16);
CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
};
let custom_cursors = vec![
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross.png"))),
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross2.png"))),
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/gradient.png"))),
];
Self {
#[cfg(not(any(android_platform, ios_platform)))]
context,
custom_cursors,
icon,
windows: Default::default(),
}
}
fn create_window(
&mut self,
event_loop: &ActiveEventLoop,
_tab_id: Option<String>,
) -> Result<WindowId, Box<dyn Error>> {
// TODO read-out activation token.
#[allow(unused_mut)]
let mut window_attributes = Window::default_attributes()
.with_title("Winit window")
.with_transparent(true)
.with_window_icon(Some(self.icon.clone()));
#[cfg(any(x11_platform, wayland_platform))]
if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_env();
println!("Using token {:?} to activate a window", token);
window_attributes = window_attributes.with_activation_token(token);
}
#[cfg(macos_platform)]
if let Some(tab_id) = _tab_id {
window_attributes = window_attributes.with_tabbing_identifier(&tab_id);
}
let window = event_loop.create_window(window_attributes)?;
#[cfg(ios_platform)]
{
use winit::platform::ios::WindowExtIOS;
window.recognize_doubletap_gesture(true);
window.recognize_pinch_gesture(true);
window.recognize_rotation_gesture(true);
}
#[cfg(not(any(android_platform, ios_platform)))]
let surface = {
// SAFETY: the surface is dropped before the `window` which
// provided it with handle, thus it doesn't outlive it.
let mut surface = unsafe { Surface::new(&self.context, &window)? };
let size = window.inner_size();
if let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{
surface
.resize(width, height)
.expect("failed to resize inner buffer");
};
surface
};
let theme = window.theme().unwrap_or(Theme::Dark);
println!("Theme: {theme:?}");
// Allow IME out of the box.
let ime = true;
window.set_ime_allowed(ime);
let state = WindowState {
window,
custom_idx: self.custom_cursors.len() - 1,
cursor_grab: CursorGrabMode::None,
named_idx: 0,
#[cfg(not(any(android_platform, ios_platform)))]
surface,
theme,
ime,
cursor_position: Default::default(),
cursor_hidden: Default::default(),
modifiers: Default::default(),
occluded: Default::default(),
rotated: Default::default(),
zoom: Default::default(),
};
let window_id = state.window.id();
println!("Created new window with id={window_id:?}");
self.windows.insert(window_id, state);
Ok(window_id)
}
fn handle_action(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, action: Action) {
let state = self.windows.get_mut(&window_id).unwrap();
let window = &state.window;
println!("Executing action: {action:?}");
match action {
Action::CloseWindow => {
let _ = self.windows.remove(&window_id);
}
Action::CreateNewWindow => {
#[cfg(any(x11_platform, wayland_platform))]
if let Err(err) = window.request_activation_token() {
println!("Failed to get activation token: {err}");
} else {
return;
}
if let Err(err) = self.create_window(event_loop, None) {
eprintln!("Error creating new window: {err}");
}
}
Action::ToggleResizeIncrements => {
let new_increments = match window.resize_increments() {
Some(_) => None,
None => Some(LogicalSize::new(25.0, 25.0)),
};
println!("Had increments: {}", new_increments.is_none());
window.set_resize_increments(new_increments);
}
Action::ToggleCursorVisibility => {
state.cursor_hidden = !state.cursor_hidden;
window.set_cursor_visible(!state.cursor_hidden);
}
Action::ToggleResizable => {
let resizable = window.is_resizable();
window.set_resizable(!resizable);
}
Action::ToggleDecorations => {
let decorated = window.is_decorated();
window.set_decorations(!decorated);
}
Action::ToggleFullscreen => {
let fullscreen = if window.fullscreen().is_some() {
None
} else {
Some(Fullscreen::Borderless(None))
};
window.set_fullscreen(fullscreen);
}
Action::ToggleMaximize => {
let maximized = window.is_maximized();
window.set_maximized(!maximized);
}
Action::ToggleImeInput => {
state.ime = !state.ime;
window.set_ime_allowed(state.ime);
if let Some(position) = state.ime.then_some(state.cursor_position).flatten() {
window.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
}
}
Action::Minimize => {
window.set_minimized(true);
}
Action::NextCursor => {
// Cursor list to cycle through.
const CURSORS: &[CursorIcon] = &[
CursorIcon::Default,
CursorIcon::Crosshair,
CursorIcon::Pointer,
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,
];
// Pick the next cursor
state.named_idx = (state.named_idx + 1) % CURSORS.len();
println!("Setting cursor to \"{:?}\"", CURSORS[state.named_idx]);
window.set_cursor(Cursor::Icon(CURSORS[state.named_idx]));
}
Action::NextCustomCursor => {
state.custom_idx = (state.custom_idx + 1) % self.custom_cursors.len();
let cursor = Cursor::Custom(self.custom_cursors[state.custom_idx].clone());
window.set_cursor(cursor);
}
Action::CycleCursorGrab => {
state.cursor_grab = match state.cursor_grab {
CursorGrabMode::None => CursorGrabMode::Confined,
CursorGrabMode::Confined => CursorGrabMode::Locked,
CursorGrabMode::Locked => CursorGrabMode::None,
};
println!("Changing cursor grab mode to {:?}", state.cursor_grab);
if let Err(err) = window.set_cursor_grab(state.cursor_grab) {
eprintln!("Error setting cursor grab: {err}");
}
}
Action::DragWindow => {
if let Err(err) = window.drag_window() {
println!("Error starting window drag: {err}");
} else {
println!("Dragging window Window={:?}", window.id());
}
}
Action::DragResizeWindow => {
let position = match state.cursor_position {
Some(position) => position,
None => {
println!("Drag-resize requires cursor to be inside the window");
return;
}
};
// The amount of points around the window.
const BORDER_SIZE: f64 = 20.0;
let win_size = window.inner_size();
let border_size = BORDER_SIZE * window.scale_factor();
let x_direction = if position.x < border_size {
ResizeDirection::West
} else if position.x > (win_size.width as f64 - border_size) {
ResizeDirection::East
} else {
// Use arbitrary direction instead of None for simplicity.
ResizeDirection::SouthEast
};
let y_direction = if position.y < border_size {
ResizeDirection::North
} else if position.y > (win_size.height as f64 - border_size) {
ResizeDirection::South
} else {
// Use arbitrary direction instead of None for simplicity.
ResizeDirection::SouthEast
};
let direction = match (x_direction, y_direction) {
(ResizeDirection::West, ResizeDirection::North) => ResizeDirection::NorthWest,
(ResizeDirection::West, ResizeDirection::South) => ResizeDirection::SouthWest,
(ResizeDirection::West, _) => ResizeDirection::West,
(ResizeDirection::East, ResizeDirection::North) => ResizeDirection::NorthEast,
(ResizeDirection::East, ResizeDirection::South) => ResizeDirection::SouthEast,
(ResizeDirection::East, _) => ResizeDirection::East,
(_, ResizeDirection::South) => ResizeDirection::South,
(_, ResizeDirection::North) => ResizeDirection::North,
_ => return,
};
if let Err(err) = window.drag_resize_window(direction) {
println!("Error starting window drag-resize: {err}");
} else {
println!("Drag-resizing window Window={:?}", window.id());
}
}
Action::ShowWindowMenu => {
if let Some(position) = state.cursor_position {
window.show_window_menu(position);
}
}
Action::PrintHelp => self.print_help(),
#[cfg(macos_platform)]
Action::CycleOptionAsAlt => {
let new = match window.option_as_alt() {
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
OptionAsAlt::Both => OptionAsAlt::None,
};
println!("Setting option as alt {:?}", new);
window.set_option_as_alt(new);
}
#[cfg(macos_platform)]
Action::CreateNewTab => {
let tab_id = window.tabbing_identifier();
if let Err(err) = self.create_window(event_loop, Some(tab_id)) {
eprintln!("Error creating new window: {err}");
}
}
}
}
fn handle_window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
let state = match self.windows.get_mut(&window_id) {
Some(state) => state,
None => return,
};
let window = &state.window;
match event {
// Resize the surface to the new size
WindowEvent::Resized(_size) => {
#[cfg(not(any(android_platform, ios_platform)))]
{
let (width, height) =
match (NonZeroU32::new(_size.width), NonZeroU32::new(_size.height)) {
(Some(width), Some(height)) => (width, height),
_ => return,
};
state
.surface
.resize(width, height)
.expect("failed to resize inner buffer");
}
window.request_redraw();
}
WindowEvent::Focused(focused) => {
if focused {
println!("Window={window_id:?} fosused");
} else {
println!("Window={window_id:?} unfosused");
}
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
println!("Window={window_id:?} changed scale to {scale_factor}");
}
WindowEvent::ThemeChanged(theme) => {
println!("Theme changed to {theme:?}");
state.theme = theme;
window.request_redraw();
}
#[cfg(not(any(android_platform, ios_platform)))]
WindowEvent::RedrawRequested => {
// Draw the window contents.
if state.occluded {
println!("Skipping drawing occluded window={:?}", window_id);
}
const WHITE: u32 = 0xFFFFFFFF;
const DARK_GRAY: u32 = 0xFF181818;
let color = match state.theme {
Theme::Light => WHITE,
Theme::Dark => DARK_GRAY,
};
let mut buffer = state
.surface
.buffer_mut()
.expect("could not retrieve buffer");
buffer.fill(color);
window.pre_present_notify();
buffer.present().expect("failed presenting to window");
}
#[cfg(any(android_platform, ios_platform))]
WindowEvent::RedrawRequested => {
println!("Drawing but without rendering...");
}
// Change window occlusion state.
WindowEvent::Occluded(occluded) => {
state.occluded = occluded;
if !occluded {
window.request_redraw();
}
}
WindowEvent::CloseRequested => {
println!("Closing Window={window_id:?}");
self.windows.remove(&window_id);
}
WindowEvent::ModifiersChanged(modifiers) => {
state.modifiers = modifiers.state();
println!("Modifiers changed to {:?}", state.modifiers);
}
WindowEvent::MouseWheel { delta, .. } => match delta {
MouseScrollDelta::LineDelta(x, y) => {
println!("Mouse wheel Line Delta: ({x},{y})");
}
MouseScrollDelta::PixelDelta(px) => {
println!("Mouse wheel Pixel Delta: ({},{})", px.x, px.y);
}
},
WindowEvent::KeyboardInput {
event:
KeyEvent {
// Dispatch actions only on press.
state: ElementState::Pressed,
logical_key,
..
},
is_synthetic: false,
..
} => {
if let Key::Character(ch) = logical_key.as_ref() {
let mods = state.modifiers;
if let Some(action) = Self::process_key_binding(&ch.to_uppercase(), &mods) {
self.handle_action(event_loop, window_id, action);
}
}
}
WindowEvent::KeyboardInput { .. } => {}
WindowEvent::MouseInput {
button,
state: ElementState::Pressed,
..
} => {
if let Some(action) = Self::process_mouse_binding(button, &state.modifiers) {
self.handle_action(event_loop, window_id, action);
}
}
WindowEvent::MouseInput { .. } => {}
WindowEvent::CursorLeft { .. } => {
println!("Cursor left Window={window_id:?}");
state.cursor_position = None;
}
WindowEvent::CursorMoved { position, .. } => {
println!("Moved cursor to {position:?}");
state.cursor_position = Some(position);
if state.ime {
window.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
}
}
WindowEvent::ActivationTokenDone { token: _token, .. } => {
#[cfg(any(x11_platform, wayland_platform))]
{
startup_notify::set_activation_token_env(_token);
if let Err(err) = self.create_window(event_loop, None) {
eprintln!("Error creating new window: {err}");
}
}
}
WindowEvent::Ime(event) => match event {
Ime::Enabled => println!("IME enabled for Window={window_id:?}"),
Ime::Preedit(text, caret_pos) => {
println!("Preedit: {}, with caret at {:?}", text, caret_pos);
}
Ime::Commit(text) => {
println!("Commited: {}", text);
}
Ime::Disabled => println!("IME disabled for Window={window_id:?}"),
},
WindowEvent::PinchGesture { delta, .. } => {
state.zoom += delta;
let zoom = state.zoom;
if delta > 0.0 {
println!("Zoomed in {delta:.5} (now: {zoom:.5})");
} else {
println!("Zoomed out {delta:.5} (now: {zoom:.5})");
}
}
WindowEvent::RotationGesture { delta, .. } => {
state.rotated += delta;
let rotated = state.rotated;
if delta > 0.0 {
println!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})");
} else {
println!("Rotated clockwise {delta:.5} (now: {rotated:.5})");
}
}
WindowEvent::DoubleTapGesture { .. } => {
println!("Smart zoom");
}
WindowEvent::TouchpadPressure { .. }
| WindowEvent::HoveredFileCancelled
| WindowEvent::CursorEntered { .. }
| WindowEvent::AxisMotion { .. }
| WindowEvent::DroppedFile(_)
| WindowEvent::HoveredFile(_)
| WindowEvent::Destroyed
| WindowEvent::Touch(_)
| WindowEvent::Moved(_) => (),
}
}
fn handle_device_event(&mut self, _: &ActiveEventLoop, _: DeviceId, event: DeviceEvent) {
println!("Device event: {event:?}");
}
/// Process the key binding.
fn process_key_binding(key: &str, mods: &ModifiersState) -> Option<Action> {
KEY_BINDINGS.iter().find_map(|binding| {
binding
.is_triggered_by(&key, mods)
.then_some(binding.action)
})
}
/// Process mouse binding.
fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option<Action> {
MOUSE_BINDINGS.iter().find_map(|binding| {
binding
.is_triggered_by(&button, mods)
.then_some(binding.action)
})
}
fn print_help(&self) {
fn modifiers_to_string(mods: ModifiersState) -> String {
let mut mods_line = String::new();
// Always add + since it's printed as a part of the bindings.
for (modifier, desc) in [
(ModifiersState::SUPER, "Super+"),
(ModifiersState::ALT, "Alt+"),
(ModifiersState::CONTROL, "Ctrl+"),
(ModifiersState::SHIFT, "Shift+"),
] {
if !mods.contains(modifier) {
continue;
}
mods_line.push_str(desc);
}
mods_line
}
println!("Keyboard bindings:");
for binding in KEY_BINDINGS {
println!(
"{}{:<10} - {:?} ({})",
modifiers_to_string(binding.mods),
binding.trigger,
binding.action,
binding.action.help(),
);
}
println!("Mouse bindings:");
for binding in MOUSE_BINDINGS {
let button_name = match binding.trigger {
MouseButton::Left => "LMB",
MouseButton::Right => "RMB",
MouseButton::Middle => "MMB",
MouseButton::Back => "Back",
MouseButton::Forward => "Forward",
MouseButton::Other(_) => "",
};
println!(
"{}{:<10} - {:?} ({})",
modifiers_to_string(binding.mods),
button_name,
binding.action,
binding.action.help(),
);
}
}
}
/// Extra state on a window used in this example.
struct WindowState {
/// The actual Winit window.
window: Window,
/// IME input.
ime: bool,
/// Render surface.
///
/// NOTE: This surface must be dropped before the `Window`.
#[cfg(not(any(android_platform, ios_platform)))]
surface: Surface,
/// The window theme we're drawing with.
theme: Theme,
/// Cursor position over the window.
cursor_position: Option<PhysicalPosition<f64>>,
/// Window modifiers state.
modifiers: ModifiersState,
/// Occlusion state of the window.
occluded: bool,
/// Current cursor grab mode.
cursor_grab: CursorGrabMode,
/// The amount of zoom into window.
zoom: f64,
/// The amount of rotation of the window.
rotated: f32,
// Cursor states.
named_idx: usize,
custom_idx: usize,
cursor_hidden: bool,
}
struct Binding<T: Eq> {
trigger: T,
mods: ModifiersState,
action: Action,
}
impl<T: Eq> Binding<T> {
const fn new(trigger: T, mods: ModifiersState, action: Action) -> Self {
Self {
trigger,
mods,
action,
}
}
fn is_triggered_by(&self, trigger: &T, mods: &ModifiersState) -> bool {
&self.trigger == trigger && &self.mods == mods
}
}
/// Helper enum describing the different kinds of actions this example can do.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Action {
CloseWindow,
ToggleCursorVisibility,
CreateNewWindow,
ToggleResizeIncrements,
ToggleImeInput,
ToggleDecorations,
ToggleResizable,
ToggleFullscreen,
ToggleMaximize,
Minimize,
NextCursor,
NextCustomCursor,
CycleCursorGrab,
PrintHelp,
DragWindow,
DragResizeWindow,
ShowWindowMenu,
#[cfg(macos_platform)]
CycleOptionAsAlt,
#[cfg(macos_platform)]
CreateNewTab,
}
impl Action {
fn help(&self) -> &'static str {
match self {
Action::CloseWindow => "Close window",
Action::ToggleCursorVisibility => "Hide cursor",
Action::CreateNewWindow => "Create new window",
Action::ToggleImeInput => "Toggle IME input",
Action::ToggleDecorations => "Toggle decorations",
Action::ToggleResizable => "Toggle window resizable state",
Action::ToggleFullscreen => "Toggle fullscreen",
Action::ToggleMaximize => "Maximize",
Action::Minimize => "Minimize",
Action::ToggleResizeIncrements => "Use resize increments when resizing window",
Action::NextCursor => "Advance the cursor to the next value",
Action::NextCustomCursor => "Advance custom cursor to the next value",
Action::CycleCursorGrab => "Cycle through cursor grab mode",
Action::PrintHelp => "Print help",
Action::DragWindow => "Start window drag",
Action::DragResizeWindow => "Start window drag-resize",
Action::ShowWindowMenu => "Show window menu",
#[cfg(macos_platform)]
Action::CycleOptionAsAlt => "Cycle option as alt mode",
#[cfg(macos_platform)]
Action::CreateNewTab => "Create new tab",
}
}
}
const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow),
Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp),
Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen),
Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations),
Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput),
Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),
Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements),
Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
// M.
Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
Binding::new("M", ModifiersState::ALT, Action::Minimize),
// N.
Binding::new("N", ModifiersState::CONTROL, Action::CreateNewWindow),
// C.
Binding::new("C", ModifiersState::CONTROL, Action::NextCursor),
Binding::new("C", ModifiersState::ALT, Action::NextCustomCursor),
Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility),
#[cfg(macos_platform)]
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
#[cfg(macos_platform)]
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
];
const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[
Binding::new(
MouseButton::Left,
ModifiersState::ALT,
Action::DragResizeWindow,
),
Binding::new(
MouseButton::Left,
ModifiersState::CONTROL,
Action::DragWindow,
),
Binding::new(
MouseButton::Right,
ModifiersState::CONTROL,
Action::ShowWindowMenu,
),
];

View File

@@ -1,62 +0,0 @@
use std::error::Error;
use winit::{
event::{Event, StartCause},
event_loop::{ActiveEventLoop, EventLoop},
};
fn main() -> Result<(), Box<dyn Error>> {
let event_loop = EventLoop::new()?;
Ok(event_loop.run(|event, event_loop| match event {
Event::NewEvents(StartCause::Init) => {
dump_monitors(event_loop);
event_loop.exit()
}
_ => {}
})?)
}
fn dump_monitors(event_loop: &ActiveEventLoop) {
println!("Monitors information");
let primary_monitor = event_loop.primary_monitor();
for monitor in event_loop.available_monitors() {
let intro = if primary_monitor.as_ref() == Some(&monitor) {
"Primary monitor"
} else {
"Monitor"
};
if let Some(name) = monitor.name() {
println!("{intro}: {name}");
} else {
println!("{intro}: [no name]");
}
let size = monitor.size();
print!(" Current mode: {}x{}", size.width, size.height);
if let Some(m_hz) = monitor.refresh_rate_millihertz() {
println!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000);
} else {
println!();
}
let position = monitor.position();
println!(" Position: {}, {}", position.x, position.y);
println!(" Scale factor: {}", monitor.scale_factor());
println!(" Available modes (width x height x bit-depth):");
for mode in monitor.video_modes() {
let size = mode.size();
let m_hz = mode.refresh_rate_millihertz();
println!(
" {:04}x{:04}x{:02} @ {:>3}.{} Hz",
size.width,
size.height,
mode.bit_depth(),
m_hz / 1000,
m_hz % 1000
);
}
}
}

View File

@@ -1,62 +0,0 @@
//! A demonstration of embedding a winit window in an existing X11 application.
use std::error::Error;
#[cfg(x11_platform)]
fn main() -> Result<(), Box<dyn Error>> {
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::x11::WindowAttributesExtX11,
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
// First argument should be a 32-bit X11 window ID.
let parent_window_id = std::env::args()
.nth(1)
.ok_or("Expected a 32-bit X11 window ID as the first argument.")?
.parse::<u32>()?;
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new()?;
let mut window = None;
event_loop.run(move |event, event_loop| match event {
Event::Resumed => {
let window_attributes = Window::default_attributes()
.with_title("An embedded window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(parent_window_id);
window = Some(event_loop.create_window(window_attributes).unwrap());
}
Event::WindowEvent { event, .. } => {
let window = window.as_ref().unwrap();
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
window.pre_present_notify();
fill::fill_window(window);
}
_ => (),
}
}
Event::AboutToWait => {
window.as_ref().unwrap().request_redraw();
}
_ => (),
})?;
Ok(())
}
#[cfg(not(x11_platform))]
fn main() -> Result<(), Box<dyn Error>> {
println!("This example is only supported on X11 platforms.");
Ok(())
}

View File

@@ -1,10 +1,7 @@
[package] [package]
name = "run-wasm" name = "run-wasm"
version = "0.1.0" version = "0.1.0"
rust-version.workspace = true edition = "2021"
repository.workspace = true
license.workspace = true
edition.workspace = true
publish = false publish = false
[dependencies] [dependencies]

View File

@@ -1,170 +0,0 @@
//! # Android
//!
//! The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/) crate.
//!
//! 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
//! various life-cycle events and synchronizing with the main JVM thread.
//!
//! Winit uses the [android-activity](https://docs.rs/android-activity/) as a
//! glue crate (prior to `0.28` it used
//! [ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue)).
//!
//! 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
//! application's main entry point. If Cargo resolves multiple versions, they will
//! clash.
//!
//! `winit` glue compatibility table:
//!
//! | winit | ndk-glue |
//! | :---: | :--------------------------: |
//! | 0.29 | `android-activity = "0.5"` |
//! | 0.28 | `android-activity = "0.4"` |
//! | 0.27 | `ndk-glue = "0.7"` |
//! | 0.26 | `ndk-glue = "0.5"` |
//! | 0.25 | `ndk-glue = "0.3"` |
//! | 0.24 | `ndk-glue = "0.2"` |
//!
//! The recommended way to avoid a conflict with the glue version is to avoid explicitly
//! depending on the `android-activity` crate, and instead consume the API that
//! 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:
//!
//! ```toml
//! [lib]
//! name = "main"
//! crate-type = ["cdylib"]
//! ```
//!
//! All Android applications are based on an `Activity` subclass, and the
//! `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:
//!
//! | 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.|
//! | [`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`]: https://developer.android.com/games/agdk/game-activity
//! [`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
//! [`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
//! [agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games
//! [agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
//! [Gradle]: https://developer.android.com/studio/build
//!
//! For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
//!
//! ## Converting from `ndk-glue` to `android-activity`
//!
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.11", features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
//! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
use crate::{
event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder},
window::{Window, WindowAttributes},
};
use self::activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {}
impl<T> EventLoopExtAndroid for EventLoop<T> {}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid {}
/// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid {
fn content_rect(&self) -> Rect;
fn config(&self) -> ConfigurationRef;
}
impl WindowExtAndroid for Window {
fn content_rect(&self) -> Rect {
self.window.content_rect()
}
fn config(&self) -> ConfigurationRef {
self.window.config()
}
}
impl ActiveEventLoopExtAndroid for ActiveEventLoop {}
/// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowAttributesExtAndroid {}
impl WindowAttributesExtAndroid for WindowAttributes {}
pub trait EventLoopBuilderExtAndroid {
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
///
/// This must be called on Android since the `AndroidApp` is not global state.
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> {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
}
fn handle_volume_keys(&mut self) -> &mut Self {
self.platform_specific.ignore_volume_keys = false;
self
}
}
/// Re-export of the `android_activity` API
///
/// Winit re-exports the `android_activity` API for convenience so that most
/// applications can rely on the Winit crate to resolve the required version of
/// `android_activity` and avoid any chance of a conflict between Winit and the
/// application crate.
///
/// Unlike most libraries there can only be a single implementation
/// of the `android_activity` glue crate linked with an application because
/// it is responsible for the application's `android_main()` entry point.
///
/// Since Winit depends on a specific version of `android_activity` the simplest
/// way to avoid creating a conflict is for applications to avoid explicitly
/// depending on the `android_activity` crate, and instead consume the API that
/// is re-exported by Winit.
///
/// For compatibility applications should then import the `AndroidApp` type for
/// their `android_main(app: AndroidApp)` function like:
/// ```rust
/// #[cfg(target_os="android")]
/// use winit::platform::android::activity::AndroidApp;
/// ```
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::*;
#[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,6 +0,0 @@
//! # Orbital / Redox OS
//!
//! Redox OS has some functionality not yet present that will be implemented
//! when its orbital display server provides it.
// There are no Orbital specific traits yet.

View File

@@ -1,103 +0,0 @@
use icrate::Foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use super::app_state::{self, EventWrapper};
use super::uikit::{UIApplication, UIWindow};
use super::window::WinitUIWindow;
use crate::{
event::{Event, WindowEvent},
window::WindowId as RootWindowId,
};
declare_class!(
pub struct AppDelegate;
unsafe impl ClassType for AppDelegate {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitApplicationDelegate";
}
impl DeclaredClass for AppDelegate {}
// UIApplicationDelegate protocol
unsafe impl AppDelegate {
#[method(application:didFinishLaunchingWithOptions:)]
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
true
}
#[method(applicationDidBecomeActive:)]
fn did_become_active(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
}
#[method(applicationWillResignActive:)]
fn will_resign_active(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
}
#[method(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, false);
}
#[method(applicationDidEnterBackground:)]
fn did_enter_background(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, true);
}
#[method(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,
}));
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
app_state::terminated(mtm);
}
#[method(applicationDidReceiveMemoryWarning:)]
fn did_receive_memory_warning(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning))
}
}
);
impl AppDelegate {
fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) {
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::Occluded(occluded),
}));
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
}
}

View File

@@ -1,55 +0,0 @@
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)]
mod app_delegate;
mod app_state;
mod event_loop;
mod monitor;
mod uikit;
mod view;
mod view_controller;
mod window;
use std::fmt;
use crate::event::DeviceId as RootDeviceId;
pub(crate) use self::{
event_loop::{
ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle,
PlatformSpecificEventLoopAttributes,
},
monitor::{MonitorHandle, VideoModeHandle},
window::{PlatformSpecificWindowAttributes, Window, WindowId},
};
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorSource;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
/// There is no way to detect which device that performed a certain event in
/// UIKit (i.e. you can't differentiate between different external keyboards,
/// or whether it was the main touchscreen, assistive technologies, or some
/// other pointer device that caused a touch event).
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub const unsafe fn dummy() -> Self {
DeviceId
}
}
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {}
#[derive(Debug)]
pub enum OsError {}
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "os error")
}
}

View File

@@ -1,41 +0,0 @@
use icrate::Foundation::{MainThreadMarker, NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIDevice;
unsafe impl ClassType for UIDevice {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIDevice {
pub fn current(_mtm: MainThreadMarker) -> Id<Self> {
unsafe { msg_send_id![Self::class(), currentDevice] }
}
#[method(userInterfaceIdiom)]
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom;
}
);
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIUserInterfaceIdiom(NSInteger);
unsafe impl Encode for UIUserInterfaceIdiom {
const ENCODING: Encoding = NSInteger::ENCODING;
}
impl UIUserInterfaceIdiom {
pub const Unspecified: UIUserInterfaceIdiom = UIUserInterfaceIdiom(-1);
pub const Phone: UIUserInterfaceIdiom = UIUserInterfaceIdiom(0);
pub const Pad: UIUserInterfaceIdiom = UIUserInterfaceIdiom(1);
pub const TV: UIUserInterfaceIdiom = UIUserInterfaceIdiom(2);
pub const CarPlay: UIUserInterfaceIdiom = UIUserInterfaceIdiom(3);
}

View File

@@ -1,14 +0,0 @@
use icrate::Foundation::NSUInteger;
use objc2::encode::{Encode, Encoding};
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(pub NSUInteger);
impl UIRectEdge {
pub const NONE: Self = Self(0);
}
unsafe impl Encode for UIRectEdge {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View File

@@ -1,192 +0,0 @@
use std::cell::Cell;
use icrate::Foundation::{MainThreadMarker, NSObject};
use objc2::rc::Id;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use super::app_state::{self};
use super::uikit::{
UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle,
UIUserInterfaceIdiom, UIView, UIViewController,
};
use crate::platform::ios::{ScreenEdge, StatusBarStyle};
use crate::{platform::ios::ValidOrientations, window::WindowAttributes};
pub struct ViewControllerState {
prefers_status_bar_hidden: Cell<bool>,
preferred_status_bar_style: Cell<UIStatusBarStyle>,
prefers_home_indicator_auto_hidden: Cell<bool>,
supported_orientations: Cell<UIInterfaceOrientationMask>,
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
}
declare_class!(
pub(crate) struct WinitViewController;
unsafe impl ClassType for WinitViewController {
#[inherits(UIResponder, NSObject)]
type Super = UIViewController;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitUIViewController";
}
impl DeclaredClass for WinitViewController {
type Ivars = ViewControllerState;
}
unsafe impl WinitViewController {
#[method(shouldAutorotate)]
fn should_autorotate(&self) -> bool {
true
}
#[method(prefersStatusBarHidden)]
fn prefers_status_bar_hidden(&self) -> bool {
self.ivars().prefers_status_bar_hidden.get()
}
#[method(preferredStatusBarStyle)]
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
self.ivars().preferred_status_bar_style.get()
}
#[method(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool {
self.ivars().prefers_home_indicator_auto_hidden.get()
}
#[method(supportedInterfaceOrientations)]
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
self.ivars().supported_orientations.get()
}
#[method(preferredScreenEdgesDeferringSystemGestures)]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
self.ivars()
.preferred_screen_edges_deferring_system_gestures
.get()
}
}
);
impl WinitViewController {
pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) {
self.ivars().prefers_status_bar_hidden.set(val);
self.setNeedsStatusBarAppearanceUpdate();
}
pub(crate) fn set_preferred_status_bar_style(&self, val: StatusBarStyle) {
let val = match val {
StatusBarStyle::Default => UIStatusBarStyle::Default,
StatusBarStyle::LightContent => UIStatusBarStyle::LightContent,
StatusBarStyle::DarkContent => UIStatusBarStyle::DarkContent,
};
self.ivars().preferred_status_bar_style.set(val);
self.setNeedsStatusBarAppearanceUpdate();
}
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.ivars().prefers_home_indicator_auto_hidden.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else {
os_capabilities.home_indicator_hidden_err_msg("ignoring")
}
}
pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: ScreenEdge) {
let val = {
assert_eq!(
val.bits() & !ScreenEdge::ALL.bits(),
0,
"invalid `ScreenEdge`"
);
UIRectEdge(val.bits().into())
};
self.ivars()
.preferred_screen_edges_deferring_system_gestures
.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else {
os_capabilities.defer_system_gestures_err_msg("ignoring")
}
}
pub(crate) fn set_supported_interface_orientations(
&self,
mtm: MainThreadMarker,
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.ivars().supported_orientations.set(mask);
UIViewController::attemptRotationToDeviceOrientation();
}
pub(crate) fn new(
mtm: MainThreadMarker,
window_attributes: &WindowAttributes,
view: &UIView,
) -> Id<Self> {
// These are set properly below, we just to set them to something in the meantime.
let this = Self::alloc().set_ivars(ViewControllerState {
prefers_status_bar_hidden: Cell::new(false),
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
prefers_home_indicator_auto_hidden: Cell::new(false),
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::NONE),
});
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
this.set_prefers_status_bar_hidden(
window_attributes
.platform_specific
.prefers_status_bar_hidden,
);
this.set_preferred_status_bar_style(
window_attributes
.platform_specific
.preferred_status_bar_style,
);
this.set_supported_interface_orientations(
mtm,
window_attributes.platform_specific.valid_orientations,
);
this.set_prefers_home_indicator_auto_hidden(
window_attributes
.platform_specific
.prefers_home_indicator_hidden,
);
this.set_preferred_screen_edges_deferring_system_gestures(
window_attributes
.platform_specific
.preferred_screen_edges_deferring_system_gestures,
);
this.setView(Some(view));
this
}
}

View File

@@ -1 +0,0 @@
pub mod xkb;

View File

@@ -1,124 +0,0 @@
//! XKB compose handling.
use std::env;
use std::ffi::CString;
use std::ops::Deref;
use std::os::unix::ffi::OsStringExt;
use std::ptr::NonNull;
use super::XkbContext;
use super::XKBCH;
use smol_str::SmolStr;
use xkbcommon_dl::{
xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags,
xkb_compose_status, xkb_compose_table, xkb_keysym_t,
};
#[derive(Debug)]
pub struct XkbComposeTable {
table: NonNull<xkb_compose_table>,
}
impl XkbComposeTable {
pub fn new(context: &XkbContext) -> Option<Self> {
let locale = env::var_os("LC_ALL")
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.or_else(|| env::var_os("LC_CTYPE"))
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.or_else(|| env::var_os("LANG"))
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.unwrap_or_else(|| "C".into());
let locale = CString::new(locale.into_vec()).unwrap();
let table = unsafe {
(XKBCH.xkb_compose_table_new_from_locale)(
context.as_ptr(),
locale.as_ptr(),
xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
)
};
let table = NonNull::new(table)?;
Some(Self { table })
}
/// Create new state with the given compose table.
pub fn new_state(&self) -> Option<XkbComposeState> {
let state = unsafe {
(XKBCH.xkb_compose_state_new)(
self.table.as_ptr(),
xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
)
};
let state = NonNull::new(state)?;
Some(XkbComposeState { state })
}
}
impl Deref for XkbComposeTable {
type Target = NonNull<xkb_compose_table>;
fn deref(&self) -> &Self::Target {
&self.table
}
}
impl Drop for XkbComposeTable {
fn drop(&mut self) {
unsafe {
(XKBCH.xkb_compose_table_unref)(self.table.as_ptr());
}
}
}
#[derive(Debug)]
pub struct XkbComposeState {
state: NonNull<xkb_compose_state>,
}
impl XkbComposeState {
pub fn get_string(&mut self, scratch_buffer: &mut Vec<u8>) -> Option<SmolStr> {
super::make_string_with(scratch_buffer, |ptr, len| unsafe {
(XKBCH.xkb_compose_state_get_utf8)(self.state.as_ptr(), ptr, len)
})
}
#[inline]
pub fn feed(&mut self, keysym: xkb_keysym_t) -> ComposeStatus {
let feed_result = unsafe { (XKBCH.xkb_compose_state_feed)(self.state.as_ptr(), keysym) };
match feed_result {
xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED => ComposeStatus::Ignored,
xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED => {
ComposeStatus::Accepted(self.status())
}
}
}
#[inline]
pub fn reset(&mut self) {
unsafe {
(XKBCH.xkb_compose_state_reset)(self.state.as_ptr());
}
}
#[inline]
pub fn status(&mut self) -> xkb_compose_status {
unsafe { (XKBCH.xkb_compose_state_get_status)(self.state.as_ptr()) }
}
}
impl Drop for XkbComposeState {
fn drop(&mut self) {
unsafe {
(XKBCH.xkb_compose_state_unref)(self.state.as_ptr());
};
}
}
#[derive(Copy, Clone, Debug)]
pub enum ComposeStatus {
Accepted(xkb_compose_status),
Ignored,
None,
}

View File

@@ -1,460 +0,0 @@
use std::ops::Deref;
use std::os::raw::c_char;
use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, Ordering};
use crate::utils::Lazy;
use log::warn;
use smol_str::SmolStr;
#[cfg(wayland_platform)]
use std::os::unix::io::OwnedFd;
use xkbcommon_dl::{
self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle,
xkbcommon_handle, XkbCommon, XkbCommonCompose,
};
#[cfg(x11_platform)]
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
use crate::event::ElementState;
use crate::event::KeyEvent;
use crate::keyboard::{Key, KeyLocation};
use crate::platform_impl::KeyEventExtra;
mod compose;
mod keymap;
mod state;
use compose::{ComposeStatus, XkbComposeState, XkbComposeTable};
use keymap::XkbKeymap;
#[cfg(x11_platform)]
pub use keymap::raw_keycode_to_physicalkey;
pub use keymap::{physicalkey_to_scancode, scancode_to_physicalkey};
pub use state::XkbState;
// TODO: Wire this up without using a static `AtomicBool`.
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle);
static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle);
#[cfg(feature = "x11")]
static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle);
#[inline(always)]
pub fn reset_dead_keys() {
RESET_DEAD_KEYS.store(true, Ordering::SeqCst);
}
#[derive(Debug)]
pub enum Error {
/// libxkbcommon is not available
XKBNotFound,
}
#[derive(Debug)]
pub struct Context {
// NOTE: field order matters.
#[cfg(x11_platform)]
pub core_keyboard_id: i32,
state: Option<XkbState>,
keymap: Option<XkbKeymap>,
compose_state1: Option<XkbComposeState>,
compose_state2: Option<XkbComposeState>,
_compose_table: Option<XkbComposeTable>,
context: XkbContext,
scratch_buffer: Vec<u8>,
}
impl Context {
pub fn new() -> Result<Self, Error> {
if xkb::xkbcommon_option().is_none() {
return Err(Error::XKBNotFound);
}
let context = XkbContext::new()?;
let mut compose_table = XkbComposeTable::new(&context);
let mut compose_state1 = compose_table.as_ref().and_then(|table| table.new_state());
let mut compose_state2 = compose_table.as_ref().and_then(|table| table.new_state());
// Disable compose if anything compose related failed to initialize.
if compose_table.is_none() || compose_state1.is_none() || compose_state2.is_none() {
compose_state2 = None;
compose_state1 = None;
compose_table = None;
}
Ok(Self {
state: None,
keymap: None,
compose_state1,
compose_state2,
#[cfg(x11_platform)]
core_keyboard_id: 0,
_compose_table: compose_table,
context,
scratch_buffer: Vec::with_capacity(8),
})
}
#[cfg(feature = "x11")]
pub fn from_x11_xkb(xcb: *mut xcb_connection_t) -> Result<Self, Error> {
let result = unsafe {
(XKBXH.xkb_x11_setup_xkb_extension)(
xcb,
1,
2,
xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
};
if result != 1 {
return Err(Error::XKBNotFound);
}
let mut this = Self::new()?;
this.core_keyboard_id = unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(xcb) };
this.set_keymap_from_x11(xcb);
Ok(this)
}
pub fn state_mut(&mut self) -> Option<&mut XkbState> {
self.state.as_mut()
}
pub fn keymap_mut(&mut self) -> Option<&mut XkbKeymap> {
self.keymap.as_mut()
}
#[cfg(wayland_platform)]
pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) {
let keymap = XkbKeymap::from_fd(&self.context, fd, size);
let state = keymap.as_ref().and_then(XkbState::new_wayland);
if keymap.is_none() || state.is_none() {
warn!("failed to update xkb keymap");
}
self.state = state;
self.keymap = keymap;
}
#[cfg(x11_platform)]
pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) {
let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id);
let state = keymap
.as_ref()
.and_then(|keymap| XkbState::new_x11(xcb, keymap));
if keymap.is_none() || state.is_none() {
warn!("failed to update xkb keymap");
}
self.state = state;
self.keymap = keymap;
}
/// Key builder context with the user provided xkb state.
pub fn key_context(&mut self) -> Option<KeyContext<'_>> {
let state = self.state.as_mut()?;
let keymap = self.keymap.as_mut()?;
let compose_state1 = self.compose_state1.as_mut();
let compose_state2 = self.compose_state2.as_mut();
let scratch_buffer = &mut self.scratch_buffer;
Some(KeyContext {
state,
keymap,
compose_state1,
compose_state2,
scratch_buffer,
})
}
/// Key builder context with the user provided xkb state.
///
/// Should be used when the original context must not be altered.
#[cfg(x11_platform)]
pub fn key_context_with_state<'a>(
&'a mut self,
state: &'a mut XkbState,
) -> Option<KeyContext<'a>> {
let keymap = self.keymap.as_mut()?;
let compose_state1 = self.compose_state1.as_mut();
let compose_state2 = self.compose_state2.as_mut();
let scratch_buffer = &mut self.scratch_buffer;
Some(KeyContext {
state,
keymap,
compose_state1,
compose_state2,
scratch_buffer,
})
}
}
pub struct KeyContext<'a> {
pub state: &'a mut XkbState,
pub keymap: &'a mut XkbKeymap,
compose_state1: Option<&'a mut XkbComposeState>,
compose_state2: Option<&'a mut XkbComposeState>,
scratch_buffer: &'a mut Vec<u8>,
}
impl<'a> KeyContext<'a> {
pub fn process_key_event(
&mut self,
keycode: u32,
state: ElementState,
repeat: bool,
) -> KeyEvent {
let mut event =
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
let physical_key = keymap::raw_keycode_to_physicalkey(keycode);
let (logical_key, location) = event.key();
let text = event.text();
let (key_without_modifiers, _) = event.key_without_modifiers();
let text_with_all_modifiers = event.text_with_all_modifiers();
let platform_specific = KeyEventExtra {
text_with_all_modifiers,
key_without_modifiers,
};
KeyEvent {
physical_key,
logical_key,
text,
location,
state,
repeat,
platform_specific,
}
}
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
self.scratch_buffer.clear();
self.scratch_buffer.reserve(8);
loop {
let bytes_written = unsafe {
(XKBH.xkb_keysym_to_utf8)(
keysym,
self.scratch_buffer.as_mut_ptr().cast(),
self.scratch_buffer.capacity(),
)
};
if bytes_written == 0 {
return None;
} else if bytes_written == -1 {
self.scratch_buffer.reserve(8);
} else {
unsafe {
self.scratch_buffer
.set_len(bytes_written.try_into().unwrap())
};
break;
}
}
// Remove the null-terminator
self.scratch_buffer.pop();
byte_slice_to_smol_str(self.scratch_buffer)
}
}
struct KeyEventResults<'a, 'b> {
context: &'a mut KeyContext<'b>,
keycode: u32,
keysym: u32,
compose: ComposeStatus,
}
impl<'a, 'b> KeyEventResults<'a, 'b> {
fn new(context: &'a mut KeyContext<'b>, keycode: u32, compose: bool) -> Self {
let keysym = context.state.get_one_sym_raw(keycode);
let compose = if let Some(state) = context.compose_state1.as_mut().filter(|_| compose) {
if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) {
state.reset();
context.compose_state2.as_mut().unwrap().reset();
}
state.feed(keysym)
} else {
ComposeStatus::None
};
KeyEventResults {
context,
keycode,
keysym,
compose,
}
}
pub fn key(&mut self) -> (Key, KeyLocation) {
let (key, location) = match self.keysym_to_key(self.keysym) {
Ok(known) => return known,
Err(undefined) => undefined,
};
if let ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSING) = self.compose {
let compose_state = self.context.compose_state2.as_mut().unwrap();
// When pressing a dead key twice, the non-combining variant of that character will
// be produced. Since this function only concerns itself with a single keypress, we
// simulate this double press here by feeding the keysym to the compose state
// twice.
compose_state.feed(self.keysym);
if matches!(compose_state.feed(self.keysym), ComposeStatus::Accepted(_)) {
// Extracting only a single `char` here *should* be fine, assuming that no
// dead key's non-combining variant ever occupies more than one `char`.
let text = compose_state.get_string(self.context.scratch_buffer);
let key = Key::Dead(text.and_then(|s| s.chars().next()));
(key, location)
} else {
(key, location)
}
} else {
let key = self
.composed_text()
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
.map(Key::Character)
.unwrap_or(key);
(key, location)
}
}
pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) {
// This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it.
let layout = self.context.state.layout(self.keycode);
let keysym = self
.context
.keymap
.first_keysym_by_level(layout, self.keycode);
match self.keysym_to_key(keysym) {
Ok((key, location)) => (key, location),
Err((key, location)) => {
let key = self
.context
.keysym_to_utf8_raw(keysym)
.map(Key::Character)
.unwrap_or(key);
(key, location)
}
}
}
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
let location = keymap::keysym_location(keysym);
let key = keymap::keysym_to_key(keysym);
if matches!(key, Key::Unidentified(_)) {
Err((key, location))
} else {
Ok((key, location))
}
}
pub fn text(&mut self) -> Option<SmolStr> {
self.composed_text()
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
}
// The current behaviour makes it so composing a character overrides attempts to input a
// control character with the `Ctrl` key. We can potentially add a configuration option
// if someone specifically wants the oppsite behaviour.
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
match self.composed_text() {
Ok(text) => text,
Err(_) => self
.context
.state
.get_utf8_raw(self.keycode, self.context.scratch_buffer),
}
}
fn composed_text(&mut self) -> Result<Option<SmolStr>, ()> {
match self.compose {
ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSED) => {
let state = self.context.compose_state1.as_mut().unwrap();
Ok(state.get_string(self.context.scratch_buffer))
}
_ => Err(()),
}
}
}
#[derive(Debug)]
pub struct XkbContext {
context: NonNull<xkb_context>,
}
impl XkbContext {
pub fn new() -> Result<Self, Error> {
let context = unsafe { (XKBH.xkb_context_new)(xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
let context = match NonNull::new(context) {
Some(context) => context,
None => return Err(Error::XKBNotFound),
};
Ok(Self { context })
}
}
impl Drop for XkbContext {
fn drop(&mut self) {
unsafe {
(XKBH.xkb_context_unref)(self.context.as_ptr());
}
}
}
impl Deref for XkbContext {
type Target = NonNull<xkb_context>;
fn deref(&self) -> &Self::Target {
&self.context
}
}
/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and
/// `xkb_state_key_get_utf8`.
fn make_string_with<F>(scratch_buffer: &mut Vec<u8>, mut f: F) -> Option<SmolStr>
where
F: FnMut(*mut c_char, usize) -> i32,
{
let size = f(ptr::null_mut(), 0);
if size == 0 {
return None;
}
let size = usize::try_from(size).unwrap();
scratch_buffer.clear();
// The allocated buffer must include space for the null-terminator.
scratch_buffer.reserve(size + 1);
unsafe {
let written = f(
scratch_buffer.as_mut_ptr().cast(),
scratch_buffer.capacity(),
);
if usize::try_from(written).unwrap() != size {
// This will likely never happen.
return None;
}
scratch_buffer.set_len(size);
};
byte_slice_to_smol_str(scratch_buffer)
}
// NOTE: This is track_caller so we can have more informative line numbers when logging
#[track_caller]
fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
std::str::from_utf8(bytes)
.map(SmolStr::new)
.map_err(|e| {
log::warn!(
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
bytes
)
})
.ok()
}

View File

@@ -1,189 +0,0 @@
//! XKB state.
use std::os::raw::c_char;
use std::ptr::NonNull;
use smol_str::SmolStr;
#[cfg(x11_platform)]
use x11_dl::xlib_xcb::xcb_connection_t;
use xkbcommon_dl::{
self as xkb, xkb_keycode_t, xkb_keysym_t, xkb_layout_index_t, xkb_state, xkb_state_component,
};
use crate::platform_impl::common::xkb::keymap::XkbKeymap;
#[cfg(x11_platform)]
use crate::platform_impl::common::xkb::XKBXH;
use crate::platform_impl::common::xkb::{make_string_with, XKBH};
#[derive(Debug)]
pub struct XkbState {
state: NonNull<xkb_state>,
modifiers: ModifiersState,
}
impl XkbState {
#[cfg(wayland_platform)]
pub fn new_wayland(keymap: &XkbKeymap) -> Option<Self> {
let state = NonNull::new(unsafe { (XKBH.xkb_state_new)(keymap.as_ptr()) })?;
Some(Self::new_inner(state))
}
#[cfg(x11_platform)]
pub fn new_x11(xcb: *mut xcb_connection_t, keymap: &XkbKeymap) -> Option<Self> {
let state = unsafe {
(XKBXH.xkb_x11_state_new_from_device)(keymap.as_ptr(), xcb, keymap._core_keyboard_id)
};
let state = NonNull::new(state)?;
Some(Self::new_inner(state))
}
fn new_inner(state: NonNull<xkb_state>) -> Self {
let modifiers = ModifiersState::default();
let mut this = Self { state, modifiers };
this.reload_modifiers();
this
}
pub fn get_one_sym_raw(&mut self, keycode: xkb_keycode_t) -> xkb_keysym_t {
unsafe { (XKBH.xkb_state_key_get_one_sym)(self.state.as_ptr(), keycode) }
}
pub fn layout(&mut self, key: xkb_keycode_t) -> xkb_layout_index_t {
unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) }
}
#[cfg(x11_platform)]
pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
self.state.as_ptr(),
xkb_state_component::XKB_STATE_MODS_DEPRESSED,
)
}
}
#[cfg(x11_platform)]
pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
self.state.as_ptr(),
xkb_state_component::XKB_STATE_MODS_LATCHED,
)
}
}
#[cfg(x11_platform)]
pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
self.state.as_ptr(),
xkb_state_component::XKB_STATE_MODS_LOCKED,
)
}
}
pub fn get_utf8_raw(
&mut self,
keycode: xkb_keycode_t,
scratch_buffer: &mut Vec<u8>,
) -> Option<SmolStr> {
make_string_with(scratch_buffer, |ptr, len| unsafe {
(XKBH.xkb_state_key_get_utf8)(self.state.as_ptr(), keycode, ptr, len)
})
}
pub fn modifiers(&self) -> ModifiersState {
self.modifiers
}
pub fn update_modifiers(
&mut self,
mods_depressed: u32,
mods_latched: u32,
mods_locked: u32,
depressed_group: u32,
latched_group: u32,
locked_group: u32,
) {
let mask = unsafe {
(XKBH.xkb_state_update_mask)(
self.state.as_ptr(),
mods_depressed,
mods_latched,
mods_locked,
depressed_group,
latched_group,
locked_group,
)
};
if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) {
// Effective value of mods have changed, we need to update our state.
self.reload_modifiers();
}
}
/// Reload the modifiers.
fn reload_modifiers(&mut self) {
self.modifiers.ctrl = self.mod_name_is_active(xkb::XKB_MOD_NAME_CTRL);
self.modifiers.alt = self.mod_name_is_active(xkb::XKB_MOD_NAME_ALT);
self.modifiers.shift = self.mod_name_is_active(xkb::XKB_MOD_NAME_SHIFT);
self.modifiers.caps_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_CAPS);
self.modifiers.logo = self.mod_name_is_active(xkb::XKB_MOD_NAME_LOGO);
self.modifiers.num_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_NUM);
}
/// Check if the modifier is active within xkb.
fn mod_name_is_active(&mut self, name: &[u8]) -> bool {
unsafe {
(XKBH.xkb_state_mod_name_is_active)(
self.state.as_ptr(),
name.as_ptr() as *const c_char,
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
) > 0
}
}
}
impl Drop for XkbState {
fn drop(&mut self) {
unsafe {
(XKBH.xkb_state_unref)(self.state.as_ptr());
}
}
}
/// Represents the current state of the keyboard modifiers
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
///
/// For some modifiers, this means that the key is currently pressed, others are toggled
/// (like caps lock).
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ModifiersState {
/// The "control" key
pub ctrl: bool,
/// The "alt" key
pub alt: bool,
/// The "shift" key
pub shift: bool,
/// The "Caps lock" key
pub caps_lock: bool,
/// The "logo" key
///
/// Also known as the "windows" key on most keyboards
pub logo: bool,
/// The "Num lock" key
pub num_lock: bool,
}
impl From<ModifiersState> for crate::keyboard::ModifiersState {
fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState {
let mut to_mods = crate::keyboard::ModifiersState::empty();
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift);
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl);
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt);
to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo);
to_mods
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,56 +0,0 @@
use std::collections::HashSet;
use std::slice;
use x11_dl::xlib::{KeyCode as XKeyCode, XModifierKeymap};
// Offsets within XModifierKeymap to each set of keycodes.
// We are only interested in Shift, Control, Alt, and Logo.
//
// There are 8 sets total. The order of keycode sets is:
// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5
//
// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html
const NUM_MODS: usize = 8;
/// Track which keys are modifiers, so we can properly replay them when they were filtered.
#[derive(Debug, Default)]
pub struct ModifierKeymap {
// Maps keycodes to modifiers
modifers: HashSet<XKeyCode>,
}
impl ModifierKeymap {
pub fn new() -> ModifierKeymap {
ModifierKeymap::default()
}
pub fn is_modifier(&self, keycode: XKeyCode) -> bool {
self.modifers.contains(&keycode)
}
pub fn reload_from_x_connection(&mut self, xconn: &super::XConnection) {
unsafe {
let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display);
if keymap.is_null() {
return;
}
self.reset_from_x_keymap(&*keymap);
(xconn.xlib.XFreeModifiermap)(keymap);
}
}
fn reset_from_x_keymap(&mut self, keymap: &XModifierKeymap) {
let keys_per_mod = keymap.max_keypermod as usize;
let keys = unsafe {
slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS)
};
self.modifers.clear();
for key in keys {
self.modifers.insert(*key);
}
}
}

View File

@@ -1,136 +0,0 @@
use std::cell::RefCell;
use std::fmt;
use std::mem;
use super::app_delegate::HandlePendingUserEvents;
use crate::event::Event;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
struct EventHandlerData {
#[allow(clippy::type_complexity)]
handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'static>,
}
impl fmt::Debug for EventHandlerData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventHandlerData").finish_non_exhaustive()
}
}
#[derive(Debug, Default)]
pub(crate) struct EventHandler {
/// This can be in the following states:
/// - Not registered by the event loop (None).
/// - Present (Some(handler)).
/// - Currently executing the handler / in use (RefCell borrowed).
inner: RefCell<Option<EventHandlerData>>,
}
impl EventHandler {
/// Set the event loop handler for the duration of the given closure.
///
/// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates
/// to store the handler in a thread local, such that it can be accessed
/// from within the closure.
pub(crate) fn set<'handler, R>(
&self,
handler: impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'handler,
closure: impl FnOnce() -> R,
) -> R {
// SAFETY: We extend the lifetime of the handler here so that we can
// store it in `EventHandler`'s `RefCell`.
//
// This is sound, since we make sure to unset the handler again at the
// end of this function, and as such the lifetime isn't actually
// extended beyond `'handler`.
let handler = unsafe {
mem::transmute::<
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'handler>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'static>,
>(Box::new(handler))
};
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(_)) => {
unreachable!("tried to set handler while another was already set");
}
Ok(data @ None) => {
*data = Some(EventHandlerData { handler });
}
Err(_) => {
unreachable!("tried to set handler that is currently in use");
}
}
struct ClearOnDrop<'a>(&'a EventHandler);
impl Drop for ClearOnDrop<'_> {
fn drop(&mut self) {
match self.0.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => {
*data = None;
}
Ok(None) => {
log::error!("tried to clear handler, but no handler was set");
}
Err(_) => {
// Note: This is not expected to ever happen, this
// module generally controls the `RefCell`, and
// prevents it from ever being borrowed outside of it.
//
// But if it _does_ happen, it is a serious error, and
// we must abort the process, it'd be unsound if we
// weren't able to unset the handler.
eprintln!("tried to clear handler that is currently in use");
std::process::abort();
}
}
}
}
let _clear_on_drop = ClearOnDrop(self);
// Note: The RefCell should not be borrowed while executing the
// closure, that'd defeat the whole point.
closure()
// `_clear_on_drop` will be dropped here, or when unwinding, ensuring
// soundness.
}
pub(crate) fn in_use(&self) -> bool {
self.inner.try_borrow().is_err()
}
pub(crate) fn ready(&self) -> bool {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
}
pub(crate) fn handle_event(
&self,
event: Event<HandlePendingUserEvents>,
event_loop: &RootActiveEventLoop,
) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(EventHandlerData { handler })) => {
// It is important that we keep the reference borrowed here,
// so that `in_use` can properly detect that the handler is
// still in use.
//
// If the handler unwinds, the `RefMut` will ensure that the
// handler is no longer borrowed.
(handler)(event, event_loop);
}
Ok(None) => {
// `NSApplication`, our app delegate and this handler are all
// global state and so it's not impossible that we could get
// an event after the application has exited the `EventLoop`.
log::error!("tried to run event handler, but no handler was set");
}
Err(_) => {
// Prevent re-entrancy.
panic!("tried to handle event while another event is currently being handled");
}
}
}
}

View File

@@ -1,8 +0,0 @@
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(pub i32);
impl DeviceId {
pub const unsafe fn dummy() -> Self {
Self(0)
}
}

View File

@@ -1,522 +0,0 @@
use smol_str::SmolStr;
use crate::keyboard::{Key, KeyCode, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub(crate) struct KeyEventExtra;
impl Key {
pub(crate) fn from_key_attribute_value(kav: &str) -> Self {
Key::Named(match kav {
"Unidentified" => return Key::Unidentified(NativeKey::Web(SmolStr::new(kav))),
"Dead" => return Key::Dead(None),
"Alt" => NamedKey::Alt,
"AltGraph" => NamedKey::AltGraph,
"CapsLock" => NamedKey::CapsLock,
"Control" => NamedKey::Control,
"Fn" => NamedKey::Fn,
"FnLock" => NamedKey::FnLock,
"NumLock" => NamedKey::NumLock,
"ScrollLock" => NamedKey::ScrollLock,
"Shift" => NamedKey::Shift,
"Symbol" => NamedKey::Symbol,
"SymbolLock" => NamedKey::SymbolLock,
"Hyper" => NamedKey::Hyper,
"Meta" => NamedKey::Super,
"Enter" => NamedKey::Enter,
"Tab" => NamedKey::Tab,
" " => NamedKey::Space,
"ArrowDown" => NamedKey::ArrowDown,
"ArrowLeft" => NamedKey::ArrowLeft,
"ArrowRight" => NamedKey::ArrowRight,
"ArrowUp" => NamedKey::ArrowUp,
"End" => NamedKey::End,
"Home" => NamedKey::Home,
"PageDown" => NamedKey::PageDown,
"PageUp" => NamedKey::PageUp,
"Backspace" => NamedKey::Backspace,
"Clear" => NamedKey::Clear,
"Copy" => NamedKey::Copy,
"CrSel" => NamedKey::CrSel,
"Cut" => NamedKey::Cut,
"Delete" => NamedKey::Delete,
"EraseEof" => NamedKey::EraseEof,
"ExSel" => NamedKey::ExSel,
"Insert" => NamedKey::Insert,
"Paste" => NamedKey::Paste,
"Redo" => NamedKey::Redo,
"Undo" => NamedKey::Undo,
"Accept" => NamedKey::Accept,
"Again" => NamedKey::Again,
"Attn" => NamedKey::Attn,
"Cancel" => NamedKey::Cancel,
"ContextMenu" => NamedKey::ContextMenu,
"Escape" => NamedKey::Escape,
"Execute" => NamedKey::Execute,
"Find" => NamedKey::Find,
"Help" => NamedKey::Help,
"Pause" => NamedKey::Pause,
"Play" => NamedKey::Play,
"Props" => NamedKey::Props,
"Select" => NamedKey::Select,
"ZoomIn" => NamedKey::ZoomIn,
"ZoomOut" => NamedKey::ZoomOut,
"BrightnessDown" => NamedKey::BrightnessDown,
"BrightnessUp" => NamedKey::BrightnessUp,
"Eject" => NamedKey::Eject,
"LogOff" => NamedKey::LogOff,
"Power" => NamedKey::Power,
"PowerOff" => NamedKey::PowerOff,
"PrintScreen" => NamedKey::PrintScreen,
"Hibernate" => NamedKey::Hibernate,
"Standby" => NamedKey::Standby,
"WakeUp" => NamedKey::WakeUp,
"AllCandidates" => NamedKey::AllCandidates,
"Alphanumeric" => NamedKey::Alphanumeric,
"CodeInput" => NamedKey::CodeInput,
"Compose" => NamedKey::Compose,
"Convert" => NamedKey::Convert,
"FinalMode" => NamedKey::FinalMode,
"GroupFirst" => NamedKey::GroupFirst,
"GroupLast" => NamedKey::GroupLast,
"GroupNext" => NamedKey::GroupNext,
"GroupPrevious" => NamedKey::GroupPrevious,
"ModeChange" => NamedKey::ModeChange,
"NextCandidate" => NamedKey::NextCandidate,
"NonConvert" => NamedKey::NonConvert,
"PreviousCandidate" => NamedKey::PreviousCandidate,
"Process" => NamedKey::Process,
"SingleCandidate" => NamedKey::SingleCandidate,
"HangulMode" => NamedKey::HangulMode,
"HanjaMode" => NamedKey::HanjaMode,
"JunjaMode" => NamedKey::JunjaMode,
"Eisu" => NamedKey::Eisu,
"Hankaku" => NamedKey::Hankaku,
"Hiragana" => NamedKey::Hiragana,
"HiraganaKatakana" => NamedKey::HiraganaKatakana,
"KanaMode" => NamedKey::KanaMode,
"KanjiMode" => NamedKey::KanjiMode,
"Katakana" => NamedKey::Katakana,
"Romaji" => NamedKey::Romaji,
"Zenkaku" => NamedKey::Zenkaku,
"ZenkakuHankaku" => NamedKey::ZenkakuHankaku,
"Soft1" => NamedKey::Soft1,
"Soft2" => NamedKey::Soft2,
"Soft3" => NamedKey::Soft3,
"Soft4" => NamedKey::Soft4,
"ChannelDown" => NamedKey::ChannelDown,
"ChannelUp" => NamedKey::ChannelUp,
"Close" => NamedKey::Close,
"MailForward" => NamedKey::MailForward,
"MailReply" => NamedKey::MailReply,
"MailSend" => NamedKey::MailSend,
"MediaClose" => NamedKey::MediaClose,
"MediaFastForward" => NamedKey::MediaFastForward,
"MediaPause" => NamedKey::MediaPause,
"MediaPlay" => NamedKey::MediaPlay,
"MediaPlayPause" => NamedKey::MediaPlayPause,
"MediaRecord" => NamedKey::MediaRecord,
"MediaRewind" => NamedKey::MediaRewind,
"MediaStop" => NamedKey::MediaStop,
"MediaTrackNext" => NamedKey::MediaTrackNext,
"MediaTrackPrevious" => NamedKey::MediaTrackPrevious,
"New" => NamedKey::New,
"Open" => NamedKey::Open,
"Print" => NamedKey::Print,
"Save" => NamedKey::Save,
"SpellCheck" => NamedKey::SpellCheck,
"Key11" => NamedKey::Key11,
"Key12" => NamedKey::Key12,
"AudioBalanceLeft" => NamedKey::AudioBalanceLeft,
"AudioBalanceRight" => NamedKey::AudioBalanceRight,
"AudioBassBoostDown" => NamedKey::AudioBassBoostDown,
"AudioBassBoostToggle" => NamedKey::AudioBassBoostToggle,
"AudioBassBoostUp" => NamedKey::AudioBassBoostUp,
"AudioFaderFront" => NamedKey::AudioFaderFront,
"AudioFaderRear" => NamedKey::AudioFaderRear,
"AudioSurroundModeNext" => NamedKey::AudioSurroundModeNext,
"AudioTrebleDown" => NamedKey::AudioTrebleDown,
"AudioTrebleUp" => NamedKey::AudioTrebleUp,
"AudioVolumeDown" => NamedKey::AudioVolumeDown,
"AudioVolumeUp" => NamedKey::AudioVolumeUp,
"AudioVolumeMute" => NamedKey::AudioVolumeMute,
"MicrophoneToggle" => NamedKey::MicrophoneToggle,
"MicrophoneVolumeDown" => NamedKey::MicrophoneVolumeDown,
"MicrophoneVolumeUp" => NamedKey::MicrophoneVolumeUp,
"MicrophoneVolumeMute" => NamedKey::MicrophoneVolumeMute,
"SpeechCorrectionList" => NamedKey::SpeechCorrectionList,
"SpeechInputToggle" => NamedKey::SpeechInputToggle,
"LaunchApplication1" => NamedKey::LaunchApplication1,
"LaunchApplication2" => NamedKey::LaunchApplication2,
"LaunchCalendar" => NamedKey::LaunchCalendar,
"LaunchContacts" => NamedKey::LaunchContacts,
"LaunchMail" => NamedKey::LaunchMail,
"LaunchMediaPlayer" => NamedKey::LaunchMediaPlayer,
"LaunchMusicPlayer" => NamedKey::LaunchMusicPlayer,
"LaunchPhone" => NamedKey::LaunchPhone,
"LaunchScreenSaver" => NamedKey::LaunchScreenSaver,
"LaunchSpreadsheet" => NamedKey::LaunchSpreadsheet,
"LaunchWebBrowser" => NamedKey::LaunchWebBrowser,
"LaunchWebCam" => NamedKey::LaunchWebCam,
"LaunchWordProcessor" => NamedKey::LaunchWordProcessor,
"BrowserBack" => NamedKey::BrowserBack,
"BrowserFavorites" => NamedKey::BrowserFavorites,
"BrowserForward" => NamedKey::BrowserForward,
"BrowserHome" => NamedKey::BrowserHome,
"BrowserRefresh" => NamedKey::BrowserRefresh,
"BrowserSearch" => NamedKey::BrowserSearch,
"BrowserStop" => NamedKey::BrowserStop,
"AppSwitch" => NamedKey::AppSwitch,
"Call" => NamedKey::Call,
"Camera" => NamedKey::Camera,
"CameraFocus" => NamedKey::CameraFocus,
"EndCall" => NamedKey::EndCall,
"GoBack" => NamedKey::GoBack,
"GoHome" => NamedKey::GoHome,
"HeadsetHook" => NamedKey::HeadsetHook,
"LastNumberRedial" => NamedKey::LastNumberRedial,
"Notification" => NamedKey::Notification,
"MannerMode" => NamedKey::MannerMode,
"VoiceDial" => NamedKey::VoiceDial,
"TV" => NamedKey::TV,
"TV3DMode" => NamedKey::TV3DMode,
"TVAntennaCable" => NamedKey::TVAntennaCable,
"TVAudioDescription" => NamedKey::TVAudioDescription,
"TVAudioDescriptionMixDown" => NamedKey::TVAudioDescriptionMixDown,
"TVAudioDescriptionMixUp" => NamedKey::TVAudioDescriptionMixUp,
"TVContentsMenu" => NamedKey::TVContentsMenu,
"TVDataService" => NamedKey::TVDataService,
"TVInput" => NamedKey::TVInput,
"TVInputComponent1" => NamedKey::TVInputComponent1,
"TVInputComponent2" => NamedKey::TVInputComponent2,
"TVInputComposite1" => NamedKey::TVInputComposite1,
"TVInputComposite2" => NamedKey::TVInputComposite2,
"TVInputHDMI1" => NamedKey::TVInputHDMI1,
"TVInputHDMI2" => NamedKey::TVInputHDMI2,
"TVInputHDMI3" => NamedKey::TVInputHDMI3,
"TVInputHDMI4" => NamedKey::TVInputHDMI4,
"TVInputVGA1" => NamedKey::TVInputVGA1,
"TVMediaContext" => NamedKey::TVMediaContext,
"TVNetwork" => NamedKey::TVNetwork,
"TVNumberEntry" => NamedKey::TVNumberEntry,
"TVPower" => NamedKey::TVPower,
"TVRadioService" => NamedKey::TVRadioService,
"TVSatellite" => NamedKey::TVSatellite,
"TVSatelliteBS" => NamedKey::TVSatelliteBS,
"TVSatelliteCS" => NamedKey::TVSatelliteCS,
"TVSatelliteToggle" => NamedKey::TVSatelliteToggle,
"TVTerrestrialAnalog" => NamedKey::TVTerrestrialAnalog,
"TVTerrestrialDigital" => NamedKey::TVTerrestrialDigital,
"TVTimer" => NamedKey::TVTimer,
"AVRInput" => NamedKey::AVRInput,
"AVRPower" => NamedKey::AVRPower,
"ColorF0Red" => NamedKey::ColorF0Red,
"ColorF1Green" => NamedKey::ColorF1Green,
"ColorF2Yellow" => NamedKey::ColorF2Yellow,
"ColorF3Blue" => NamedKey::ColorF3Blue,
"ColorF4Grey" => NamedKey::ColorF4Grey,
"ColorF5Brown" => NamedKey::ColorF5Brown,
"ClosedCaptionToggle" => NamedKey::ClosedCaptionToggle,
"Dimmer" => NamedKey::Dimmer,
"DisplaySwap" => NamedKey::DisplaySwap,
"DVR" => NamedKey::DVR,
"Exit" => NamedKey::Exit,
"FavoriteClear0" => NamedKey::FavoriteClear0,
"FavoriteClear1" => NamedKey::FavoriteClear1,
"FavoriteClear2" => NamedKey::FavoriteClear2,
"FavoriteClear3" => NamedKey::FavoriteClear3,
"FavoriteRecall0" => NamedKey::FavoriteRecall0,
"FavoriteRecall1" => NamedKey::FavoriteRecall1,
"FavoriteRecall2" => NamedKey::FavoriteRecall2,
"FavoriteRecall3" => NamedKey::FavoriteRecall3,
"FavoriteStore0" => NamedKey::FavoriteStore0,
"FavoriteStore1" => NamedKey::FavoriteStore1,
"FavoriteStore2" => NamedKey::FavoriteStore2,
"FavoriteStore3" => NamedKey::FavoriteStore3,
"Guide" => NamedKey::Guide,
"GuideNextDay" => NamedKey::GuideNextDay,
"GuidePreviousDay" => NamedKey::GuidePreviousDay,
"Info" => NamedKey::Info,
"InstantReplay" => NamedKey::InstantReplay,
"Link" => NamedKey::Link,
"ListProgram" => NamedKey::ListProgram,
"LiveContent" => NamedKey::LiveContent,
"Lock" => NamedKey::Lock,
"MediaApps" => NamedKey::MediaApps,
"MediaAudioTrack" => NamedKey::MediaAudioTrack,
"MediaLast" => NamedKey::MediaLast,
"MediaSkipBackward" => NamedKey::MediaSkipBackward,
"MediaSkipForward" => NamedKey::MediaSkipForward,
"MediaStepBackward" => NamedKey::MediaStepBackward,
"MediaStepForward" => NamedKey::MediaStepForward,
"MediaTopMenu" => NamedKey::MediaTopMenu,
"NavigateIn" => NamedKey::NavigateIn,
"NavigateNext" => NamedKey::NavigateNext,
"NavigateOut" => NamedKey::NavigateOut,
"NavigatePrevious" => NamedKey::NavigatePrevious,
"NextFavoriteChannel" => NamedKey::NextFavoriteChannel,
"NextUserProfile" => NamedKey::NextUserProfile,
"OnDemand" => NamedKey::OnDemand,
"Pairing" => NamedKey::Pairing,
"PinPDown" => NamedKey::PinPDown,
"PinPMove" => NamedKey::PinPMove,
"PinPToggle" => NamedKey::PinPToggle,
"PinPUp" => NamedKey::PinPUp,
"PlaySpeedDown" => NamedKey::PlaySpeedDown,
"PlaySpeedReset" => NamedKey::PlaySpeedReset,
"PlaySpeedUp" => NamedKey::PlaySpeedUp,
"RandomToggle" => NamedKey::RandomToggle,
"RcLowBattery" => NamedKey::RcLowBattery,
"RecordSpeedNext" => NamedKey::RecordSpeedNext,
"RfBypass" => NamedKey::RfBypass,
"ScanChannelsToggle" => NamedKey::ScanChannelsToggle,
"ScreenModeNext" => NamedKey::ScreenModeNext,
"Settings" => NamedKey::Settings,
"SplitScreenToggle" => NamedKey::SplitScreenToggle,
"STBInput" => NamedKey::STBInput,
"STBPower" => NamedKey::STBPower,
"Subtitle" => NamedKey::Subtitle,
"Teletext" => NamedKey::Teletext,
"VideoModeNext" => NamedKey::VideoModeNext,
"Wink" => NamedKey::Wink,
"ZoomToggle" => NamedKey::ZoomToggle,
"F1" => NamedKey::F1,
"F2" => NamedKey::F2,
"F3" => NamedKey::F3,
"F4" => NamedKey::F4,
"F5" => NamedKey::F5,
"F6" => NamedKey::F6,
"F7" => NamedKey::F7,
"F8" => NamedKey::F8,
"F9" => NamedKey::F9,
"F10" => NamedKey::F10,
"F11" => NamedKey::F11,
"F12" => NamedKey::F12,
"F13" => NamedKey::F13,
"F14" => NamedKey::F14,
"F15" => NamedKey::F15,
"F16" => NamedKey::F16,
"F17" => NamedKey::F17,
"F18" => NamedKey::F18,
"F19" => NamedKey::F19,
"F20" => NamedKey::F20,
"F21" => NamedKey::F21,
"F22" => NamedKey::F22,
"F23" => NamedKey::F23,
"F24" => NamedKey::F24,
"F25" => NamedKey::F25,
"F26" => NamedKey::F26,
"F27" => NamedKey::F27,
"F28" => NamedKey::F28,
"F29" => NamedKey::F29,
"F30" => NamedKey::F30,
"F31" => NamedKey::F31,
"F32" => NamedKey::F32,
"F33" => NamedKey::F33,
"F34" => NamedKey::F34,
"F35" => NamedKey::F35,
string => return Key::Character(SmolStr::new(string)),
})
}
}
impl PhysicalKey {
pub fn from_key_code_attribute_value(kcav: &str) -> Self {
PhysicalKey::Code(match kcav {
"Backquote" => KeyCode::Backquote,
"Backslash" => KeyCode::Backslash,
"BracketLeft" => KeyCode::BracketLeft,
"BracketRight" => KeyCode::BracketRight,
"Comma" => KeyCode::Comma,
"Digit0" => KeyCode::Digit0,
"Digit1" => KeyCode::Digit1,
"Digit2" => KeyCode::Digit2,
"Digit3" => KeyCode::Digit3,
"Digit4" => KeyCode::Digit4,
"Digit5" => KeyCode::Digit5,
"Digit6" => KeyCode::Digit6,
"Digit7" => KeyCode::Digit7,
"Digit8" => KeyCode::Digit8,
"Digit9" => KeyCode::Digit9,
"Equal" => KeyCode::Equal,
"IntlBackslash" => KeyCode::IntlBackslash,
"IntlRo" => KeyCode::IntlRo,
"IntlYen" => KeyCode::IntlYen,
"KeyA" => KeyCode::KeyA,
"KeyB" => KeyCode::KeyB,
"KeyC" => KeyCode::KeyC,
"KeyD" => KeyCode::KeyD,
"KeyE" => KeyCode::KeyE,
"KeyF" => KeyCode::KeyF,
"KeyG" => KeyCode::KeyG,
"KeyH" => KeyCode::KeyH,
"KeyI" => KeyCode::KeyI,
"KeyJ" => KeyCode::KeyJ,
"KeyK" => KeyCode::KeyK,
"KeyL" => KeyCode::KeyL,
"KeyM" => KeyCode::KeyM,
"KeyN" => KeyCode::KeyN,
"KeyO" => KeyCode::KeyO,
"KeyP" => KeyCode::KeyP,
"KeyQ" => KeyCode::KeyQ,
"KeyR" => KeyCode::KeyR,
"KeyS" => KeyCode::KeyS,
"KeyT" => KeyCode::KeyT,
"KeyU" => KeyCode::KeyU,
"KeyV" => KeyCode::KeyV,
"KeyW" => KeyCode::KeyW,
"KeyX" => KeyCode::KeyX,
"KeyY" => KeyCode::KeyY,
"KeyZ" => KeyCode::KeyZ,
"Minus" => KeyCode::Minus,
"Period" => KeyCode::Period,
"Quote" => KeyCode::Quote,
"Semicolon" => KeyCode::Semicolon,
"Slash" => KeyCode::Slash,
"AltLeft" => KeyCode::AltLeft,
"AltRight" => KeyCode::AltRight,
"Backspace" => KeyCode::Backspace,
"CapsLock" => KeyCode::CapsLock,
"ContextMenu" => KeyCode::ContextMenu,
"ControlLeft" => KeyCode::ControlLeft,
"ControlRight" => KeyCode::ControlRight,
"Enter" => KeyCode::Enter,
"MetaLeft" => KeyCode::SuperLeft,
"MetaRight" => KeyCode::SuperRight,
"ShiftLeft" => KeyCode::ShiftLeft,
"ShiftRight" => KeyCode::ShiftRight,
"Space" => KeyCode::Space,
"Tab" => KeyCode::Tab,
"Convert" => KeyCode::Convert,
"KanaMode" => KeyCode::KanaMode,
"Lang1" => KeyCode::Lang1,
"Lang2" => KeyCode::Lang2,
"Lang3" => KeyCode::Lang3,
"Lang4" => KeyCode::Lang4,
"Lang5" => KeyCode::Lang5,
"NonConvert" => KeyCode::NonConvert,
"Delete" => KeyCode::Delete,
"End" => KeyCode::End,
"Help" => KeyCode::Help,
"Home" => KeyCode::Home,
"Insert" => KeyCode::Insert,
"PageDown" => KeyCode::PageDown,
"PageUp" => KeyCode::PageUp,
"ArrowDown" => KeyCode::ArrowDown,
"ArrowLeft" => KeyCode::ArrowLeft,
"ArrowRight" => KeyCode::ArrowRight,
"ArrowUp" => KeyCode::ArrowUp,
"NumLock" => KeyCode::NumLock,
"Numpad0" => KeyCode::Numpad0,
"Numpad1" => KeyCode::Numpad1,
"Numpad2" => KeyCode::Numpad2,
"Numpad3" => KeyCode::Numpad3,
"Numpad4" => KeyCode::Numpad4,
"Numpad5" => KeyCode::Numpad5,
"Numpad6" => KeyCode::Numpad6,
"Numpad7" => KeyCode::Numpad7,
"Numpad8" => KeyCode::Numpad8,
"Numpad9" => KeyCode::Numpad9,
"NumpadAdd" => KeyCode::NumpadAdd,
"NumpadBackspace" => KeyCode::NumpadBackspace,
"NumpadClear" => KeyCode::NumpadClear,
"NumpadClearEntry" => KeyCode::NumpadClearEntry,
"NumpadComma" => KeyCode::NumpadComma,
"NumpadDecimal" => KeyCode::NumpadDecimal,
"NumpadDivide" => KeyCode::NumpadDivide,
"NumpadEnter" => KeyCode::NumpadEnter,
"NumpadEqual" => KeyCode::NumpadEqual,
"NumpadHash" => KeyCode::NumpadHash,
"NumpadMemoryAdd" => KeyCode::NumpadMemoryAdd,
"NumpadMemoryClear" => KeyCode::NumpadMemoryClear,
"NumpadMemoryRecall" => KeyCode::NumpadMemoryRecall,
"NumpadMemoryStore" => KeyCode::NumpadMemoryStore,
"NumpadMemorySubtract" => KeyCode::NumpadMemorySubtract,
"NumpadMultiply" => KeyCode::NumpadMultiply,
"NumpadParenLeft" => KeyCode::NumpadParenLeft,
"NumpadParenRight" => KeyCode::NumpadParenRight,
"NumpadStar" => KeyCode::NumpadStar,
"NumpadSubtract" => KeyCode::NumpadSubtract,
"Escape" => KeyCode::Escape,
"Fn" => KeyCode::Fn,
"FnLock" => KeyCode::FnLock,
"PrintScreen" => KeyCode::PrintScreen,
"ScrollLock" => KeyCode::ScrollLock,
"Pause" => KeyCode::Pause,
"BrowserBack" => KeyCode::BrowserBack,
"BrowserFavorites" => KeyCode::BrowserFavorites,
"BrowserForward" => KeyCode::BrowserForward,
"BrowserHome" => KeyCode::BrowserHome,
"BrowserRefresh" => KeyCode::BrowserRefresh,
"BrowserSearch" => KeyCode::BrowserSearch,
"BrowserStop" => KeyCode::BrowserStop,
"Eject" => KeyCode::Eject,
"LaunchApp1" => KeyCode::LaunchApp1,
"LaunchApp2" => KeyCode::LaunchApp2,
"LaunchMail" => KeyCode::LaunchMail,
"MediaPlayPause" => KeyCode::MediaPlayPause,
"MediaSelect" => KeyCode::MediaSelect,
"MediaStop" => KeyCode::MediaStop,
"MediaTrackNext" => KeyCode::MediaTrackNext,
"MediaTrackPrevious" => KeyCode::MediaTrackPrevious,
"Power" => KeyCode::Power,
"Sleep" => KeyCode::Sleep,
"AudioVolumeDown" => KeyCode::AudioVolumeDown,
"AudioVolumeMute" => KeyCode::AudioVolumeMute,
"AudioVolumeUp" => KeyCode::AudioVolumeUp,
"WakeUp" => KeyCode::WakeUp,
"Hyper" => KeyCode::Hyper,
"Turbo" => KeyCode::Turbo,
"Abort" => KeyCode::Abort,
"Resume" => KeyCode::Resume,
"Suspend" => KeyCode::Suspend,
"Again" => KeyCode::Again,
"Copy" => KeyCode::Copy,
"Cut" => KeyCode::Cut,
"Find" => KeyCode::Find,
"Open" => KeyCode::Open,
"Paste" => KeyCode::Paste,
"Props" => KeyCode::Props,
"Select" => KeyCode::Select,
"Undo" => KeyCode::Undo,
"Hiragana" => KeyCode::Hiragana,
"Katakana" => KeyCode::Katakana,
"F1" => KeyCode::F1,
"F2" => KeyCode::F2,
"F3" => KeyCode::F3,
"F4" => KeyCode::F4,
"F5" => KeyCode::F5,
"F6" => KeyCode::F6,
"F7" => KeyCode::F7,
"F8" => KeyCode::F8,
"F9" => KeyCode::F9,
"F10" => KeyCode::F10,
"F11" => KeyCode::F11,
"F12" => KeyCode::F12,
"F13" => KeyCode::F13,
"F14" => KeyCode::F14,
"F15" => KeyCode::F15,
"F16" => KeyCode::F16,
"F17" => KeyCode::F17,
"F18" => KeyCode::F18,
"F19" => KeyCode::F19,
"F20" => KeyCode::F20,
"F21" => KeyCode::F21,
"F22" => KeyCode::F22,
"F23" => KeyCode::F23,
"F24" => KeyCode::F24,
"F25" => KeyCode::F25,
"F26" => KeyCode::F26,
"F27" => KeyCode::F27,
"F28" => KeyCode::F28,
"F29" => KeyCode::F29,
"F30" => KeyCode::F30,
"F31" => KeyCode::F31,
"F32" => KeyCode::F32,
"F33" => KeyCode::F33,
"F34" => KeyCode::F34,
"F35" => KeyCode::F35,
_ => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
})
}
}

View File

@@ -1,30 +0,0 @@
// A poly-fill for `lazy_cell`
// Replace with std::sync::LazyLock when https://github.com/rust-lang/rust/issues/109736 is stablized.
// This isn't used on every platform, which can come up as dead code warnings.
#![allow(dead_code)]
use std::ops::Deref;
use std::sync::OnceLock;
pub(crate) struct Lazy<T> {
cell: OnceLock<T>,
init: fn() -> T,
}
impl<T> Lazy<T> {
pub const fn new(f: fn() -> T) -> Self {
Self {
cell: OnceLock::new(),
init: f,
}
}
}
impl<T> Deref for Lazy<T> {
type Target = T;
#[inline]
fn deref(&self) -> &'_ T {
self.cell.get_or_init(self.init)
}
}

View File

@@ -5,7 +5,7 @@ fn needs_send<T: Send>() {}
fn event_loop_proxy_send() { fn event_loop_proxy_send() {
#[allow(dead_code)] #[allow(dead_code)]
fn is_send<T: 'static + Send>() { fn is_send<T: 'static + Send>() {
// ensures that `winit::EventLoopProxy<T: Send>` implements `Send` // ensures that `winit::EventLoopProxy` implements `Send`
needs_send::<winit::event_loop::EventLoopProxy<T>>(); needs_send::<winit::event_loop::EventLoopProxy<T>>();
} }
} }
@@ -18,7 +18,7 @@ fn window_send() {
#[test] #[test]
fn window_builder_send() { fn window_builder_send() {
needs_send::<winit::window::WindowAttributes>(); needs_send::<winit::window::WindowBuilder>();
} }
#[test] #[test]
@@ -31,6 +31,6 @@ fn ids_send() {
#[test] #[test]
fn custom_cursor_send() { fn custom_cursor_send() {
needs_send::<winit::window::CustomCursorSource>(); needs_send::<winit::window::CustomCursorBuilder>();
needs_send::<winit::window::CustomCursor>(); needs_send::<winit::window::CustomCursor>();
} }

View File

@@ -1,15 +1,6 @@
#[allow(dead_code)] #[allow(dead_code)]
fn needs_sync<T: Sync>() {} fn needs_sync<T: Sync>() {}
#[test]
fn event_loop_proxy_send() {
#[allow(dead_code)]
fn is_send<T: 'static + Send>() {
// ensures that `winit::EventLoopProxy<T: Send>` implements `Sync`
needs_sync::<winit::event_loop::EventLoopProxy<T>>();
}
}
#[test] #[test]
fn window_sync() { fn window_sync() {
// ensures that `winit::Window` implements `Sync` // ensures that `winit::Window` implements `Sync`
@@ -18,11 +9,11 @@ fn window_sync() {
#[test] #[test]
fn window_builder_sync() { fn window_builder_sync() {
needs_sync::<winit::window::WindowAttributes>(); needs_sync::<winit::window::WindowBuilder>();
} }
#[test] #[test]
fn custom_cursor_sync() { fn custom_cursor_sync() {
needs_sync::<winit::window::CustomCursorSource>(); needs_sync::<winit::window::CustomCursorBuilder>();
needs_sync::<winit::window::CustomCursor>(); needs_sync::<winit::window::CustomCursor>();
} }

32
winit-core/Cargo.toml Normal file
View File

@@ -0,0 +1,32 @@
[package]
name = "winit-core"
version = "0.1.0"
authors = ["The winit contributors"]
description = "Base types for windowing libraries"
edition = "2021"
keywords = ["windowing"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version = "1.70.0"
[features]
default = ["std"]
std = ["alloc"]
alloc = []
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
[dependencies]
bitflags.workspace = true
cursor-icon.workspace = true
mint = { version = "0.5.6", optional = true }
serde = { workspace = true, optional = true }
smol_str.workspace = true
[target.'cfg(target_family = "wasm")'.dependencies]
web-time.workspace = true
[build-dependencies]
cfg_aliases.workspace = true

3
winit-core/README.md Normal file
View File

@@ -0,0 +1,3 @@
# winit-core - Base types for a windowing system
TODO

View File

@@ -1,4 +1,4 @@
//! # DPI //! UI scaling is important, so read the docs for this module if you don't want to be confused.
//! //!
//! ## Why should I care about UI scaling? //! ## Why should I care about UI scaling?
//! //!
@@ -35,11 +35,14 @@
//! //!
//! ### Position and Size types //! ### Position and Size types
//! //!
//! The [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the //! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the
//! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels //! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels
//! divided by the scale factor. //! divided by the scale factor.
//! All of Winit's functions return physical types, but can take either logical or physical
//! coordinates as input, allowing you to use the most convenient coordinate system for your
//! particular application.
//! //!
//! The position and size types are generic over their exact pixel type, `P`, to allow the //! Winit's position and size 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
@@ -48,24 +51,58 @@
//! 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.
//! //!
//! ## Cargo Features //! ### Events
//! //!
//! This crate provides the following Cargo features: //! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
//! monitor or if the user changes their DPI settings. This allows you to rescale your application's
//! UI elements and adjust how the platform changes the window's size to reflect the new scale
//! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor
//! can be found by calling [`window.scale_factor()`].
//! //!
//! * `serde`: Enables serialization/deserialization of certain types with //! ## How is the scale factor calculated?
//! [Serde](https://crates.io/crates/serde). //!
//! * `mint`: Enables mint (math interoperability standard types) conversions. //! The scale factor is calculated differently on different platforms:
//!
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
//! display settings. While users are free to select any option they want, they're only given a
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7. The scale factor is
//! global and changing it requires logging out. See [this article][windows_1] for technical
//! details.
//! - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific
//! displays. When available, the user may pick a per-monitor scaling factor from a set of
//! pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default,
//! but the specific value varies across devices.
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
//! currently uses a three-pronged approach:
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable if present.
//! + If not present, use the value set in `Xft.dpi` in Xresources.
//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
//!
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
//! - **Wayland:** The scale factor is suggested by the compositor for each window individually. The
//! monitor scale factor may differ from the window scale factor.
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
//! information.
//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the
//! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information.
//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels.
//! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by
//! both the screen scaling and the browser zoom level and can go below `1.0`.
//! //!
//! //!
//! [points]: https://en.wikipedia.org/wiki/Point_(typography) //! [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
#![cfg_attr( //! [`window.scale_factor()`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.scale_factor
docsrs, //! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
feature(doc_auto_cfg, doc_cfg_hide), //! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
doc(cfg_hide(doc, docsrs)) //! [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
#![forbid(unsafe_code)] //! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -131,7 +168,7 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
/// A position represented in logical pixels. /// A position represented in logical pixels.
/// ///
/// 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 noticeable 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, Ord, PartialOrd, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -550,13 +587,15 @@ impl<P: Pixel> From<LogicalPosition<P>> for Position {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use crate::dpi;
use std::collections::HashSet; use std::collections::HashSet;
macro_rules! test_pixel_int_impl { macro_rules! test_pixel_int_impl {
($($name:ident => $ty:ty),*) => {$( ($($name:ident => $ty:ty),*) => {$(
#[test] #[test]
fn $name() { fn $name() {
use dpi::Pixel;
assert_eq!( assert_eq!(
<$ty as Pixel>::from_f64(37.0), <$ty as Pixel>::from_f64(37.0),
37, 37,
@@ -625,6 +664,8 @@ mod tests {
($($name:ident => $ty:ty),*) => {$( ($($name:ident => $ty:ty),*) => {$(
#[test] #[test]
fn $name() { fn $name() {
use dpi::Pixel;
assert_approx_eq!( assert_approx_eq!(
<$ty as Pixel>::from_f64(37.0), <$ty as Pixel>::from_f64(37.0),
37.0, 37.0,
@@ -717,40 +758,46 @@ mod tests {
#[test] #[test]
fn test_validate_scale_factor() { fn test_validate_scale_factor() {
assert!(validate_scale_factor(1.0)); assert!(dpi::validate_scale_factor(1.0));
assert!(validate_scale_factor(2.0)); assert!(dpi::validate_scale_factor(2.0));
assert!(validate_scale_factor(3.0)); assert!(dpi::validate_scale_factor(3.0));
assert!(validate_scale_factor(1.5)); assert!(dpi::validate_scale_factor(1.5));
assert!(validate_scale_factor(0.5)); assert!(dpi::validate_scale_factor(0.5));
assert!(!validate_scale_factor(0.0)); assert!(!dpi::validate_scale_factor(0.0));
assert!(!validate_scale_factor(-1.0)); assert!(!dpi::validate_scale_factor(-1.0));
assert!(!validate_scale_factor(f64::INFINITY)); assert!(!dpi::validate_scale_factor(f64::INFINITY));
assert!(!validate_scale_factor(f64::NAN)); assert!(!dpi::validate_scale_factor(f64::NAN));
assert!(!validate_scale_factor(f64::NEG_INFINITY)); assert!(!dpi::validate_scale_factor(f64::NEG_INFINITY));
} }
#[test] #[test]
fn test_logical_position() { fn test_logical_position() {
let log_pos = LogicalPosition::new(1.0, 2.0); let log_pos = dpi::LogicalPosition::new(1.0, 2.0);
assert_eq!(log_pos.to_physical::<u32>(1.0), PhysicalPosition::new(1, 2)); assert_eq!(
assert_eq!(log_pos.to_physical::<u32>(2.0), PhysicalPosition::new(2, 4)); log_pos.to_physical::<u32>(1.0),
assert_eq!(log_pos.cast::<u32>(), LogicalPosition::new(1, 2)); 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!( assert_eq!(
log_pos, log_pos,
LogicalPosition::from_physical(PhysicalPosition::new(1.0, 2.0), 1.0) dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(1.0, 2.0), 1.0)
); );
assert_eq!( assert_eq!(
log_pos, log_pos,
LogicalPosition::from_physical(PhysicalPosition::new(2.0, 4.0), 2.0) dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(2.0, 4.0), 2.0)
); );
assert_eq!( assert_eq!(
LogicalPosition::from((2.0, 2.0)), dpi::LogicalPosition::from((2.0, 2.0)),
LogicalPosition::new(2.0, 2.0) dpi::LogicalPosition::new(2.0, 2.0)
); );
assert_eq!( assert_eq!(
LogicalPosition::from([2.0, 3.0]), dpi::LogicalPosition::from([2.0, 3.0]),
LogicalPosition::new(2.0, 3.0) dpi::LogicalPosition::new(2.0, 3.0)
); );
let x: (f64, f64) = log_pos.into(); let x: (f64, f64) = log_pos.into();
@@ -762,44 +809,56 @@ mod tests {
#[test] #[test]
fn test_physical_position() { fn test_physical_position() {
assert_eq!( assert_eq!(
PhysicalPosition::from_logical(LogicalPosition::new(1.0, 2.0), 1.0), dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(1.0, 2.0), 1.0),
PhysicalPosition::new(1, 2) dpi::PhysicalPosition::new(1, 2)
); );
assert_eq!( assert_eq!(
PhysicalPosition::from_logical(LogicalPosition::new(2.0, 4.0), 0.5), dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(2.0, 4.0), 0.5),
PhysicalPosition::new(1, 2) dpi::PhysicalPosition::new(1, 2)
); );
assert_eq!( assert_eq!(
PhysicalPosition::from((2.0, 2.0)), dpi::PhysicalPosition::from((2.0, 2.0)),
PhysicalPosition::new(2.0, 2.0) dpi::PhysicalPosition::new(2.0, 2.0)
); );
assert_eq!( assert_eq!(
PhysicalPosition::from([2.0, 3.0]), dpi::PhysicalPosition::from([2.0, 3.0]),
PhysicalPosition::new(2.0, 3.0) dpi::PhysicalPosition::new(2.0, 3.0)
); );
let x: (f64, f64) = PhysicalPosition::new(1, 2).into(); let x: (f64, f64) = dpi::PhysicalPosition::new(1, 2).into();
assert_eq!(x, (1.0, 2.0)); assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = PhysicalPosition::new(1, 2).into(); let x: [f64; 2] = dpi::PhysicalPosition::new(1, 2).into();
assert_eq!(x, [1.0, 2.0]); assert_eq!(x, [1.0, 2.0]);
} }
#[test] #[test]
fn test_logical_size() { fn test_logical_size() {
let log_size = LogicalSize::new(1.0, 2.0); let log_size = dpi::LogicalSize::new(1.0, 2.0);
assert_eq!(log_size.to_physical::<u32>(1.0), PhysicalSize::new(1, 2)); assert_eq!(
assert_eq!(log_size.to_physical::<u32>(2.0), PhysicalSize::new(2, 4)); log_size.to_physical::<u32>(1.0),
assert_eq!(log_size.cast::<u32>(), LogicalSize::new(1, 2)); 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!( assert_eq!(
log_size, log_size,
LogicalSize::from_physical(PhysicalSize::new(1.0, 2.0), 1.0) dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(1.0, 2.0), 1.0)
); );
assert_eq!( assert_eq!(
log_size, log_size,
LogicalSize::from_physical(PhysicalSize::new(2.0, 4.0), 2.0) 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)
); );
assert_eq!(LogicalSize::from((2.0, 2.0)), LogicalSize::new(2.0, 2.0));
assert_eq!(LogicalSize::from([2.0, 3.0]), LogicalSize::new(2.0, 3.0));
let x: (f64, f64) = log_size.into(); let x: (f64, f64) = log_size.into();
assert_eq!(x, (1.0, 2.0)); assert_eq!(x, (1.0, 2.0));
@@ -810,130 +869,136 @@ mod tests {
#[test] #[test]
fn test_physical_size() { fn test_physical_size() {
assert_eq!( assert_eq!(
PhysicalSize::from_logical(LogicalSize::new(1.0, 2.0), 1.0), dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(1.0, 2.0), 1.0),
PhysicalSize::new(1, 2) dpi::PhysicalSize::new(1, 2)
); );
assert_eq!( assert_eq!(
PhysicalSize::from_logical(LogicalSize::new(2.0, 4.0), 0.5), dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(2.0, 4.0), 0.5),
PhysicalSize::new(1, 2) 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)
); );
assert_eq!(PhysicalSize::from((2.0, 2.0)), PhysicalSize::new(2.0, 2.0));
assert_eq!(PhysicalSize::from([2.0, 3.0]), PhysicalSize::new(2.0, 3.0));
let x: (f64, f64) = PhysicalSize::new(1, 2).into(); let x: (f64, f64) = dpi::PhysicalSize::new(1, 2).into();
assert_eq!(x, (1.0, 2.0)); assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = PhysicalSize::new(1, 2).into(); let x: [f64; 2] = dpi::PhysicalSize::new(1, 2).into();
assert_eq!(x, [1.0, 2.0]); assert_eq!(x, [1.0, 2.0]);
} }
#[test] #[test]
fn test_size() { fn test_size() {
assert_eq!( assert_eq!(
Size::new(PhysicalSize::new(1, 2)), dpi::Size::new(dpi::PhysicalSize::new(1, 2)),
Size::Physical(PhysicalSize::new(1, 2)) dpi::Size::Physical(dpi::PhysicalSize::new(1, 2))
); );
assert_eq!( assert_eq!(
Size::new(LogicalSize::new(1.0, 2.0)), dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)),
Size::Logical(LogicalSize::new(1.0, 2.0)) dpi::Size::Logical(dpi::LogicalSize::new(1.0, 2.0))
); );
assert_eq!( assert_eq!(
Size::new(PhysicalSize::new(1, 2)).to_logical::<f64>(1.0), dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(1.0),
LogicalSize::new(1.0, 2.0) dpi::LogicalSize::new(1.0, 2.0)
); );
assert_eq!( assert_eq!(
Size::new(PhysicalSize::new(1, 2)).to_logical::<f64>(2.0), dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(2.0),
LogicalSize::new(0.5, 1.0) dpi::LogicalSize::new(0.5, 1.0)
); );
assert_eq!( assert_eq!(
Size::new(LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0), dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0),
LogicalSize::new(1.0, 2.0) dpi::LogicalSize::new(1.0, 2.0)
); );
assert_eq!( assert_eq!(
Size::new(PhysicalSize::new(1, 2)).to_physical::<u32>(1.0), dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(1.0),
PhysicalSize::new(1, 2) dpi::PhysicalSize::new(1, 2)
); );
assert_eq!( assert_eq!(
Size::new(PhysicalSize::new(1, 2)).to_physical::<u32>(2.0), dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(2.0),
PhysicalSize::new(1, 2) dpi::PhysicalSize::new(1, 2)
); );
assert_eq!( assert_eq!(
Size::new(LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0), dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0),
PhysicalSize::new(1, 2) dpi::PhysicalSize::new(1, 2)
); );
assert_eq!( assert_eq!(
Size::new(LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0), dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0),
PhysicalSize::new(2, 4) dpi::PhysicalSize::new(2, 4)
); );
let small = Size::Physical((1, 2).into()); let small = dpi::Size::Physical((1, 2).into());
let medium = Size::Logical((3, 4).into()); let medium = dpi::Size::Logical((3, 4).into());
let medium_physical = Size::new(medium.to_physical::<u32>(1.0)); let medium_physical = dpi::Size::new(medium.to_physical::<u32>(1.0));
let large = Size::Physical((5, 6).into()); let large = dpi::Size::Physical((5, 6).into());
assert_eq!(Size::clamp(medium, small, large, 1.0), medium_physical); assert_eq!(dpi::Size::clamp(medium, small, large, 1.0), medium_physical);
assert_eq!(Size::clamp(small, medium, large, 1.0), medium_physical); assert_eq!(dpi::Size::clamp(small, medium, large, 1.0), medium_physical);
assert_eq!(Size::clamp(large, small, medium, 1.0), medium_physical); assert_eq!(dpi::Size::clamp(large, small, medium, 1.0), medium_physical);
} }
#[test] #[test]
fn test_position() { fn test_position() {
assert_eq!( assert_eq!(
Position::new(PhysicalPosition::new(1, 2)), dpi::Position::new(dpi::PhysicalPosition::new(1, 2)),
Position::Physical(PhysicalPosition::new(1, 2)) dpi::Position::Physical(dpi::PhysicalPosition::new(1, 2))
); );
assert_eq!( assert_eq!(
Position::new(LogicalPosition::new(1.0, 2.0)), dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)),
Position::Logical(LogicalPosition::new(1.0, 2.0)) dpi::Position::Logical(dpi::LogicalPosition::new(1.0, 2.0))
); );
assert_eq!( assert_eq!(
Position::new(PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0), dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0),
LogicalPosition::new(1.0, 2.0) dpi::LogicalPosition::new(1.0, 2.0)
); );
assert_eq!( assert_eq!(
Position::new(PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0), dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0),
LogicalPosition::new(0.5, 1.0) dpi::LogicalPosition::new(0.5, 1.0)
); );
assert_eq!( assert_eq!(
Position::new(LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0), dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0),
LogicalPosition::new(1.0, 2.0) dpi::LogicalPosition::new(1.0, 2.0)
); );
assert_eq!( assert_eq!(
Position::new(PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0), dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0),
PhysicalPosition::new(1, 2) dpi::PhysicalPosition::new(1, 2)
); );
assert_eq!( assert_eq!(
Position::new(PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0), dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0),
PhysicalPosition::new(1, 2) dpi::PhysicalPosition::new(1, 2)
); );
assert_eq!( assert_eq!(
Position::new(LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0), dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0),
PhysicalPosition::new(1, 2) dpi::PhysicalPosition::new(1, 2)
); );
assert_eq!( assert_eq!(
Position::new(LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0), dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0),
PhysicalPosition::new(2, 4) dpi::PhysicalPosition::new(2, 4)
); );
} }
// Eat coverage for the Debug impls et al // Eat coverage for the Debug impls et al
#[test] #[test]
fn ensure_attrs_do_not_panic() { fn ensure_attrs_do_not_panic() {
let _ = format!("{:?}", LogicalPosition::<u32>::default().clone()); let _ = format!("{:?}", dpi::LogicalPosition::<u32>::default().clone());
HashSet::new().insert(LogicalPosition::<u32>::default()); HashSet::new().insert(dpi::LogicalPosition::<u32>::default());
let _ = format!("{:?}", PhysicalPosition::<u32>::default().clone()); let _ = format!("{:?}", dpi::PhysicalPosition::<u32>::default().clone());
HashSet::new().insert(PhysicalPosition::<u32>::default()); HashSet::new().insert(dpi::PhysicalPosition::<u32>::default());
let _ = format!("{:?}", LogicalSize::<u32>::default().clone()); let _ = format!("{:?}", dpi::LogicalSize::<u32>::default().clone());
HashSet::new().insert(LogicalSize::<u32>::default()); HashSet::new().insert(dpi::LogicalSize::<u32>::default());
let _ = format!("{:?}", PhysicalSize::<u32>::default().clone()); let _ = format!("{:?}", dpi::PhysicalSize::<u32>::default().clone());
HashSet::new().insert(PhysicalSize::<u32>::default()); HashSet::new().insert(dpi::PhysicalSize::<u32>::default());
let _ = format!("{:?}", Size::Physical((1, 2).into()).clone()); let _ = format!("{:?}", dpi::Size::Physical((1, 2).into()).clone());
let _ = format!("{:?}", Position::Physical((1, 2).into()).clone()); let _ = format!("{:?}", dpi::Position::Physical((1, 2).into()).clone());
} }
} }

54
winit-core/src/error.rs Normal file
View File

@@ -0,0 +1,54 @@
//! Common error types.
use std::{error, fmt};
/// The error type for when the requested operation is not supported by the backend.
#[derive(Clone)]
pub struct NotSupportedError {
_marker: (),
}
impl Default for NotSupportedError {
fn default() -> Self {
Self::new()
}
}
impl NotSupportedError {
/// Create a new [`NotSupportedError`].
#[inline]
pub fn new() -> NotSupportedError {
NotSupportedError { _marker: () }
}
}
impl fmt::Debug for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("NotSupportedError").finish()
}
}
impl fmt::Display for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.pad("the requested operation is not supported by Winit")
}
}
impl error::Error for NotSupportedError {}
#[cfg(test)]
mod tests {
#![allow(clippy::redundant_clone)]
use super::*;
// Eat attributes for testing
#[test]
fn ensure_fmt_does_not_panic() {
let _ = format!(
"{:?}, {}",
NotSupportedError::new(),
NotSupportedError::new().clone()
);
}
}

View File

@@ -1,64 +1,64 @@
//! The [`Event`] enum and assorted supporting types. //! Incoming notifications from the GUI system.
//!
//! These are sent to the closure given to [`EventLoop::run(...)`], where they get use crate::dpi::{PhysicalPosition, PhysicalSize};
//! processed and used to modify the program state. For more details, see the root-level documentation. use crate::event_loop::AsyncRequestSerial;
//! use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
//! Some of these events represent different "parts" of a traditional event-handling loop. You could use crate::window::{ActivationToken, Theme, WindowId};
//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this:
//! use std::fmt;
//! ```rust,ignore
//! let mut start_cause = StartCause::Init;
//!
//! while !elwt.exiting() {
//! event_handler(NewEvents(start_cause), elwt);
//!
//! for e in (window events, user events, device events) {
//! event_handler(e, elwt);
//! }
//!
//! for w in (redraw windows) {
//! event_handler(RedrawRequested(w), elwt);
//! }
//!
//! event_handler(AboutToWait, elwt);
//! start_cause = wait_if_necessary();
//! }
//!
//! event_handler(LoopExiting, elwt);
//! ```
//!
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
//! describes what happens in what order.
//!
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Mutex, Weak}; use std::sync::{Mutex, Weak};
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
use std::time::Instant; use std::time::Instant;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
#[cfg(web_platform)] #[cfg(web_platform)]
use web_time::Instant; use web_time::Instant;
use crate::error::ExternalError; #[cfg(feature = "serde")]
#[cfg(doc)] pub use serde::{Deserialize, Serialize};
use crate::window::Window;
use crate::{ use smol_str::SmolStr;
dpi::{PhysicalPosition, PhysicalSize},
event_loop::AsyncRequestSerial, /// Identifier of an input device.
keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState}, ///
platform_impl, /// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
window::{ActivationToken, Theme, WindowId}, /// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
}; /// physical. Virtual devices typically aggregate inputs from multiple physical devices.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(u64);
impl DeviceId {
/// Returns a dummy id, useful for unit testing.
///
/// # Safety
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub const unsafe fn dummy() -> Self {
DeviceId(0)
}
}
impl From<u64> for DeviceId {
fn from(value: u64) -> Self {
Self(value)
}
}
impl From<DeviceId> for u64 {
fn from(value: DeviceId) -> Self {
value.0
}
}
/// Describes a generic event. /// Describes a generic event.
/// ///
/// See the module-level docs for more information on the event loop manages each event. /// See the module-level docs for more information on the event loop manages each event.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Event<T: 'static> { pub enum Event<T: 'static, KeyExtra> {
/// Emitted when new events arrive from the OS to be processed. /// Emitted when new events arrive from the OS to be processed.
/// ///
/// This event type is useful as a place to put code that should be done before you start /// This event type is useful as a place to put code that should be done before you start
@@ -70,7 +70,7 @@ pub enum Event<T: 'static> {
/// Emitted when the OS sends an event to a winit window. /// Emitted when the OS sends an event to a winit window.
WindowEvent { WindowEvent {
window_id: WindowId, window_id: WindowId,
event: WindowEvent, event: WindowEvent<KeyExtra>,
}, },
/// Emitted when the OS sends an event to a device. /// Emitted when the OS sends an event to a device.
@@ -79,7 +79,7 @@ pub enum Event<T: 'static> {
event: DeviceEvent, event: DeviceEvent,
}, },
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event) /// Emitted when an event is sent from [`EventLoopProxy::send_event`](https://docs.rs/winit/latest/winit/event_loop/struct.EventLoopProxy.html#method.send_event)
UserEvent(T), UserEvent(T),
/// Emitted when the application has been suspended. /// Emitted when the application has been suspended.
@@ -255,9 +255,9 @@ pub enum Event<T: 'static> {
MemoryWarning, MemoryWarning,
} }
impl<T> Event<T> { impl<T, Extra> Event<T, Extra> {
#[allow(clippy::result_large_err)] #[allow(clippy::result_large_err)]
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> { pub fn map_nonuser_event<U>(self) -> Result<Event<U, Extra>, Event<T, Extra>> {
use self::Event::*; use self::Event::*;
match self { match self {
UserEvent(_) => Err(self), UserEvent(_) => Err(self),
@@ -304,12 +304,14 @@ pub enum StartCause {
} }
/// Describes an event from a [`Window`]. /// Describes an event from a [`Window`].
///
/// [`Window`]: https://docs.rs/winit/latest/winit/window/struct.Window.html
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum WindowEvent { pub enum WindowEvent<KeyExtra> {
/// The activation token was delivered back and now could be used. /// The activation token was delivered back and now could be used.
/// ///
#[cfg_attr( #[cfg_attr(
not(any(x11_platform, wayland_platform)), not(any(x11_platform, wayland_platfrom)),
allow(rustdoc::broken_intra_doc_links) allow(rustdoc::broken_intra_doc_links)
)] )]
/// Delivered in response to [`request_activation_token`]. /// Delivered in response to [`request_activation_token`].
@@ -367,7 +369,7 @@ pub enum WindowEvent {
/// events which are not marked as `is_synthetic`. /// events which are not marked as `is_synthetic`.
KeyboardInput { KeyboardInput {
device_id: DeviceId, device_id: DeviceId,
event: KeyEvent, event: KeyEvent<KeyExtra>,
/// If `true`, the event was generated synthetically by winit /// If `true`, the event was generated synthetically by winit
/// in one of the following circumstances: /// in one of the following circumstances:
@@ -391,6 +393,8 @@ pub enum WindowEvent {
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **iOS / Android / Web / Orbital:** Unsupported. /// - **iOS / Android / Web / Orbital:** Unsupported.
///
/// [`Window::set_ime_allowed`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_allowed
Ime(Ime), Ime(Ime),
/// The cursor has moved on the window. /// The cursor has moved on the window.
@@ -473,7 +477,7 @@ pub enum WindowEvent {
/// The event is general enough that its generating gesture is allowed to vary /// The event is general enough that its generating gesture is allowed to vary
/// across platforms. It could also be generated by another device. /// across platforms. It could also be generated by another device.
/// ///
/// Unfortunately, neither [Windows](https://support.microsoft.com/en-us/windows/touch-gestures-for-windows-a9d28305-4818-a5df-4e2b-e5590f850741) /// Unfortunatly, neither [Windows](https://support.microsoft.com/en-us/windows/touch-gestures-for-windows-a9d28305-4818-a5df-4e2b-e5590f850741)
/// nor [Wayland](https://wayland.freedesktop.org/libinput/doc/latest/gestures.html) /// nor [Wayland](https://wayland.freedesktop.org/libinput/doc/latest/gestures.html)
/// support this gesture or any other gesture with the same effect. /// support this gesture or any other gesture with the same effect.
/// ///
@@ -536,10 +540,11 @@ pub enum WindowEvent {
/// * Changing the display's scale factor (e.g. in Control Panel on Windows). /// * Changing the display's scale factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different scale factor. /// * Moving the window to a display with a different scale factor.
/// ///
/// To update the window size, use the provided [`InnerSizeWriter`] handle. By default, the window is /// After this event callback has been processed, the window will be resized to whatever value
/// resized to the value suggested by the OS, but it can be changed to any value. /// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested
/// by the OS, but it can be changed to any value.
/// ///
/// For more information about DPI in general, see the [`dpi`] crate. /// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
ScaleFactorChanged { ScaleFactorChanged {
scale_factor: f64, scale_factor: f64,
/// Handle to update inner size during scale changes. /// Handle to update inner size during scale changes.
@@ -595,33 +600,11 @@ pub enum WindowEvent {
/// ///
/// Winit will aggregate duplicate redraw requests into a single event, to /// Winit will aggregate duplicate redraw requests into a single event, to
/// help avoid duplicating rendering work. /// help avoid duplicating rendering work.
///
/// [`Window::request_redraw`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.request_redraw
RedrawRequested, RedrawRequested,
} }
/// Identifier of an input device.
///
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl DeviceId {
/// Returns a dummy id, useful for unit testing.
///
/// # Safety
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub const unsafe fn dummy() -> Self {
#[allow(unused_unsafe)]
DeviceId(unsafe { platform_impl::DeviceId::dummy() })
}
}
/// Represents raw hardware events that are not associated with any particular window. /// Represents raw hardware events that are not associated with any particular window.
/// ///
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person /// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
@@ -682,7 +665,7 @@ pub struct RawKeyEvent {
/// Describes a keyboard input targeting a window. /// Describes a keyboard input targeting a window.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEvent { pub struct KeyEvent<Extra> {
/// Represents the position of a key independent of the currently active layout. /// Represents the position of a key independent of the currently active layout.
/// ///
/// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode). /// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode).
@@ -732,7 +715,7 @@ pub struct KeyEvent {
/// - **Web:** Dead keys might be reported as the real key instead /// - **Web:** Dead keys might be reported as the real key instead
/// of `Dead` depending on the browser/OS. /// of `Dead` depending on the browser/OS.
/// ///
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers /// [`key_without_modifiers`]: https://docs.rs/winit/latest/winit/platform/modifier_supplement/trait.KeyEventExtModifierSupplement.html#tymethod.key_without_modifiers
pub logical_key: keyboard::Key, pub logical_key: keyboard::Key,
/// Contains the text produced by this keypress. /// Contains the text produced by this keypress.
@@ -789,7 +772,7 @@ pub struct KeyEvent {
/// modifiers applied. /// modifiers applied.
/// ///
/// On Android, iOS, Redox and Web, this type is a no-op. /// On Android, iOS, Redox and Web, this type is a no-op.
pub(crate) platform_specific: platform_impl::KeyEventExtra, pub extra: Extra,
} }
/// Describes keyboard modifiers event. /// Describes keyboard modifiers event.
@@ -804,6 +787,15 @@ pub struct Modifiers {
} }
impl Modifiers { impl Modifiers {
/// Only `winit` should instantiate this!
#[doc(hidden)]
pub fn new(state: ModifiersState, pressed_mods: ModifiersKeys) -> Self {
Self {
state,
pressed_mods,
}
}
/// The state of the modifiers. /// The state of the modifiers.
pub fn state(&self) -> ModifiersState { pub fn state(&self) -> ModifiersState {
self.state self.state
@@ -903,6 +895,8 @@ impl From<ModifiersState> for Modifiers {
/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit. /// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
/// Ime::Commit("啊不") /// Ime::Commit("啊不")
/// ``` /// ```
///
/// [`Window::set_ime_cursor_area`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_cursor_area
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Ime { pub enum Ime {
@@ -911,6 +905,8 @@ pub enum Ime {
/// After getting this event you could receive [`Preedit`](Self::Preedit) and /// After getting this event you could receive [`Preedit`](Self::Preedit) and
/// [`Commit`](Self::Commit) events. You should also start performing IME related requests /// [`Commit`](Self::Commit) events. You should also start performing IME related requests
/// like [`Window::set_ime_cursor_area`]. /// like [`Window::set_ime_cursor_area`].
///
/// [`Window::set_ime_cursor_area`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_cursor_area
Enabled, Enabled,
/// Notifies when a new composing text should be set at the cursor position. /// Notifies when a new composing text should be set at the cursor position.
@@ -933,6 +929,8 @@ pub enum Ime {
/// [`Commit`](Self::Commit) events until the next [`Enabled`](Self::Enabled) event. You should /// [`Commit`](Self::Commit) events until the next [`Enabled`](Self::Enabled) event. You should
/// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear pending /// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear pending
/// preedit text. /// preedit text.
///
/// [`Window::set_ime_cursor_area`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_cursor_area
Disabled, Disabled,
} }
@@ -1112,7 +1110,7 @@ pub enum MouseScrollDelta {
PixelDelta(PhysicalPosition<f64>), PixelDelta(PhysicalPosition<f64>),
} }
/// Handle to synchronously change the size of the window from the /// Handle to synchroniously change the size of the window from the
/// [`WindowEvent`]. /// [`WindowEvent`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct InnerSizeWriter { pub struct InnerSizeWriter {
@@ -1121,22 +1119,27 @@ pub struct InnerSizeWriter {
impl InnerSizeWriter { impl InnerSizeWriter {
#[cfg(not(orbital_platform))] #[cfg(not(orbital_platform))]
pub(crate) fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self { pub fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
Self { new_inner_size } Self { new_inner_size }
} }
/// Try to request inner size which will be set synchronously on the window. /// Try to request inner size which will be set synchroniously on the window.
pub fn request_inner_size( pub fn request_inner_size(
&mut self, &mut self,
new_inner_size: PhysicalSize<u32>, new_inner_size: PhysicalSize<u32>,
) -> Result<(), ExternalError> { ) -> Result<(), InnerSizeIgnored> {
if let Some(inner) = self.new_inner_size.upgrade() { if let Some(inner) = self.new_inner_size.upgrade() {
*inner.lock().unwrap() = new_inner_size; *inner.lock().unwrap() = new_inner_size;
Ok(()) Ok(())
} else { } else {
Err(ExternalError::Ignored) Err(InnerSizeIgnored { _private: () })
} }
} }
/// Get the underlying size.
pub fn get(&self) -> PhysicalSize<u32> {
*self.new_inner_size.upgrade().unwrap().lock().unwrap()
}
} }
impl PartialEq for InnerSizeWriter { impl PartialEq for InnerSizeWriter {
@@ -1145,6 +1148,25 @@ impl PartialEq for InnerSizeWriter {
} }
} }
/// Could not write the inner size.
pub struct InnerSizeIgnored {
_private: (),
}
impl fmt::Debug for InnerSizeIgnored {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InnerSizeIgnored").finish_non_exhaustive()
}
}
impl fmt::Display for InnerSizeIgnored {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("tried to write inner size after")
}
}
impl std::error::Error for InnerSizeIgnored {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::event; use crate::event;
@@ -1270,7 +1292,7 @@ mod tests {
#[allow(clippy::redundant_clone)] #[allow(clippy::redundant_clone)]
#[test] #[test]
fn test_event_clone() { fn test_event_clone() {
foreach_event!(|event: event::Event<()>| { foreach_event!(|event: event::Event<(), ()>| {
let event2 = event.clone(); let event2 = event.clone();
assert_eq!(event, event2); assert_eq!(event, event2);
}) })
@@ -1278,7 +1300,7 @@ mod tests {
#[test] #[test]
fn test_map_nonuser_event() { fn test_map_nonuser_event() {
foreach_event!(|event: event::Event<()>| { foreach_event!(|event: event::Event<(), ()>| {
let is_user = matches!(event, event::Event::UserEvent(())); let is_user = matches!(event, event::Event::UserEvent(()));
let event2 = event.map_nonuser_event::<()>(); let event2 = event.map_nonuser_event::<()>();
if is_user { if is_user {
@@ -1312,7 +1334,7 @@ mod tests {
#[allow(clippy::clone_on_copy)] #[allow(clippy::clone_on_copy)]
#[test] #[test]
fn ensure_attrs_do_not_panic() { fn ensure_attrs_do_not_panic() {
foreach_event!(|event: event::Event<()>| { foreach_event!(|event: event::Event<(), ()>| {
let _ = format!("{:?}", event); let _ = format!("{:?}", event);
}); });
let _ = event::StartCause::Init.clone(); let _ = event::StartCause::Init.clone();

View File

@@ -0,0 +1,80 @@
//! Types needed to define the event loop.
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(web_platform))]
use std::time::{Duration, Instant};
#[cfg(web_platform)]
use web_time::{Duration, Instant};
/// A unique identifier of the winit's async request.
///
/// This could be used to identify the async request once it's done
/// and a specific action must be taken.
///
/// One of the handling scenarious could be to maintain a working list
/// containing [`AsyncRequestSerial`] and some closure associated with it.
/// Then once event is arriving the working list is being traversed and a job
/// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AsyncRequestSerial {
serial: u64,
}
impl AsyncRequestSerial {
/// Get the next serial in the sequence.
pub fn get() -> Self {
static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0);
// NOTE: we rely on wrap around here, while the user may just request
// in the loop u64::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial }
}
}
/// Set through [`EventLoopWindowTarget::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
///
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::Wait
///
/// [`EventLoopWindowTarget::set_control_flow()`]: https://docs.rs/winit/latest/winit/event_loop/struct.EventLoopWindowTarget.html#method.set_control_flow
/// [`Event::AboutToWait`]: crate::event::Event::AboutToWait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
#[default]
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the given time is reached.
///
/// Useful for implementing efficient timers. Applications which want to render at the display's
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API
/// to reduce odds of missed frames.
///
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
}
impl ControlFlow {
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
///
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
/// instead set to [`Wait`].
///
/// [`WaitUntil`]: Self::WaitUntil
/// [`Wait`]: Self::Wait
pub fn wait_duration(timeout: Duration) -> Self {
match Instant::now().checked_add(timeout) {
Some(instant) => Self::WaitUntil(instant),
None => Self::Wait,
}
}
}

View File

@@ -20,7 +20,7 @@
// the W3C short notice apply to the `Key` and `KeyCode` enums and their variants and the // the W3C short notice apply to the `Key` and `KeyCode` enums and their variants and the
// documentation attached to their variants. // documentation attached to their variants.
// --------- BEGINNING OF W3C LICENSE -------------------------------------------------------------- // --------- BEGGINING OF W3C LICENSE --------------------------------------------------------------
// //
// License // License
// //
@@ -55,7 +55,7 @@
// //
// --------- END OF W3C LICENSE -------------------------------------------------------------------- // --------- END OF W3C LICENSE --------------------------------------------------------------------
// --------- BEGINNING OF W3C SHORT NOTICE --------------------------------------------------------- // --------- BEGGINING OF W3C SHORT NOTICE ---------------------------------------------------------
// //
// winit: https://github.com/rust-windowing/winit // winit: https://github.com/rust-windowing/winit
// //
@@ -744,7 +744,7 @@ pub enum KeyCode {
/// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's /// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's
/// another key which the specification calls `Super`. That does not exist here.) /// another key which the specification calls `Super`. That does not exist here.)
/// - The `Space` variant here, can be identified by the character it generates in the /// - The `Space` variant here, can be identified by the character it generates in the
/// specification. /// specificaiton.
/// ///
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/ /// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
#[non_exhaustive] #[non_exhaustive]
@@ -914,7 +914,7 @@ pub enum NamedKey {
Standby, Standby,
/// The WakeUp key. (`KEYCODE_WAKEUP`) /// The WakeUp key. (`KEYCODE_WAKEUP`)
WakeUp, WakeUp,
/// Initiate the multi-candidate mode. /// Initate the multi-candidate mode.
AllCandidates, AllCandidates,
Alphanumeric, Alphanumeric,
/// Initiate the Code Input mode to allow characters to be entered by /// Initiate the Code Input mode to allow characters to be entered by
@@ -1454,12 +1454,35 @@ pub enum NamedKey {
F35, F35,
} }
// NOTE: the exact modifier key is not used to represent modifiers state in the
// first place due to a fact that modifiers state could be changed without any
// key being pressed and on some platforms like Wayland/X11 which key resulted
// in modifiers change is hidden, also, not that it really matters.
//
// The reason this API is even exposed is mostly to provide a way for users
// to treat modifiers differently based on their position, which is required
// on macOS due to their AltGr/Option situation.
bitflags! {
#[doc(hidden)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ModifiersKeys: u8 {
const LSHIFT = 0b0000_0001;
const RSHIFT = 0b0000_0010;
const LCONTROL = 0b0000_0100;
const RCONTROL = 0b0000_1000;
const LALT = 0b0001_0000;
const RALT = 0b0010_0000;
const LSUPER = 0b0100_0000;
const RSUPER = 0b1000_0000;
}
}
/// Key represents the meaning of a keypress. /// Key represents the meaning of a keypress.
/// ///
/// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with /// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
/// additions: /// additions:
/// - All simple variants are wrapped under the `Named` variant /// - All simple variants are wrapped under the `Named` variant
/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`. /// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`.
/// - The `Dead` variant here, can specify the character which is inserted when pressing the /// - The `Dead` variant here, can specify the character which is inserted when pressing the
/// dead-key twice. /// dead-key twice.
/// ///
@@ -1565,7 +1588,7 @@ impl NamedKey {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use winit::keyboard::NamedKey; /// use winit_core::keyboard::NamedKey;
/// ///
/// assert_eq!(NamedKey::Enter.to_text(), Some("\r")); /// assert_eq!(NamedKey::Enter.to_text(), Some("\r"));
/// assert_eq!(NamedKey::F20.to_text(), None); /// assert_eq!(NamedKey::F20.to_text(), None);
@@ -1588,7 +1611,7 @@ impl Key {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use winit::keyboard::{NamedKey, Key}; /// use winit_core::keyboard::{NamedKey, Key};
/// ///
/// assert_eq!(Key::Character("a".into()).to_text(), Some("a")); /// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
/// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r")); /// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
@@ -1727,28 +1750,6 @@ pub enum ModifiersKeyState {
Unknown, Unknown,
} }
// NOTE: the exact modifier key is not used to represent modifiers state in the
// first place due to a fact that modifiers state could be changed without any
// key being pressed and on some platforms like Wayland/X11 which key resulted
// in modifiers change is hidden, also, not that it really matters.
//
// The reason this API is even exposed is mostly to provide a way for users
// to treat modifiers differently based on their position, which is required
// on macOS due to their AltGr/Option situation.
bitflags! {
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct ModifiersKeys: u8 {
const LSHIFT = 0b0000_0001;
const RSHIFT = 0b0000_0010;
const LCONTROL = 0b0000_0100;
const RCONTROL = 0b0000_1000;
const LALT = 0b0001_0000;
const RALT = 0b0010_0000;
const LSUPER = 0b0100_0000;
const RSUPER = 0b1000_0000;
}
}
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
mod modifiers_serde { mod modifiers_serde {
use super::ModifiersState; use super::ModifiersState;

18
winit-core/src/lib.rs Normal file
View File

@@ -0,0 +1,18 @@
//! Base types for a windowing library.
//!
//! This crate contains types, traits and basic functions from [`winit`] that are platform
//! independent. It is intended to allow for other crates to build abstractions around [`winit`]
//! without needing to pull in all of [`winit`]'s dependencies, as well as to provide an
//! interface for alternative backends for [`winit`] to be constructed.
//!
//! [`winit`]: https://docs.rs/winit
#[cfg(any(not(feature = "std"), not(feature = "alloc")))]
compile_error! { "no-std and no-alloc usage are not yet supported" }
pub mod dpi;
pub mod error;
pub mod event;
pub mod event_loop;
pub mod keyboard;
pub mod window;

229
winit-core/src/window.rs Normal file
View File

@@ -0,0 +1,229 @@
//! Types used in window construction.
#[doc(inline)]
pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError};
#[cfg(feature = "serde")]
pub use serde::{Deserialize, Serialize};
/// Identifier of a window. Unique for each window.
///
/// Can be obtained with [`window.id()`](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.id).
///
/// Whenever you receive an event specific to a window, this event contains a `WindowId` which you
/// can then compare to the ids of your windows.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(u64);
impl WindowId {
/// Returns a dummy id, useful for unit testing.
///
/// # Safety
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real [`WindowId`].
///
/// **Passing this into a winit function will result in undefined behavior.**
pub const unsafe fn dummy() -> Self {
WindowId(0)
}
}
impl From<WindowId> for u64 {
fn from(window_id: WindowId) -> Self {
window_id.0
}
}
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self(raw_id)
}
}
/// The behavior of cursor grabbing.
///
/// Use this enum with [`Window::set_cursor_grab`] to grab the cursor.
///
/// [`Window::set_cursor_grab`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_cursor_grab
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CursorGrabMode {
/// No grabbing of the cursor is performed.
None,
/// The cursor is confined to the window area.
///
/// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you
/// want to do so.
///
/// ## Platform-specific
///
/// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
///
/// [`ExternalError::NotSupported`]: https://docs.rs/winit/latest/winit/error/enum.ExternalError.html#variant.NotSupported
Confined,
/// The cursor is locked inside the window area to the certain position.
///
/// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you
/// want to do so.
///
/// ## Platform-specific
///
/// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
/// - **iOS / Android / Orbital:** Always returns an [`ExternalError::NotSupported`].
///
/// [`ExternalError::NotSupported`]: https://docs.rs/winit/latest/winit/error/enum.ExternalError.html#variant.NotSupported
Locked,
}
/// Defines the orientation that a window resize will be performed.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ResizeDirection {
East,
North,
NorthEast,
NorthWest,
South,
SouthEast,
SouthWest,
West,
}
impl From<ResizeDirection> for CursorIcon {
fn from(direction: ResizeDirection) -> Self {
use ResizeDirection::*;
match direction {
East => CursorIcon::EResize,
North => CursorIcon::NResize,
NorthEast => CursorIcon::NeResize,
NorthWest => CursorIcon::NwResize,
South => CursorIcon::SResize,
SouthEast => CursorIcon::SeResize,
SouthWest => CursorIcon::SwResize,
West => CursorIcon::WResize,
}
}
}
/// The theme variant to use.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Theme {
/// Use the light variant.
Light,
/// Use the dark variant.
Dark,
}
/// ## Platform-specific
///
/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`].
///
/// [`Critical`]: Self::Critical
/// [`Informational`]: Self::Informational
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum UserAttentionType {
/// ## Platform-specific
///
/// - **macOS:** Bounces the dock icon until the application is in focus.
/// - **Windows:** Flashes both the window and the taskbar button until the application is in focus.
Critical,
/// ## Platform-specific
///
/// - **macOS:** Bounces the dock icon once.
/// - **Windows:** Flashes the taskbar button until the application is in focus.
#[default]
Informational,
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WindowButtons: u32 {
const CLOSE = 1 << 0;
const MINIMIZE = 1 << 1;
const MAXIMIZE = 1 << 2;
}
}
/// A window level groups windows with respect to their z-position.
///
/// The relative ordering between windows in different window levels is fixed.
/// The z-order of a window within the same window level may change dynamically on user interaction.
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Wayland:** Unsupported.
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
pub enum WindowLevel {
/// The window will always be below normal windows.
///
/// This is useful for a widget-based app.
AlwaysOnBottom,
/// The default.
#[default]
Normal,
/// The window will always be on top of normal windows.
AlwaysOnTop,
}
/// Generic IME purposes for use in [`Window::set_ime_purpose`].
///
/// The purpose may improve UX by optimizing the IME for the specific use case,
/// if winit can express the purpose to the platform and the platform reacts accordingly.
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported.
///
/// [`Window::set_ime_purpose`]: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_ime_purpose
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub enum ImePurpose {
/// No special hints for the IME (default).
Normal,
/// The IME is used for password input.
Password,
/// The IME is used to input into a terminal.
///
/// For example, that could alter OSK on Wayland to show extra buttons.
Terminal,
}
impl Default for ImePurpose {
fn default() -> Self {
Self::Normal
}
}
/// An stringly-typed token used to activate the [`Window`].
///
/// [`Window`]: https://docs.rs/winit/latest/winit/window/struct.Window.html
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ActivationToken {
pub(crate) token: String,
}
impl ActivationToken {
/// Create a new [`ActivationToken`].
pub fn new(token: String) -> Self {
Self { token }
}
/// Get the underlying token.
pub fn token(&self) -> &str {
&self.token
}
/// Convert into the underlying token.
pub fn into_token(self) -> String {
self.token
}
}

261
winit/Cargo.toml Normal file
View File

@@ -0,0 +1,261 @@
[package]
name = "winit"
version = "0.29.10"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
keywords = ["windowing"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version = "1.70.0"
[package.metadata.docs.rs]
features = [
"rwh_04",
"rwh_05",
"rwh_06",
"serde",
"mint",
# Enabled to get docs to compile
"android-native-activity",
]
default-target = "x86_64-unknown-linux-gnu"
# These are all tested in CI
targets = [
# Windows
"i686-pc-windows-msvc",
"x86_64-pc-windows-msvc",
# macOS
"x86_64-apple-darwin",
# Unix (X11 & Wayland)
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# iOS
"x86_64-apple-ios",
# Android
"aarch64-linux-android",
# Web
"wasm32-unknown-unknown",
]
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
android-native-activity = ["android-activity/native-activity"]
android-game-activity = ["android-activity/game-activity"]
mint = ["winit-core/mint"]
serde = ["dep:serde", "winit-core/serde", "cursor-icon/serde", "smol_str/serde"]
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
[build-dependencies]
cfg_aliases.workspace = true
[dependencies]
bitflags.workspace = true
cursor-icon.workspace = true
log = "0.4"
once_cell = "1.12"
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
serde = { workspace = true, optional = true }
smol_str.workspace = true
winit-core.workspace = true
[dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] }
simple_logger = { version = "4.2.0", default_features = false }
winit = { path = ".", features = ["rwh_05"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
softbuffer = { version = "0.3.0", default-features = false, features = ["x11", "x11-dlopen", "wayland", "wayland-dlopen"] }
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.5.0"
ndk = { version = "0.8.0", default-features = false }
ndk-sys = "0.5.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
objc2 = "0.5.0"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1"
[target.'cfg(target_os = "macos")'.dependencies.icrate]
version = "0.1.0"
features = [
"dispatch",
"Foundation",
"Foundation_NSArray",
"Foundation_NSAttributedString",
"Foundation_NSMutableAttributedString",
"Foundation_NSData",
"Foundation_NSDictionary",
"Foundation_NSString",
"Foundation_NSProcessInfo",
"Foundation_NSThread",
"Foundation_NSNumber",
"AppKit",
"AppKit_NSAppearance",
"AppKit_NSApplication",
"AppKit_NSBitmapImageRep",
"AppKit_NSButton",
"AppKit_NSColor",
"AppKit_NSControl",
"AppKit_NSCursor",
"AppKit_NSEvent",
"AppKit_NSGraphicsContext",
"AppKit_NSImage",
"AppKit_NSImageRep",
"AppKit_NSMenu",
"AppKit_NSMenuItem",
"AppKit_NSPasteboard",
"AppKit_NSResponder",
"AppKit_NSScreen",
"AppKit_NSTextInputContext",
"AppKit_NSView",
"AppKit_NSWindow",
"AppKit_NSWindowTabGroup",
]
[target.'cfg(target_os = "ios")'.dependencies.icrate]
version = "0.1.0"
features = [
"dispatch",
"Foundation",
"Foundation_NSArray",
"Foundation_NSString",
"Foundation_NSProcessInfo",
"Foundation_NSThread",
"Foundation_NSSet",
]
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.48"
features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Media",
"Win32_System_Com_StructuredStorage",
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
"Win32_System_WindowsProgramming",
"Win32_UI_Accessibility",
"Win32_UI_Controls",
"Win32_UI_HiDpi",
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Input_Pointer",
"Win32_UI_Input_Touch",
"Win32_UI_Shell",
"Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging",
]
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
ahash = { version = "0.8.3", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.12.3"
libc = "0.2.64"
memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
x11-dl = { version = "2.18.5", optional = true }
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
xkbcommon-dl = "0.4.0"
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.4.1"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.64"
features = [
'AbortController',
'AbortSignal',
'Blob',
'console',
'CssStyleDeclaration',
'Document',
'DomException',
'DomRect',
'DomRectReadOnly',
'Element',
'Event',
'EventTarget',
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'HtmlImageElement',
'ImageBitmap',
'ImageBitmapOptions',
'ImageBitmapRenderingContext',
'ImageData',
'IntersectionObserver',
'IntersectionObserverEntry',
'KeyboardEvent',
'MediaQueryList',
'MessageChannel',
'MessagePort',
'Node',
'PageTransitionEvent',
'PointerEvent',
'PremultiplyAlpha',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'VisibilityState',
'Window',
'WheelEvent',
'Url',
]
[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.64"
pin-project = "1"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-time.workspace = true
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
atomic-waker = "1"
concurrent-queue = { version = "2", default-features = false }
[target.'cfg(target_family = "wasm")'.dev-dependencies]
console_log = "1"
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
[[example]]
doc-scrape-examples = true
name = "window"

1
winit/README.md Symbolic link
View File

@@ -0,0 +1 @@
../README.md

24
winit/build.rs Normal file
View File

@@ -0,0 +1,24 @@
use cfg_aliases::cfg_aliases;
fn main() {
// The script doesn't depend on our code
println!("cargo:rerun-if-changed=build.rs");
// Setup cfg aliases
cfg_aliases! {
// Systems.
android_platform: { target_os = "android" },
web_platform: { all(target_family = "wasm", target_os = "unknown") },
macos_platform: { target_os = "macos" },
ios_platform: { target_os = "ios" },
windows_platform: { target_os = "windows" },
apple: { any(target_os = "ios", target_os = "macos") },
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
redox: { target_os = "redox" },
// Native displays.
x11_platform: { all(feature = "x11", free_unix, not(redox)) },
wayland_platform: { all(feature = "wayland", free_unix, not(redox)) },
orbital_platform: { redox },
}
}

View File

@@ -1,3 +1,10 @@
#[cfg(all(
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
))]
#[path = "util/fill.rs"]
mod fill;
#[cfg(all( #[cfg(all(
feature = "rwh_06", feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform) any(x11_platform, macos_platform, windows_platform)
@@ -6,57 +13,58 @@
fn main() -> Result<(), impl std::error::Error> { fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap; use std::collections::HashMap;
use winit::dpi::{LogicalPosition, LogicalSize, Position}; use winit::{
use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; dpi::{LogicalPosition, LogicalSize, Position},
use winit::event_loop::{ActiveEventLoop, EventLoop}; event::{ElementState, Event, KeyEvent, WindowEvent},
use winit::raw_window_handle::HasRawWindowHandle; event_loop::{EventLoop, EventLoopWindowTarget},
use winit::window::Window; raw_window_handle::HasRawWindowHandle,
window::{Window, WindowId},
};
#[path = "util/fill.rs"] fn spawn_child_window(
mod fill; parent: &Window,
event_loop: &EventLoopWindowTarget,
fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window { windows: &mut HashMap<WindowId, Window>,
) {
let parent = parent.raw_window_handle().unwrap(); let parent = parent.raw_window_handle().unwrap();
let mut window_attributes = Window::default_attributes() let mut builder = Window::builder()
.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)))
.with_visible(true); .with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window. // `with_parent_window` is unsafe. Parent window must be a valid window.
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) }; builder = unsafe { builder.with_parent_window(Some(parent)) };
let child_window = builder.build(event_loop).unwrap();
event_loop.create_window(window_attributes).unwrap() let id = child_window.id();
windows.insert(id, child_window);
println!("child window created with id: {id:?}");
} }
let mut windows = HashMap::new(); let mut windows = HashMap::new();
let event_loop: EventLoop<()> = EventLoop::new().unwrap(); let event_loop: EventLoop<()> = EventLoop::new().unwrap();
let mut parent_window_id = None; let parent_window = Window::builder()
event_loop.run(move |event: Event<()>, event_loop| {
match event {
Event::Resumed => {
let attributes = Window::default_attributes()
.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))
let window = event_loop.create_window(attributes).unwrap(); .build(&event_loop)
.unwrap();
parent_window_id = Some(window.id()); println!("parent window: {parent_window:?})");
println!("Parent window id: {parent_window_id:?})"); event_loop.run(move |event: Event<()>, elwt| {
windows.insert(window.id(), window); if let Event::WindowEvent { event, window_id } = event {
} match event {
Event::WindowEvent { window_id, event } => match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
windows.clear(); windows.clear();
event_loop.exit(); elwt.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
// by some key inputs. // by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the parent window, // the child windows are always placed at (0, 0) with size (200, 200) in the parent window,
// so we also can see this log when we move the cursor around (200, 200) in parent window. // so we also can see this log when we move the cursor arround (200, 200) in parent window.
println!("cursor entered in the window {window_id:?}"); println!("cursor entered in the window {window_id:?}");
} }
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
@@ -67,11 +75,7 @@ fn main() -> Result<(), impl std::error::Error> {
}, },
.. ..
} => { } => {
let parent_window = windows.get(&parent_window_id.unwrap()).unwrap(); spawn_child_window(&parent_window, elwt, &mut windows);
let child_window = spawn_child_window(parent_window, event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
windows.insert(child_id, child_window);
} }
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) { if let Some(window) = windows.get(&window_id) {
@@ -79,16 +83,15 @@ fn main() -> Result<(), impl std::error::Error> {
} }
} }
_ => (), _ => (),
}, }
_ => (),
} }
}) })
} }
#[cfg(all( #[cfg(not(all(
feature = "rwh_06", feature = "rwh_06",
not(any(x11_platform, macos_platform, windows_platform)) 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, with the `rwh_06` feature enabled.");
} }

View File

@@ -37,14 +37,17 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Press 'Esc' to close the window."); println!("Press 'Esc' to close the window.");
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
.build(&event_loop)
.unwrap();
let mut mode = Mode::Wait; let mut mode = Mode::Wait;
let mut request_redraw = false; let mut request_redraw = false;
let mut wait_cancelled = false; let mut wait_cancelled = false;
let mut close_requested = false; let mut close_requested = false;
let mut window = None; event_loop.run(move |event, elwt| {
event_loop.run(move |event, event_loop| {
use winit::event::StartCause; use winit::event::StartCause;
println!("{event:?}"); println!("{event:?}");
match event { match event {
@@ -54,12 +57,6 @@ fn main() -> Result<(), impl std::error::Error> {
_ => false, _ => false,
} }
} }
Event::Resumed => {
let window_attributes = Window::default_attributes().with_title(
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
);
window = Some(event_loop.create_window(window_attributes).unwrap());
}
Event::WindowEvent { event, .. } => match event { Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
close_requested = true; close_requested = true;
@@ -73,7 +70,7 @@ fn main() -> Result<(), impl std::error::Error> {
}, },
.. ..
} => match key.as_ref() { } => match key.as_ref() {
// WARNING: Consider using `key_without_modifiers()` if available on your platform. // WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example // See the `key_binding` example
Key::Character("1") => { Key::Character("1") => {
mode = Mode::Wait; mode = Mode::Wait;
@@ -97,34 +94,32 @@ fn main() -> Result<(), impl std::error::Error> {
_ => (), _ => (),
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
let window = window.as_ref().unwrap(); fill::fill_window(&window);
window.pre_present_notify();
fill::fill_window(window);
} }
_ => (), _ => (),
}, },
Event::AboutToWait => { Event::AboutToWait => {
if request_redraw && !wait_cancelled && !close_requested { if request_redraw && !wait_cancelled && !close_requested {
window.as_ref().unwrap().request_redraw(); window.request_redraw();
} }
match mode { match mode {
Mode::Wait => event_loop.set_control_flow(ControlFlow::Wait), Mode::Wait => elwt.set_control_flow(ControlFlow::Wait),
Mode::WaitUntil => { Mode::WaitUntil => {
if !wait_cancelled { if !wait_cancelled {
event_loop.set_control_flow(ControlFlow::WaitUntil( elwt.set_control_flow(ControlFlow::WaitUntil(
time::Instant::now() + WAIT_TIME, time::Instant::now() + WAIT_TIME,
)); ));
} }
} }
Mode::Poll => { Mode::Poll => {
thread::sleep(POLL_SLEEP_TIME); thread::sleep(POLL_SLEEP_TIME);
event_loop.set_control_flow(ControlFlow::Poll); elwt.set_control_flow(ControlFlow::Poll);
} }
}; };
if close_requested { if close_requested {
event_loop.exit(); elwt.exit();
} }
} }
_ => (), _ => (),

88
winit/examples/cursor.rs Normal file
View File

@@ -0,0 +1,88 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
window::{CursorIcon, Window},
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder().build(&event_loop).unwrap();
window.set_title("A fantastic window!");
let mut cursor_idx = 0;
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
..
},
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1;
} else {
cursor_idx = 0;
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
WindowEvent::CloseRequested => {
elwt.exit();
}
_ => (),
}
}
})
}
const CURSORS: &[CursorIcon] = &[
CursorIcon::Default,
CursorIcon::Crosshair,
CursorIcon::Pointer,
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,
];

View File

@@ -0,0 +1,73 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState, NamedKey},
window::{CursorGrabMode, Window},
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
.build(&event_loop)
.unwrap();
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, elwt| match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
..
},
..
} => {
let result = match key {
Key::Named(NamedKey::Escape) => {
elwt.exit();
Ok(())
}
Key::Character(ch) => match ch.to_lowercase().as_str() {
"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_key());
Ok(())
}
_ => Ok(()),
},
_ => Ok(()),
};
if let Err(err) = result {
println!("error: {err}");
}
}
WindowEvent::ModifiersChanged(new) => modifiers = new.state(),
WindowEvent::RedrawRequested => fill::fill_window(&window),
_ => (),
},
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

@@ -0,0 +1,143 @@
#![allow(clippy::single_match, clippy::disallowed_methods)]
#[cfg(not(web_platform))]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{EventLoop, EventLoopWindowTarget},
keyboard::Key,
window::{CursorIcon, CustomCursor, Window},
};
#[cfg(web_platform)]
use {
std::sync::atomic::{AtomicU64, Ordering},
std::time::Duration,
winit::platform::web::CustomCursorExtWebSys,
};
#[cfg(web_platform)]
static COUNTER: AtomicU64 = AtomicU64::new(0);
fn decode_cursor(bytes: &[u8], window_target: &EventLoopWindowTarget) -> CustomCursor {
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
let samples = img.into_flat_samples();
let (_, w, h) = samples.extents();
let (w, h) = (w as u16, h as u16);
let builder = CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap();
builder.build(window_target)
}
#[cfg(not(web_platform))]
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
#[cfg(not(web_platform))]
SimpleLogger::new()
.with_level(log::LevelFilter::Info)
.init()
.unwrap();
#[cfg(web_platform)]
console_log::init_with_level(log::Level::Debug).unwrap();
let event_loop = EventLoop::new().unwrap();
let builder = Window::builder().with_title("A fantastic window!");
#[cfg(web_platform)]
let builder = {
use winit::platform::web::WindowBuilderExtWebSys;
builder.with_append(true)
};
let window = builder.build(&event_loop).unwrap();
let mut cursor_idx = 0;
let mut cursor_visible = true;
let custom_cursors = [
decode_cursor(include_bytes!("data/cross.png"), &event_loop),
decode_cursor(include_bytes!("data/cross2.png"), &event_loop),
decode_cursor(include_bytes!("data/gradient.png"), &event_loop),
];
event_loop.run(move |event, _elwt| match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key: key,
..
},
..
} => match key.as_ref() {
Key::Character("1") => {
log::debug!("Setting cursor to {:?}", cursor_idx);
window.set_cursor(custom_cursors[cursor_idx].clone());
cursor_idx = (cursor_idx + 1) % 3;
}
Key::Character("2") => {
log::debug!("Setting cursor icon to default");
window.set_cursor(CursorIcon::default());
}
Key::Character("3") => {
cursor_visible = !cursor_visible;
log::debug!("Setting cursor visibility to {:?}", cursor_visible);
window.set_cursor_visible(cursor_visible);
}
#[cfg(web_platform)]
Key::Character("4") => {
log::debug!("Setting cursor to a random image from an URL");
window.set_cursor(
CustomCursor::from_url(
format!(
"https://picsum.photos/128?random={}",
COUNTER.fetch_add(1, Ordering::Relaxed)
),
64,
64,
)
.build(_elwt),
);
}
#[cfg(web_platform)]
Key::Character("5") => {
log::debug!("Setting cursor to an animation");
window.set_cursor(
CustomCursor::from_animation(
Duration::from_secs(3),
vec![
custom_cursors[0].clone(),
custom_cursors[1].clone(),
CustomCursor::from_url(
format!(
"https://picsum.photos/128?random={}",
COUNTER.fetch_add(1, Ordering::Relaxed)
),
64,
64,
)
.build(_elwt),
],
)
.unwrap()
.build(_elwt),
);
}
_ => {}
},
WindowEvent::RedrawRequested => {
#[cfg(not(web_platform))]
fill::fill_window(&window);
}
WindowEvent::CloseRequested => {
#[cfg(not(web_platform))]
_elwt.exit();
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
_ => {}
})
}

View File

@@ -0,0 +1,60 @@
#![allow(clippy::single_match)]
#[cfg(not(web_platform))]
fn main() -> Result<(), impl std::error::Error> {
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
#[derive(Debug, Clone, Copy)]
enum CustomEvent {
Timer,
}
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
let window = Window::builder()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
// `EventLoopProxy` allows you to dispatch custom events to the main Winit event
// loop from any thread.
let event_loop_proxy = event_loop.create_proxy();
std::thread::spawn(move || {
// Wake up the `event_loop` once every second and dispatch a custom event
// from a different thread.
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
event_loop_proxy.send_event(CustomEvent::Timer).ok();
}
});
event_loop.run(move |event, elwt| match event {
Event::UserEvent(event) => println!("user event: {event:?}"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => elwt.exit(),
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
fill::fill_window(&window);
}
_ => (),
})
}
#[cfg(web_platform)]
fn main() {
panic!("This example is not supported on web.");
}

View File

Before

Width:  |  Height:  |  Size: 159 B

After

Width:  |  Height:  |  Size: 159 B

View File

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 129 B

View File

Before

Width:  |  Height:  |  Size: 229 B

After

Width:  |  Height:  |  Size: 229 B

View File

@@ -0,0 +1,105 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::{Window, WindowId},
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window_1 = Window::builder().build(&event_loop).unwrap();
let window_2 = Window::builder().build(&event_loop).unwrap();
let mut switched = false;
let mut entered_id = window_2.id();
let mut cursor_location = None;
event_loop.run(move |event, elwt| 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 => elwt.exit(),
WindowEvent::CursorMoved { position, .. } => cursor_location = Some(position),
WindowEvent::MouseInput { state, button, .. } => {
let window = if (window_id == window_1.id() && switched)
|| (window_id == window_2.id() && !switched)
{
&window_2
} else {
&window_1
};
match (button, state) {
(MouseButton::Left, ElementState::Pressed) => window.drag_window().unwrap(),
(MouseButton::Right, ElementState::Released) => {
if let Some(position) = cursor_location {
window.show_window_menu(position);
}
}
_ => (),
}
}
WindowEvent::CursorEntered { .. } => {
entered_id = window_id;
name_windows(entered_id, switched, &window_1, &window_2)
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Released,
logical_key: Key::Character(c),
..
},
..
} => match c.as_str() {
"x" => {
switched = !switched;
name_windows(entered_id, switched, &window_1, &window_2);
println!("Switched!")
}
"d" => {
let window = if (window_id == window_1.id() && switched)
|| (window_id == window_2.id() && !switched)
{
&window_2
} else {
&window_1
};
window.set_decorations(!window.is_decorated());
}
_ => (),
},
WindowEvent::RedrawRequested => {
if window_id == window_1.id() {
fill::fill_window(&window_1);
} else if window_id == window_2.id() {
fill::fill_window(&window_2);
}
}
_ => (),
},
_ => (),
})
}
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");
}

56
winit/examples/focus.rs Normal file
View File

@@ -0,0 +1,56 @@
#![allow(clippy::single_match)]
//! Example for focusing a window.
use simple_logger::SimpleLogger;
#[cfg(not(web_platform))]
use std::time;
#[cfg(web_platform)]
use web_time as time;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::EventLoop,
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop)
.unwrap();
let mut deadline = time::Instant::now() + time::Duration::from_secs(3);
event_loop.run(move |event, elwt| {
match event {
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
// Timeout reached; focus the window.
println!("Re-focusing the window.");
deadline += time::Duration::from_secs(3);
window.focus_window();
}
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::RedrawRequested => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
_ => (),
}
elwt.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(deadline));
})
}

View File

@@ -0,0 +1,163 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::dpi::LogicalSize;
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::EventLoop;
use winit::keyboard::{Key, NamedKey};
use winit::window::{Fullscreen, Window};
#[cfg(target_os = "macos")]
use winit::platform::macos::WindowExtMacOS;
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let mut decorations = true;
let mut minimized = false;
let mut with_min_size = false;
let mut with_max_size = false;
let window = Window::builder()
.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");
#[cfg(target_os = "macos")]
println!("- C\tToggle simple fullscreen 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");
println!("- I\tToggle mIn size limit");
println!("- A\tToggle mAx size limit");
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
} => match key {
Key::Named(NamedKey::Escape) => elwt.exit(),
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
Key::Character(ch) => match ch.to_lowercase().as_str() {
"f" | "b" if window.fullscreen().is_some() => {
window.set_fullscreen(None);
}
"f" => {
let fullscreen = Some(Fullscreen::Exclusive(mode.clone()));
println!("Setting mode: {fullscreen:?}");
window.set_fullscreen(fullscreen);
}
"b" => {
let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone())));
println!("Setting mode: {fullscreen:?}");
window.set_fullscreen(fullscreen);
}
#[cfg(target_os = "macos")]
"c" => {
window.set_simple_fullscreen(!window.simple_fullscreen());
}
"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}");
}
"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}");
}
"d" => {
decorations = !decorations;
window.set_decorations(decorations);
}
"x" => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
"z" => {
minimized = !minimized;
window.set_minimized(minimized);
}
"i" => {
with_min_size = !with_min_size;
let min_size = if with_min_size {
Some(LogicalSize::new(100, 100))
} else {
None
};
window.set_min_inner_size(min_size);
eprintln!(
"Min: {with_min_size}: {min_size:?} => {:?}",
window.inner_size()
);
}
"a" => {
with_max_size = !with_max_size;
let max_size = if with_max_size {
Some(LogicalSize::new(200, 200))
} else {
None
};
window.set_max_inner_size(max_size);
eprintln!(
"Max: {with_max_size}: {max_size:?} => {:?}",
window.inner_size()
);
}
_ => (),
},
_ => (),
},
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
}
})
}

View File

@@ -0,0 +1,86 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_title("Your faithful window")
.build(&event_loop)
.unwrap();
let mut close_requested = false;
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = 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 {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
..
},
..
} => {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
match key.as_ref() {
Key::Character("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.
elwt.exit();
}
}
Key::Character("n") => {
if close_requested {
println!("Your window will continue to stay by your side.");
close_requested = false;
}
}
_ => (),
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
}
})
}

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

94
winit/examples/ime.rs Normal file
View File

@@ -0,0 +1,94 @@
#![allow(clippy::single_match)]
use log::LevelFilter;
use simple_logger::SimpleLogger;
use winit::{
dpi::{PhysicalPosition, PhysicalSize},
event::{ElementState, Event, Ime, WindowEvent},
event_loop::EventLoop,
keyboard::NamedKey,
window::{ImePurpose, Window},
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
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().unwrap();
let window = Window::builder()
.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, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::CursorMoved { position, .. } => {
cursor_position = position;
}
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_cursor_area(ime_pos, PhysicalSize::new(10, 10));
}
}
WindowEvent::Ime(event) => {
println!("{event:?}");
may_show_ime = event != Ime::Disabled;
if may_show_ime {
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
}
}
WindowEvent::KeyboardInput { event, .. } => {
println!("key: {event:?}");
if event.state == ElementState::Pressed && event.logical_key == NamedKey::F2 {
ime_allowed = !ime_allowed;
window.set_ime_allowed(ime_allowed);
println!("\nIME allowed: {ime_allowed}\n");
}
if event.state == ElementState::Pressed && event.logical_key == NamedKey::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");
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
}
})
}

View File

@@ -0,0 +1,62 @@
#![allow(clippy::single_match)]
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState},
// WARNING: This is not available on all platforms (for example on the web).
platform::modifier_supplement::KeyEventExtModifierSupplement,
window::Window,
};
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
fn main() {
println!("This example is not supported on this platform");
}
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
fn main() -> Result<(), impl std::error::Error> {
#[path = "util/fill.rs"]
mod fill;
simple_logger::SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_inner_size(LogicalSize::new(400.0, 200.0))
.build(&event_loop)
.unwrap();
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::ModifiersChanged(new) => {
modifiers = new.state();
}
WindowEvent::KeyboardInput { event, .. } => {
if event.state == ElementState::Pressed && !event.repeat {
match event.key_without_modifiers().as_ref() {
Key::Character("1") => {
if modifiers.shift_key() {
println!("Shift + 1 | logical_key: {:?}", event.logical_key);
} else {
println!("1");
}
}
_ => (),
}
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
};
})
}

View File

@@ -0,0 +1,58 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::monitor::MonitorHandle;
use winit::{event_loop::EventLoop, window::Window};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder().build(&event_loop).unwrap();
if let Some(mon) = window.primary_monitor() {
print_info("Primary output", mon);
}
for mon in window.available_monitors() {
if Some(&mon) == window.primary_monitor().as_ref() {
continue;
}
println!();
print_info("Output", mon);
}
}
fn print_info(intro: &str, monitor: MonitorHandle) {
if let Some(name) = monitor.name() {
println!("{intro}: {name}");
} else {
println!("{intro}: [no name]");
}
let PhysicalSize { width, height } = monitor.size();
print!(" Current mode: {width}x{height}");
if let Some(m_hz) = monitor.refresh_rate_millihertz() {
println!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000);
} else {
println!();
}
let PhysicalPosition { x, y } = monitor.position();
println!(" Position: {x},{y}");
println!(" Scale factor: {}", monitor.scale_factor());
println!(" Available modes (width x height x bit-depth):");
for mode in monitor.video_modes() {
let PhysicalSize { width, height } = mode.size();
let bits = mode.bit_depth();
let m_hz = mode.refresh_rate_millihertz();
println!(
" {width}x{height}x{bits} @ {}.{} Hz",
m_hz / 1000,
m_hz % 1000
);
}
}

View File

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

View File

@@ -0,0 +1,211 @@
#![allow(clippy::single_match)]
#[cfg(not(web_platform))]
fn main() -> Result<(), impl std::error::Error> {
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState, NamedKey},
window::{CursorGrabMode, CursorIcon, Fullscreen, Window, WindowLevel},
};
const WINDOW_COUNT: usize = 3;
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
for _ in 0..WINDOW_COUNT {
let window = Window::builder()
.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);
let mut modifiers = ModifiersState::default();
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()
);
}
}
WindowEvent::ModifiersChanged(new) => {
modifiers = new.state();
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Released,
logical_key: key,
..
},
..
} => {
use NamedKey::{ArrowLeft, ArrowRight};
window.set_title(&format!("{key:?}"));
let state = !modifiers.shift_key();
match key {
// Cycle through video modes
Key::Named(ArrowRight) | Key::Named(ArrowLeft) => {
if key == ArrowLeft {
video_mode_id = video_mode_id.saturating_sub(1);
} else if key == ArrowRight {
video_mode_id = (video_modes.len() - 1).min(video_mode_id + 1);
}
println!("Picking video mode: {}", video_modes[video_mode_id]);
}
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
Key::Character(ch) => match ch.to_lowercase().as_str() {
"1" => window.set_window_level(WindowLevel::AlwaysOnTop),
"2" => window.set_window_level(WindowLevel::AlwaysOnBottom),
"3" => window.set_window_level(WindowLevel::Normal),
"c" => window.set_cursor(match state {
true => CursorIcon::Progress,
false => CursorIcon::Default,
}),
"d" => window.set_decorations(!state),
"f" => window.set_fullscreen(match (state, modifiers.alt_key()) {
(true, false) => Some(Fullscreen::Borderless(None)),
(true, true) => Some(Fullscreen::Exclusive(
video_modes[video_mode_id].clone(),
)),
(false, _) => None,
}),
ch @ ("g" | "l") => {
let mode = match (ch, state) {
("l", true) => CursorGrabMode::Locked,
("g", true) => CursorGrabMode::Confined,
(_, _) => CursorGrabMode::None,
};
if let Err(err) = window.set_cursor_grab(mode) {
println!("error: {err}");
}
}
"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());
}
"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" => {
let _ = window.request_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 50,
WINDOW_SIZE.height + 50,
),
false => WINDOW_SIZE,
});
}
"k" => window.set_min_inner_size(match state {
true => Some(PhysicalSize::new(
WINDOW_SIZE.width - 100,
WINDOW_SIZE.height - 100,
)),
false => None,
}),
"o" => window.set_max_inner_size(match state {
true => Some(PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
)),
false => None,
}),
"w" => {
if let Size::Physical(size) = WINDOW_SIZE.into() {
window
.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, elwt| {
if window_senders.is_empty() {
elwt.exit()
}
match event {
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested
| WindowEvent::Destroyed
| WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Released,
logical_key: Key::Named(NamedKey::Escape),
..
},
..
} => {
window_senders.remove(&window_id);
}
_ => {
if let Some(tx) = window_senders.get(&window_id) {
tx.send(event).unwrap();
}
}
},
_ => {}
}
})
}
#[cfg(web_platform)]
fn main() {
panic!("Example not supported on Web");
}

View File

@@ -0,0 +1,64 @@
#![allow(clippy::single_match)]
use std::collections::HashMap;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, NamedKey},
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
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, elwt| {
if let Event::WindowEvent { event, window_id } = event {
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() {
elwt.exit();
}
}
WindowEvent::KeyboardInput {
event,
is_synthetic: false,
..
} if event.state == ElementState::Pressed => match event.logical_key {
Key::Named(NamedKey::Escape) => elwt.exit(),
Key::Character(c) if c == "n" || c == "N" => {
let window = Window::new(elwt).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
}
_ => (),
},
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
}
})
}

View File

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

View File

@@ -0,0 +1,59 @@
#![allow(clippy::single_match)]
#[cfg(not(web_platform))]
fn main() -> Result<(), impl std::error::Error> {
use std::{sync::Arc, thread, time};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = {
let window = Window::builder()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
Arc::new(window)
};
thread::spawn({
let window = window.clone();
move || loop {
thread::sleep(time::Duration::from_secs(1));
window.request_redraw();
}
});
event_loop.run(move |event, elwt| {
println!("{event:?}");
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => elwt.exit(),
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}
})
}
#[cfg(web_platform)]
fn main() {
unimplemented!() // `Window` can't be sent between threads
}

View File

@@ -0,0 +1,54 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let mut resizable = false;
let window = Window::builder()
.with_title("Hit space to toggle resizability.")
.with_inner_size(LogicalSize::new(600.0, 300.0))
.with_min_inner_size(LogicalSize::new(400.0, 200.0))
.with_max_inner_size(LogicalSize::new(800.0, 400.0))
.with_resizable(resizable)
.build(&event_loop)
.unwrap();
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key: PhysicalKey::Code(KeyCode::Space),
state: ElementState::Released,
..
},
..
} => {
resizable = !resizable;
println!("Resizable: {resizable}");
window.set_resizable(resizable);
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
};
})
}

View File

@@ -0,0 +1,115 @@
//! Demonstrates the use of startup notifications on Linux.
#[cfg(any(x11_platform, wayland_platform))]
#[path = "./util/fill.rs"]
mod fill;
#[cfg(any(x11_platform, wayland_platform))]
mod example {
use std::collections::HashMap;
use std::rc::Rc;
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::EventLoop;
use winit::platform::startup_notify::{
EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify,
};
use winit::window::{Window, WindowId};
pub(super) fn main() -> Result<(), impl std::error::Error> {
// Create the event loop and get the activation token.
let event_loop = EventLoop::new().unwrap();
let mut current_token = match event_loop.read_token_from_env() {
Some(token) => Some(token),
None => {
println!("No startup notification token found in environment.");
None
}
};
let mut windows: HashMap<WindowId, Rc<Window>> = HashMap::new();
let mut counter = 0;
let mut create_first_window = false;
event_loop.run(move |event, elwt| {
match event {
Event::Resumed => create_first_window = true,
Event::WindowEvent { window_id, event } => match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key,
state: ElementState::Pressed,
..
},
..
} => {
if logical_key == "n" {
if let Some(window) = windows.get(&window_id) {
// Request a new activation token on this window.
// Once we get it we will use it to create a window.
window
.request_activation_token()
.expect("Failed to request activation token.");
}
}
}
WindowEvent::CloseRequested => {
// Remove the window from the map.
windows.remove(&window_id);
if windows.is_empty() {
elwt.exit();
return;
}
}
WindowEvent::ActivationTokenDone { token, .. } => {
current_token = Some(token);
}
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
super::fill::fill_window(window);
}
}
_ => {}
},
_ => (),
}
// See if we've passed the deadline.
if current_token.is_some() || create_first_window {
// Create the initial window.
let window = {
let mut builder = Window::builder().with_title(format!("Window {}", counter));
if let Some(token) = current_token.take() {
println!("Creating a window with token {token:?}");
builder = builder.with_activation_token(token);
}
Rc::new(builder.build(elwt).unwrap())
};
// Add the window to the map.
windows.insert(window.id(), window.clone());
counter += 1;
create_first_window = false;
}
})
}
}
#[cfg(any(x11_platform, wayland_platform))]
fn main() -> Result<(), impl std::error::Error> {
example::main()
}
#[cfg(not(any(x11_platform, wayland_platform)))]
fn main() {
println!("This example is only supported on X11 and Wayland platforms.");
}

68
winit/examples/theme.rs Normal file
View File

@@ -0,0 +1,68 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::{Theme, Window},
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.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, elwt| {
if let Event::WindowEvent { window_id, event } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::ThemeChanged(theme) if window_id == window.id() => {
println!("Theme is changed: {theme:?}")
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
} => match key.as_ref() {
Key::Character("A" | "a") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(None);
}
Key::Character("L" | "l") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Light));
}
Key::Character("D" | "d") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Dark));
}
_ => (),
},
WindowEvent::RedrawRequested => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}
}
})
}

54
winit/examples/timer.rs Normal file
View File

@@ -0,0 +1,54 @@
#![allow(clippy::single_match)]
use std::time::Duration;
#[cfg(not(web_platform))]
use std::time::Instant;
#[cfg(web_platform)]
use web_time::Instant;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let timer_length = Duration::new(1, 0);
event_loop.run(move |event, elwt| {
println!("{event:?}");
match event {
Event::NewEvents(StartCause::Init) => {
elwt.set_control_flow(ControlFlow::WaitUntil(Instant::now() + timer_length));
}
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
elwt.set_control_flow(ControlFlow::WaitUntil(Instant::now() + timer_length));
println!("\nTimer\n");
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => elwt.exit(),
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
fill::fill_window(&window);
}
_ => (),
}
})
}

View File

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

View File

@@ -0,0 +1,38 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_decorations(false)
.with_transparent(true)
.build(&event_loop)
.unwrap();
window.set_title("A fantastic window!");
event_loop.run(move |event, elwt| {
println!("{event:?}");
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
}
})
}

View File

@@ -28,7 +28,7 @@ mod platform {
// ManuallyDrop to prevent destructors from running. // ManuallyDrop to prevent destructors from running.
// //
// A static, thread-local map of graphics contexts to open windows. // A static, thread-local map of graphics contexts to open windows.
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) }; static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
} }
/// The graphics context used to draw to a window. /// The graphics context used to draw to a window.

View File

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

147
winit/examples/web.rs Normal file
View File

@@ -0,0 +1,147 @@
#![allow(clippy::disallowed_methods, clippy::single_match)]
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::{Fullscreen, Window},
};
pub fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap();
let builder = Window::builder().with_title("A fantastic window!");
#[cfg(wasm_platform)]
let builder = {
use winit::platform::web::WindowBuilderExtWebSys;
builder.with_append(true)
};
let window = builder.build(&event_loop).unwrap();
#[cfg(web_platform)]
let log_list = wasm::insert_canvas_and_create_log_list(&window);
event_loop.run(move |event, elwt| {
#[cfg(web_platform)]
wasm::log_event(&log_list, &event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => elwt.exit(),
Event::AboutToWait => {
window.request_redraw();
}
Event::WindowEvent {
window_id,
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Character(c),
state: ElementState::Released,
..
},
..
},
} if window_id == window.id() && c == "f" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
_ => (),
}
})
}
#[cfg(web_platform)]
mod wasm {
use std::num::NonZeroU32;
use softbuffer::{Surface, SurfaceExtWeb};
use wasm_bindgen::prelude::*;
use winit::{
event::{Event, WindowEvent},
window::Window,
};
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
#[allow(clippy::main_recursion)]
let _ = super::main();
}
pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element {
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas().unwrap();
let mut surface = Surface::from_canvas(canvas.clone()).unwrap();
surface
.resize(
NonZeroU32::new(canvas.width()).unwrap(),
NonZeroU32::new(canvas.height()).unwrap(),
)
.unwrap();
let mut buffer = surface.buffer_mut().unwrap();
buffer.fill(0xFFF0000);
buffer.present().unwrap();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
let style = &canvas.style();
style.set_property("margin", "50px").unwrap();
// Use to test interactions with border and padding.
//style.set_property("border", "50px solid black").unwrap();
//style.set_property("padding", "50px").unwrap();
let log_header = document.create_element("h2").unwrap();
log_header.set_text_content(Some("Event Log"));
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.
let event = match event {
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => None,
Event::WindowEvent { event, .. } => Some(format!("{event:?}")),
Event::Resumed | Event::Suspended => Some(format!("{event:?}")),
_ => None,
};
if let Some(event) = event {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let log = document.create_element("li").unwrap();
let date = js_sys::Date::new_0();
log.set_text_content(Some(&format!(
"{:02}:{:02}:{:02}.{:03}: {event}",
date.get_hours(),
date.get_minutes(),
date.get_seconds(),
date.get_milliseconds(),
)));
log_list
.insert_before(&log, log_list.first_child().as_ref())
.unwrap();
}
}
}

View File

@@ -0,0 +1,102 @@
#![allow(clippy::disallowed_methods)]
pub fn main() {
println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
}
#[cfg(web_platform)]
mod wasm {
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::HtmlCanvasElement;
use winit::{
dpi::PhysicalSize,
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::web::WindowBuilderExtWebSys,
window::Window,
};
const EXPLANATION: &str = "
This example draws a circle in the middle of a 4/1 aspect ratio canvas which acts as a useful demonstration of winit's resize handling on web.
Even when the browser window is resized or aspect-ratio of the canvas changed the circle should always:
* Fill the entire width or height of the canvas (whichever is smaller) without exceeding it.
* Be perfectly round
* Not be blurry or pixelated (there is no antialiasing so you may still see jagged edges depending on the DPI of your monitor)
Currently winit does not handle resizes on web so the circle is rendered incorrectly.
This example demonstrates the desired future functionality which will possibly be provided by https://github.com/rust-windowing/winit/pull/2074
";
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_title("A fantastic window!")
// When running in a non-wasm environment this would set the window size to 100x100.
// However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page.
.with_inner_size(PhysicalSize::new(100, 100))
.with_append(true)
.build(&event_loop)
.unwrap();
let canvas = create_canvas(&window);
// Render once with the size info we currently have
render_circle(&canvas, window.inner_size());
let _ = event_loop.run(move |event, _| match event {
Event::WindowEvent {
event: WindowEvent::Resized(resize),
window_id,
} if window_id == window.id() => {
render_circle(&canvas, resize);
}
_ => (),
});
}
pub fn create_canvas(window: &Window) -> HtmlCanvasElement {
use winit::platform::web::WindowExtWebSys;
let web_window = web_sys::window().unwrap();
let document = web_window.document().unwrap();
let body = document.body().unwrap();
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
let canvas = window.canvas().unwrap();
canvas
.style()
.set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;");
let explanation = document.create_element("pre").unwrap();
explanation.set_text_content(Some(EXPLANATION));
body.append_child(&explanation).unwrap();
canvas
}
pub fn render_circle(canvas: &HtmlCanvasElement, size: PhysicalSize<u32>) {
log::info!("rendering circle with canvas size: {:?}", size);
let context = canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap();
context.begin_path();
context
.arc(
size.width as f64 / 2.0,
size.height as f64 / 2.0,
size.width.min(size.height) as f64 / 2.0,
0.0,
std::f64::consts::PI * 2.0,
)
.unwrap();
context.fill();
}
}

43
winit/examples/window.rs Normal file
View File

@@ -0,0 +1,43 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.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, elwt| {
println!("{event:?}");
match event {
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::RedrawRequested => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
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, KeyEvent, WindowEvent},
event_loop::{DeviceEvents, EventLoop},
keyboard::Key,
window::{Window, WindowButtons},
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.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.listen_device_events(DeviceEvents::Always);
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { window_id, event } = event {
match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
} => match key.as_ref() {
Key::Character("F" | "f") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE);
}
Key::Character("G" | "g") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE);
}
Key::Character("H" | "h") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE);
}
_ => (),
},
WindowEvent::CloseRequested if window_id == window.id() => elwt.exit(),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
}
})
}

View File

@@ -0,0 +1,137 @@
#![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, KeyEvent, RawKeyEvent, WindowEvent},
event_loop::{DeviceEvents, EventLoop},
keyboard::{Key, KeyCode, PhysicalKey},
window::{Fullscreen, Window},
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.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.listen_device_events(DeviceEvents::Always);
event_loop.run(move |event, elwt| {
match event {
// This used to use the virtual key, but the new API
// only provides the `physical_key` (`Code`).
Event::DeviceEvent {
event:
DeviceEvent::Key(RawKeyEvent {
physical_key,
state: ElementState::Released,
..
}),
..
} => match physical_key {
PhysicalKey::Code(KeyCode::KeyM) => {
if minimized {
minimized = !minimized;
window.set_minimized(minimized);
window.focus_window();
}
}
PhysicalKey::Code(KeyCode::KeyV) => {
if !visible {
visible = !visible;
window.set_visible(visible);
}
}
_ => (),
},
Event::WindowEvent { window_id, event } => match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Character(key_str),
state: ElementState::Pressed,
..
},
..
} => match key_str.as_ref() {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
"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");
}
}
"f" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
let monitor = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
}
"p" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
"m" => {
minimized = !minimized;
window.set_minimized(minimized);
}
"q" => {
elwt.exit();
}
"v" => {
visible = !visible;
window.set_visible(visible);
}
"x" => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
_ => (),
},
WindowEvent::CloseRequested if window_id == window.id() => elwt.exit(),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
_ => (),
}
})
}

View File

@@ -0,0 +1,149 @@
//! Demonstrates capability to create in-app draggable regions for client-side decoration support.
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::{CursorIcon, ResizeDirection, Window},
};
const BORDER: f64 = 8.0;
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0))
.with_min_inner_size(winit::dpi::LogicalSize::new(400.0, 200.0))
.with_decorations(false)
.build(&event_loop)
.unwrap();
let mut border = false;
let mut cursor_location = None;
event_loop.run(move |event, elwt| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Press 'B' to toggle borderless")
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::CursorMoved { position, .. } => {
if !window.is_decorated() {
let new_location =
cursor_resize_direction(window.inner_size(), position, BORDER);
if new_location != cursor_location {
cursor_location = new_location;
window.set_cursor(cursor_direction_icon(cursor_location))
}
}
}
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
if let Some(dir) = cursor_location {
let _res = window.drag_resize_window(dir);
} else if !window.is_decorated() {
let _res = window.drag_window();
}
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Released,
logical_key: Key::Character(c),
..
},
..
} if matches!(c.as_ref(), "B" | "b") => {
border = !border;
window.set_decorations(border);
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
_ => (),
})
}
fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> CursorIcon {
match resize_direction {
Some(resize_direction) => match resize_direction {
ResizeDirection::East => CursorIcon::EResize,
ResizeDirection::North => CursorIcon::NResize,
ResizeDirection::NorthEast => CursorIcon::NeResize,
ResizeDirection::NorthWest => CursorIcon::NwResize,
ResizeDirection::South => CursorIcon::SResize,
ResizeDirection::SouthEast => CursorIcon::SeResize,
ResizeDirection::SouthWest => CursorIcon::SwResize,
ResizeDirection::West => CursorIcon::WResize,
},
None => CursorIcon::Default,
}
}
fn cursor_resize_direction(
win_size: winit::dpi::PhysicalSize<u32>,
position: winit::dpi::PhysicalPosition<f64>,
border_size: f64,
) -> Option<ResizeDirection> {
enum XDirection {
West,
East,
Default,
}
enum YDirection {
North,
South,
Default,
}
let xdir = if position.x < border_size {
XDirection::West
} else if position.x > (win_size.width as f64 - border_size) {
XDirection::East
} else {
XDirection::Default
};
let ydir = if position.y < border_size {
YDirection::North
} else if position.y > (win_size.height as f64 - border_size) {
YDirection::South
} else {
YDirection::Default
};
Some(match xdir {
XDirection::West => match ydir {
YDirection::North => ResizeDirection::NorthWest,
YDirection::South => ResizeDirection::SouthWest,
YDirection::Default => ResizeDirection::West,
},
XDirection::East => match ydir {
YDirection::North => ResizeDirection::NorthEast,
YDirection::South => ResizeDirection::SouthEast,
YDirection::Default => ResizeDirection::East,
},
XDirection::Default => match ydir {
YDirection::North => ResizeDirection::North,
YDirection::South => ResizeDirection::South,
YDirection::Default => return None,
},
})
}

View File

@@ -0,0 +1,60 @@
#![allow(clippy::single_match)]
use std::path::Path;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::{Icon, Window},
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
// since it seems to work well enough in most cases. Be careful about going too high, or
// you'll be bitten by the low-quality downscaling built into the WM.
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
let icon = load_icon(Path::new(path));
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_title("An iconic window!")
// At present, this only does anything on Windows and X11, so if you want to save load
// time, you can put icon loading behind a function that returns `None` on other platforms.
.with_window_icon(Some(icon))
.build(&event_loop)
.unwrap();
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::DroppedFile(path) => {
window.set_window_icon(Some(load_icon(&path)));
}
WindowEvent::RedrawRequested => fill::fill_window(&window),
_ => (),
}
}
})
}
fn load_icon(path: &Path) -> Icon {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)
};
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
}

View File

@@ -30,7 +30,7 @@ fn main() -> Result<(), impl std::error::Error> {
fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> { fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> {
let mut app = App::default(); let mut app = App::default();
event_loop.run_on_demand(move |event, event_loop| { event_loop.run_on_demand(move |event, elwt| {
println!("Run {idx}: {:?}", event); println!("Run {idx}: {:?}", event);
if let Some(window) = &app.window { if let Some(window) = &app.window {
@@ -60,15 +60,16 @@ fn main() -> Result<(), impl std::error::Error> {
} if id == window_id => { } if id == window_id => {
println!("--------------------------------------------------------- Window {idx} Destroyed"); println!("--------------------------------------------------------- Window {idx} Destroyed");
app.window_id = None; app.window_id = None;
event_loop.exit(); elwt.exit();
} }
_ => (), _ => (),
} }
} else if let Event::Resumed = event { } else if let Event::Resumed = event {
let window_attributes = Window::default_attributes() let window = Window::builder()
.with_title("Fantastic window number one!") .with_title("Fantastic window number one!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)); .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
let window = event_loop.create_window(window_attributes).unwrap(); .build(elwt)
.unwrap();
app.window_id = Some(window.id()); app.window_id = Some(window.id());
app.window = Some(window); app.window = Some(window);
} }

View File

@@ -0,0 +1,72 @@
#![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::Window,
};
#[cfg(target_os = "macos")]
#[path = "util/fill.rs"]
mod fill;
/// Prints the keyboard events characters received when option_is_alt is true versus false.
/// A left mouse click will toggle option_is_alt.
#[cfg(target_os = "macos")]
fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop)
.unwrap();
window.set_ime_allowed(true);
let mut option_as_alt = window.option_as_alt();
event_loop.run(move |event, elwt| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => elwt.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::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
_ => (),
})
}
#[cfg(not(target_os = "macos"))]
fn main() {
println!("This example is only supported on MacOS");
}

View File

@@ -25,40 +25,38 @@ fn main() -> std::process::ExitCode {
let mut event_loop = EventLoop::new().unwrap(); let mut event_loop = EventLoop::new().unwrap();
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let window = Window::builder()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let mut window = None; 'main: loop {
loop {
let timeout = Some(Duration::ZERO); let timeout = Some(Duration::ZERO);
let status = event_loop.pump_events(timeout, |event, event_loop| { let status = event_loop.pump_events(timeout, |event, elwt| {
if let Event::WindowEvent { event, .. } = &event { if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise // Print only Window events to reduce noise
println!("{event:?}"); println!("{event:?}");
} }
match event { match event {
Event::Resumed => { Event::WindowEvent {
let window_attributes = event: WindowEvent::CloseRequested,
Window::default_attributes().with_title("A fantastic window!"); window_id,
window = Some(event_loop.create_window(window_attributes).unwrap()); } if window_id == window.id() => elwt.exit(),
}
Event::WindowEvent { event, .. } => {
let window = window.as_ref().unwrap();
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => fill::fill_window(window),
_ => (),
}
}
Event::AboutToWait => { Event::AboutToWait => {
window.as_ref().unwrap().request_redraw(); window.request_redraw();
}
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
fill::fill_window(&window);
} }
_ => (), _ => (),
} }
}); });
if let PumpStatus::Exit(exit_code) = status { if let PumpStatus::Exit(exit_code) = status {
break ExitCode::from(exit_code as u8); break 'main ExitCode::from(exit_code as u8);
} }
// Sleep for 1/60 second to simulate application work // Sleep for 1/60 second to simulate application work

View File

@@ -0,0 +1,52 @@
use log::debug;
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, WindowEvent},
event_loop::EventLoop,
keyboard::NamedKey,
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = Window::builder()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(128.0, 128.0))
.with_resize_increments(LogicalSize::new(25.0, 25.0))
.build(&event_loop)
.unwrap();
let mut has_increments = true;
event_loop.run(move |event, elwt| match event {
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput { event, .. }
if event.logical_key == NamedKey::Space
&& event.state == ElementState::Released =>
{
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);
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => window.request_redraw(),
_ => (),
})
}

View File

@@ -0,0 +1,107 @@
#![allow(clippy::single_match)]
#[cfg(target_os = "macos")]
use std::{collections::HashMap, num::NonZeroUsize};
#[cfg(target_os = "macos")]
use simple_logger::SimpleLogger;
#[cfg(target_os = "macos")]
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, NamedKey},
platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS},
window::Window,
};
#[cfg(target_os = "macos")]
#[path = "util/fill.rs"]
mod fill;
#[cfg(target_os = "macos")]
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let mut windows = HashMap::new();
let window = Window::new(&event_loop).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
println!("Press N to open a new window.");
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, window_id } = event {
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() {
elwt.exit();
}
}
WindowEvent::Resized(_) => {
if let Some(window) = windows.get(&window_id) {
window.request_redraw();
}
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key,
..
},
is_synthetic: false,
..
} => match logical_key.as_ref() {
Key::Character("t") => {
let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier();
let window = Window::builder()
.with_tabbing_identifier(&tabbing_id)
.build(elwt)
.unwrap();
println!("Added a new tab: {:?}", window.id());
windows.insert(window.id(), window);
}
Key::Character("w") => {
let _ = windows.remove(&window_id);
}
Key::Named(NamedKey::ArrowRight) => {
windows.get(&window_id).unwrap().select_next_tab();
}
Key::Named(NamedKey::ArrowLeft) => {
windows.get(&window_id).unwrap().select_previous_tab();
}
Key::Character(ch) => {
if let Ok(index) = ch.parse::<NonZeroUsize>() {
let index = index.get();
// Select the last tab when pressing `9`.
let window = windows.get(&window_id).unwrap();
if index == 9 {
window.select_tab_at_index(window.num_tabs() - 1)
} else {
window.select_tab_at_index(index - 1);
}
}
}
_ => (),
},
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
}
})
}
#[cfg(not(target_os = "macos"))]
fn main() {
println!("This example is only supported on MacOS");
}

View File

@@ -0,0 +1,70 @@
//! A demonstration of embedding a winit window in an existing X11 application.
#[cfg(x11_platform)]
#[path = "util/fill.rs"]
mod fill;
#[cfg(x11_platform)]
mod imple {
use super::fill;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::x11::WindowBuilderExtX11,
window::Window,
};
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
// First argument should be a 32-bit X11 window ID.
let parent_window_id = std::env::args()
.nth(1)
.ok_or("Expected a 32-bit X11 window ID as the first argument.")?
.parse::<u32>()?;
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new()?;
let window = Window::builder()
.with_title("An embedded window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(parent_window_id)
.build(&event_loop)
.unwrap();
event_loop.run(move |event, elwt| {
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => elwt.exit(),
Event::AboutToWait => {
window.request_redraw();
}
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
}
})?;
Ok(())
}
}
#[cfg(not(x11_platform))]
mod imple {
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
println!("This example is only supported on X11 platforms.");
Ok(())
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
imple::entry()
}

View File

@@ -5,7 +5,8 @@ use std::{error::Error, hash::Hash};
use cursor_icon::CursorIcon; use cursor_icon::CursorIcon;
use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource}; use crate::event_loop::EventLoopWindowTarget;
use crate::platform_impl::{self, PlatformCustomCursor, PlatformCustomCursorBuilder};
/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`]. /// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048; pub const MAX_CURSOR_SIZE: u16 = 2048;
@@ -48,28 +49,31 @@ impl From<CustomCursor> for Cursor {
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use winit::event_loop::ActiveEventLoop; /// use winit::{
/// # use winit::window::Window; /// event::{Event, WindowEvent},
/// # fn scope(event_loop: &ActiveEventLoop, window: &Window) { /// event_loop::{ControlFlow, EventLoop},
/// use winit::window::CustomCursor; /// window::{CustomCursor, Window},
/// };
///
/// let mut event_loop = EventLoop::new().unwrap();
/// ///
/// let w = 10; /// let w = 10;
/// let h = 10; /// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize]; /// let rgba = vec![255; (w * h * 4) as usize];
/// ///
/// #[cfg(not(target_family = "wasm"))] /// #[cfg(not(target_family = "wasm"))]
/// let source = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap(); /// let builder = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
/// ///
/// #[cfg(target_family = "wasm")] /// #[cfg(target_family = "wasm")]
/// let source = { /// let builder = {
/// use winit::platform::web::CustomCursorExtWebSys; /// use winit::platform::web::CustomCursorExtWebSys;
/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0) /// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
/// }; /// };
/// ///
/// let custom_cursor = event_loop.create_custom_cursor(source); /// let custom_cursor = builder.build(&event_loop);
/// ///
/// let window = Window::new(&event_loop).unwrap();
/// window.set_cursor(custom_cursor.clone()); /// window.set_cursor(custom_cursor.clone());
/// # }
/// ``` /// ```
#[derive(Clone, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CustomCursor { pub struct CustomCursor {
@@ -87,9 +91,9 @@ impl CustomCursor {
height: u16, height: u16,
hotspot_x: u16, hotspot_x: u16,
hotspot_y: u16, hotspot_y: u16,
) -> Result<CustomCursorSource, BadImage> { ) -> Result<CustomCursorBuilder, BadImage> {
Ok(CustomCursorSource { Ok(CustomCursorBuilder {
inner: PlatformCustomCursorSource::from_rgba( inner: PlatformCustomCursorBuilder::from_rgba(
rgba.into(), rgba.into(),
width, width,
height, height,
@@ -100,12 +104,20 @@ impl CustomCursor {
} }
} }
/// Source for [`CustomCursor`]. /// Builds a [`CustomCursor`].
/// ///
/// See [`CustomCursor`] for more details. /// See [`CustomCursor`] for more details.
#[derive(Debug)] #[derive(Debug)]
pub struct CustomCursorSource { pub struct CustomCursorBuilder {
pub(crate) inner: PlatformCustomCursorSource, pub(crate) inner: PlatformCustomCursorBuilder,
}
impl CustomCursorBuilder {
pub fn build(self, window_target: &EventLoopWindowTarget) -> CustomCursor {
CustomCursor {
inner: PlatformCustomCursor::build(self.inner, &window_target.p),
}
}
} }
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments. /// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
@@ -166,14 +178,12 @@ impl fmt::Display for BadImage {
impl Error for BadImage {} impl Error for BadImage {}
/// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with /// Platforms export this directly as `PlatformCustomCursorBuilder` if they need to only work with images.
/// images.
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage); pub(crate) struct OnlyCursorImageBuilder(pub(crate) CursorImage);
#[allow(dead_code)] #[allow(dead_code)]
impl OnlyCursorImageSource { impl OnlyCursorImageBuilder {
pub(crate) fn from_rgba( pub(crate) fn from_rgba(
rgba: Vec<u8>, rgba: Vec<u8>,
width: u16, width: u16,
@@ -203,6 +213,16 @@ impl PartialEq for OnlyCursorImage {
impl Eq for OnlyCursorImage {} impl Eq for OnlyCursorImage {}
#[allow(dead_code)]
impl OnlyCursorImage {
pub(crate) fn build(
builder: OnlyCursorImageBuilder,
_: &platform_impl::EventLoopWindowTarget,
) -> Self {
Self(Arc::new(builder.0))
}
}
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) struct CursorImage { pub(crate) struct CursorImage {
@@ -277,4 +297,8 @@ impl NoCustomCursor {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?; CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
Ok(Self) Ok(Self)
} }
fn build(self, _: &platform_impl::EventLoopWindowTarget) -> NoCustomCursor {
self
}
} }

View File

@@ -1,6 +1,8 @@
use crate::platform_impl;
use std::{error, fmt}; use std::{error, fmt};
use crate::platform_impl; #[doc(inline)]
pub use winit_core::error::NotSupportedError;
// TODO: Rename // TODO: Rename
/// An error that may be generated when requesting Winit state /// An error that may be generated when requesting Winit state
@@ -14,10 +16,10 @@ pub enum ExternalError {
Os(OsError), Os(OsError),
} }
/// The error type for when the requested operation is not supported by the backend. impl From<winit_core::event::InnerSizeIgnored> for ExternalError {
#[derive(Clone)] fn from(_: winit_core::event::InnerSizeIgnored) -> Self {
pub struct NotSupportedError { Self::Ignored
_marker: (), }
} }
/// The error type for when the OS cannot perform the requested operation. /// The error type for when the OS cannot perform the requested operation.
@@ -47,14 +49,6 @@ impl From<OsError> for EventLoopError {
} }
} }
impl NotSupportedError {
#[inline]
#[allow(dead_code)]
pub(crate) fn new() -> NotSupportedError {
NotSupportedError { _marker: () }
}
}
impl OsError { impl OsError {
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError { pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError {
@@ -88,18 +82,6 @@ impl fmt::Display for ExternalError {
} }
} }
impl fmt::Debug for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("NotSupportedError").finish()
}
}
impl fmt::Display for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.pad("the requested operation is not supported by Winit")
}
}
impl fmt::Display for EventLoopError { impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self { match self {
@@ -113,7 +95,6 @@ impl fmt::Display for EventLoopError {
impl error::Error for OsError {} impl error::Error for OsError {}
impl error::Error for ExternalError {} impl error::Error for ExternalError {}
impl error::Error for NotSupportedError {}
impl error::Error for EventLoopError {} impl error::Error for EventLoopError {}
#[cfg(test)] #[cfg(test)]
@@ -125,11 +106,6 @@ mod tests {
// Eat attributes for testing // Eat attributes for testing
#[test] #[test]
fn ensure_fmt_does_not_panic() { fn ensure_fmt_does_not_panic() {
let _ = format!(
"{:?}, {}",
NotSupportedError::new(),
NotSupportedError::new().clone()
);
let _ = format!( let _ = format!(
"{:?}, {}", "{:?}, {}",
ExternalError::NotSupported(NotSupportedError::new()), ExternalError::NotSupported(NotSupportedError::new()),

53
winit/src/event.rs Normal file
View File

@@ -0,0 +1,53 @@
//! The [`Event`] enum and assorted supporting types.
//!
//! These are sent to the closure given to [`EventLoop::run(...)`], where they get
//! processed and used to modify the program state. For more details, see the root-level documentation.
//!
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this:
//!
//! ```rust,ignore
//! let mut start_cause = StartCause::Init;
//!
//! while !elwt.exiting() {
//! event_handler(NewEvents(start_cause), elwt);
//!
//! for e in (window events, user events, device events) {
//! event_handler(e, elwt);
//! }
//!
//! for w in (redraw windows) {
//! event_handler(RedrawRequested(w), elwt);
//! }
//!
//! event_handler(AboutToWait, elwt);
//! start_cause = wait_if_necessary();
//! }
//!
//! event_handler(LoopExiting, elwt);
//! ```
//!
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
//! describes what happens in what order.
//!
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use crate::platform_impl;
#[doc(inline)]
pub use winit_core::event::{
DeviceEvent, DeviceId, ElementState, Force, Ime, InnerSizeIgnored, InnerSizeWriter, Modifiers,
MouseButton, MouseScrollDelta, RawKeyEvent, StartCause, Touch, TouchPhase,
};
pub type Event<T> = winit_core::event::Event<T, KeyExtra>;
pub type WindowEvent = winit_core::event::WindowEvent<KeyExtra>;
pub type KeyEvent = winit_core::event::KeyEvent<KeyExtra>;
/// Extra keyboard information.
#[derive(Debug, Clone)]
pub struct KeyExtra {
#[allow(dead_code)]
pub(crate) extra: platform_impl::KeyEventExtra,
}

View File

@@ -8,20 +8,18 @@
//! See the root-level documentation for information on how to create and use an event loop to //! See the root-level documentation for information on how to create and use an event loop to
//! handle events. //! handle events.
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::Deref;
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::{error, fmt}; use std::{error, fmt};
#[cfg(not(web_platform))] use crate::error::EventLoopError;
use std::time::{Duration, Instant};
#[cfg(web_platform)]
use web_time::{Duration, Instant};
use crate::error::{EventLoopError, OsError};
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
use crate::{event::Event, monitor::MonitorHandle, platform_impl}; use crate::{event::Event, monitor::MonitorHandle, platform_impl};
#[doc(inline)]
pub use winit_core::event_loop::{AsyncRequestSerial, ControlFlow};
/// Provides a way to retrieve events from the system and from the windows that were registered to /// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop. /// the events loop.
/// ///
@@ -45,9 +43,11 @@ pub struct EventLoop<T: 'static> {
/// Target that associates windows with an [`EventLoop`]. /// Target that associates windows with an [`EventLoop`].
/// ///
/// This type exists to allow you to create new windows while Winit executes /// This type exists to allow you to create new windows while Winit executes
/// your callback. /// your callback. [`EventLoop`] will coerce into this type (`impl<T> Deref for
pub struct ActiveEventLoop { /// EventLoop<T>`), so functions that take this as a parameter can also take
pub(crate) p: platform_impl::ActiveEventLoop, /// `&EventLoop`.
pub struct EventLoopWindowTarget {
pub(crate) p: platform_impl::EventLoopWindowTarget,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
} }
@@ -133,53 +133,9 @@ impl<T> fmt::Debug for EventLoop<T> {
} }
} }
impl fmt::Debug for ActiveEventLoop { impl fmt::Debug for EventLoopWindowTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("ActiveEventLoop { .. }") f.pad("EventLoopWindowTarget { .. }")
}
}
/// Set through [`ActiveEventLoop::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
///
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::Wait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
#[default]
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the given time is reached.
///
/// Useful for implementing efficient timers. Applications which want to render at the display's
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API
/// to reduce odds of missed frames.
///
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
}
impl ControlFlow {
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
///
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
/// instead set to [`Wait`].
///
/// [`WaitUntil`]: Self::WaitUntil
/// [`Wait`]: Self::Wait
pub fn wait_duration(timeout: Duration) -> Self {
match Instant::now().checked_add(timeout) {
Some(instant) => Self::WaitUntil(instant),
None => Self::Wait,
}
} }
} }
@@ -239,80 +195,30 @@ impl<T> EventLoop<T> {
/// ///
/// This function won't be available with `target_feature = "exception-handling"`. /// This function won't be available with `target_feature = "exception-handling"`.
/// ///
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() /// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
/// [`run()`]: Self::run() /// [`run()`]: Self::run()
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on Web. /// [^1]: `EventLoopExtWebSys::spawn()` is only available on Web.
#[inline] #[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))] #[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError> pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(Event<T>, &ActiveEventLoop), F: FnMut(Event<T>, &EventLoopWindowTarget),
{ {
self.event_loop.run(event_handler) self.event_loop.run(event_handler)
} }
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events /// Creates an [`EventLoopProxy`] that can be used to dispatch user events to the main event loop.
/// to the main event loop, possibly from another thread.
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy { EventLoopProxy {
event_loop_proxy: self.event_loop.create_proxy(), event_loop_proxy: self.event_loop.create_proxy(),
} }
} }
/// Gets a persistent reference to the underlying platform display.
///
/// See the [`OwnedDisplayHandle`] type for more information.
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle {
platform: self.event_loop.window_target().p.owned_display_handle(),
}
}
/// Change if or when [`DeviceEvent`]s are captured.
///
/// See [`ActiveEventLoop::listen_device_events`] for details.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn listen_device_events(&self, allowed: DeviceEvents) {
self.event_loop
.window_target()
.p
.listen_device_events(allowed);
}
/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.event_loop
.window_target()
.p
.set_control_flow(control_flow)
}
/// Create a window.
///
/// Creating window without event loop running often leads to improper window creation;
/// use [`ActiveEventLoop::create_window`] instead.
#[deprecated = "use `ActiveEventLoop::create_window` instead"]
#[inline]
pub fn create_window(&self, window_attributes: WindowAttributes) -> Result<Window, OsError> {
let window =
platform_impl::Window::new(&self.event_loop.window_target().p, window_attributes)?;
Ok(Window { window })
}
/// Create custom cursor.
pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor {
self.event_loop
.window_target()
.p
.create_custom_cursor(custom_cursor)
}
} }
#[cfg(feature = "rwh_06")] #[cfg(feature = "rwh_06")]
impl<T> rwh_06::HasDisplayHandle for EventLoop<T> { impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> { fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
rwh_06::HasDisplayHandle::display_handle(self.event_loop.window_target()) rwh_06::HasDisplayHandle::display_handle(&**self)
} }
} }
@@ -320,7 +226,7 @@ impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> { unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop. /// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
rwh_05::HasRawDisplayHandle::raw_display_handle(self.event_loop.window_target()) rwh_05::HasRawDisplayHandle::raw_display_handle(&**self)
} }
} }
@@ -352,26 +258,14 @@ impl<T> AsRawFd for EventLoop<T> {
} }
} }
impl ActiveEventLoop { impl<T> Deref for EventLoop<T> {
/// Create the window. type Target = EventLoopWindowTarget;
/// fn deref(&self) -> &EventLoopWindowTarget {
/// Possible causes of error include denied permission, incompatible system, and lack of memory. self.event_loop.window_target()
/// }
/// ## Platform-specific
///
/// - **Web:** The window is created but not inserted into the web page automatically. Please
/// see the web platform module for more information.
#[inline]
pub fn create_window(&self, window_attributes: WindowAttributes) -> Result<Window, OsError> {
let window = platform_impl::Window::new(&self.p, window_attributes)?;
Ok(Window { window })
}
/// Create custom cursor.
pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor {
self.p.create_custom_cursor(custom_cursor)
} }
impl EventLoopWindowTarget {
/// Returns the list of all the monitors available on the system. /// Returns the list of all the monitors available on the system.
#[inline] #[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> { pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
@@ -446,7 +340,7 @@ impl ActiveEventLoop {
} }
#[cfg(feature = "rwh_06")] #[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for ActiveEventLoop { impl rwh_06::HasDisplayHandle for EventLoopWindowTarget {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> { fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.p.raw_display_handle_rwh_06()?; let raw = self.p.raw_display_handle_rwh_06()?;
// SAFETY: The display will never be deallocated while the event loop is alive. // SAFETY: The display will never be deallocated while the event loop is alive.
@@ -455,7 +349,7 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
} }
#[cfg(feature = "rwh_05")] #[cfg(feature = "rwh_05")]
unsafe impl rwh_05::HasRawDisplayHandle for ActiveEventLoop { unsafe impl rwh_05::HasRawDisplayHandle for EventLoopWindowTarget {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop. /// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
self.p.raw_display_handle_rwh_05() self.p.raw_display_handle_rwh_05()
@@ -466,7 +360,7 @@ unsafe impl rwh_05::HasRawDisplayHandle for ActiveEventLoop {
/// ///
/// The purpose of this type is to provide a cheaply clonable handle to the underlying /// The purpose of this type is to provide a cheaply clonable handle to the underlying
/// display handle. This is often used by graphics APIs to connect to the underlying APIs. /// display handle. This is often used by graphics APIs to connect to the underlying APIs.
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`] /// It is difficult to keep a handle to the [`EventLoop`] type or the [`EventLoopWindowTarget`]
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as /// type. In contrast, this type involves no lifetimes and can be persisted for as long as
/// needed. /// needed.
/// ///
@@ -566,29 +460,3 @@ pub enum DeviceEvents {
/// Never capture device events. /// Never capture device events.
Never, Never,
} }
/// A unique identifier of the winit's async request.
///
/// This could be used to identify the async request once it's done
/// and a specific action must be taken.
///
/// One of the handling scenarios could be to maintain a working list
/// containing [`AsyncRequestSerial`] and some closure associated with it.
/// Then once event is arriving the working list is being traversed and a job
/// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AsyncRequestSerial {
serial: usize,
}
impl AsyncRequestSerial {
// TODO(kchibisov): Remove `cfg` when the clipboard will be added.
#[allow(dead_code)]
pub(crate) fn get() -> Self {
static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0);
// NOTE: We rely on wrap around here, while the user may just request
// in the loop usize::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial }
}
}

View File

@@ -2,15 +2,22 @@
//! //!
//! # Building windows //! # Building windows
//! //!
//! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with //! Before you can build a [`Window`], you first need to build an [`EventLoop`]. This is done with the
//! the [`EventLoop::new()`] function. //! [`EventLoop::new()`] function.
//! //!
//! ```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().unwrap();
//! ``` //! ```
//! //!
//! Then you create a [`Window`] with [`create_window`]. //! Once this is done, there are two ways to create a [`Window`]:
//!
//! - Calling [`Window::new(&event_loop)`][window_new].
//! - Calling [`let builder = Window::builder()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
//!
//! The first method is the simplest and will give you default values for everything. The second
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
//! //!
//! # Event handling //! # Event handling
//! //!
@@ -60,6 +67,7 @@
//! }; //! };
//! //!
//! let event_loop = EventLoop::new().unwrap(); //! let event_loop = EventLoop::new().unwrap();
//! let window = Window::builder().build(&event_loop).unwrap();
//! //!
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't //! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications. //! // dispatched any events. This is ideal for games and similar applications.
@@ -70,19 +78,14 @@
//! // 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); //! event_loop.set_control_flow(ControlFlow::Wait);
//! //!
//! let mut window = None; //! event_loop.run(move |event, elwt| {
//!
//! event_loop.run(move |event, event_loop| {
//! match event { //! match event {
//! Event::Resumed => {
//! window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
//! }
//! Event::WindowEvent { //! Event::WindowEvent {
//! event: WindowEvent::CloseRequested, //! event: WindowEvent::CloseRequested,
//! .. //! ..
//! } => { //! } => {
//! println!("The close button was pressed; stopping"); //! println!("The close button was pressed; stopping");
//! event_loop.exit(); //! elwt.exit();
//! }, //! },
//! Event::AboutToWait => { //! Event::AboutToWait => {
//! // Application update code. //! // Application update code.
@@ -92,7 +95,7 @@
//! // 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 render here instead.
//! window.as_ref().unwrap().request_redraw(); //! window.request_redraw();
//! }, //! },
//! Event::WindowEvent { //! Event::WindowEvent {
//! event: WindowEvent::RedrawRequested, //! event: WindowEvent::RedrawRequested,
@@ -123,54 +126,19 @@
//! 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
//! display the window to the user. If you notice this happening, you should create the window with //! display the window to the user. If you notice this happening, you should create the window with
//! [`visible` set to `false`](crate::window::WindowAttributes::with_visible) and explicitly make the //! [`visible` set to `false`](crate::window::WindowBuilder::with_visible) and explicitly make the
//! window visible only once you're ready to render into it. //! window visible only once you're ready to render into it.
//! //!
//! # UI scaling
//!
//! UI scaling is important, go read the docs for the [`dpi`] crate for an
//! introduction.
//!
//! All of Winit's functions return physical types, but can take either logical or physical
//! coordinates as input, allowing you to use the most convenient coordinate system for your
//! particular application.
//!
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
//! monitor or if the user changes their DPI settings. This allows you to rescale your application's
//! UI elements and adjust how the platform changes the window's size to reflect the new scale
//! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor
//! can be found by calling [`window.scale_factor()`].
//!
//! [`ScaleFactorChanged`]: event::WindowEvent::ScaleFactorChanged
//! [`window.scale_factor()`]: window::Window::scale_factor
//!
//! # Cargo Features
//!
//! Winit provides the following Cargo features:
//!
//! * `x11` (enabled by default): On Unix platforms, enables the X11 backend.
//! * `wayland` (enabled by default): On Unix platforms, enables the Wayland
//! backend.
//! * `rwh_04`: Implement `raw-window-handle v0.4` traits.
//! * `rwh_05`: Implement `raw-window-handle v0.5` traits.
//! * `rwh_06`: Implement `raw-window-handle v0.6` traits.
//! * `serde`: Enables serialization/deserialization of certain types with
//! [Serde](https://crates.io/crates/serde).
//! * `mint`: Enables mint (math interoperability standard types) conversions.
//!
//! See the [`platform`] module for documentation on platform-specific cargo
//! features.
//!
//! [`EventLoop`]: event_loop::EventLoop //! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoop::new()`]: event_loop::EventLoop::new //! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [`EventLoop::run()`]: event_loop::EventLoop::run //! [`EventLoop::run()`]: event_loop::EventLoop::run
//! [`exit()`]: event_loop::ActiveEventLoop::exit //! [`exit()`]: event_loop::EventLoopWindowTarget::exit
//! [`Window`]: window::Window //! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId //! [`WindowId`]: window::WindowId
//! [`WindowAttributes`]: window::WindowAttributes //! [`WindowBuilder`]: window::WindowBuilder
//! [window_new]: window::Window::new //! [window_new]: window::Window::new
//! [`create_window`]: event_loop::ActiveEventLoop::create_window //! [window_builder_new]: window::Window::builder
//! [window_builder_build]: window::WindowBuilder::build
//! [`Window::id()`]: window::Window::id //! [`Window::id()`]: window::Window::id
//! [`WindowEvent`]: event::WindowEvent //! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent //! [`DeviceEvent`]: event::DeviceEvent
@@ -184,7 +152,7 @@
#![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)] #![deny(clippy::all)]
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(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, docsrs,
@@ -196,9 +164,8 @@
#[cfg(feature = "rwh_06")] #[cfg(feature = "rwh_06")]
pub use rwh_06 as raw_window_handle; pub use rwh_06 as raw_window_handle;
// Re-export DPI types so that users don't have to put it in Cargo.toml.
#[doc(inline)] #[doc(inline)]
pub use dpi; pub use winit_core::{dpi, keyboard};
#[macro_use] #[macro_use]
pub mod error; pub mod error;
@@ -206,10 +173,8 @@ mod cursor;
pub mod event; pub mod event;
pub mod event_loop; pub mod event_loop;
mod icon; mod icon;
pub mod keyboard;
pub mod monitor; pub mod monitor;
mod platform_impl; mod platform_impl;
mod utils;
pub mod window; pub mod window;
pub mod platform; pub mod platform;

View File

@@ -3,7 +3,7 @@
//! If you want to get basic information about a monitor, you can use the //! If you want to get basic information about a monitor, you can use the
//! [`MonitorHandle`] type. This is retrieved from one of the following //! [`MonitorHandle`] type. This is retrieved from one of the following
//! methods, which return an iterator of [`MonitorHandle`]: //! methods, which return an iterator of [`MonitorHandle`]:
//! - [`ActiveEventLoop::available_monitors`](crate::event_loop::ActiveEventLoop::available_monitors). //! - [`EventLoopWindowTarget::available_monitors`](crate::event_loop::EventLoopWindowTarget::available_monitors).
//! - [`Window::available_monitors`](crate::window::Window::available_monitors). //! - [`Window::available_monitors`](crate::window::Window::available_monitors).
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
@@ -145,7 +145,7 @@ impl MonitorHandle {
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical /// Returns the scale factor of the underlying monitor. To map logical pixels to physical
/// pixels and vice versa, use [`Window::scale_factor`]. /// pixels and vice versa, use [`Window::scale_factor`].
/// ///
/// See the [`dpi`] module for more information. /// See the [`dpi`](crate::dpi) module for more information.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///

View File

@@ -0,0 +1,104 @@
use crate::{
event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget},
window::{Window, WindowBuilder},
};
use self::activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {}
impl<T> EventLoopExtAndroid for EventLoop<T> {}
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Android.
pub trait EventLoopWindowTargetExtAndroid {}
/// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid {
fn content_rect(&self) -> Rect;
fn config(&self) -> ConfigurationRef;
}
impl WindowExtAndroid for Window {
fn content_rect(&self) -> Rect {
self.window.content_rect()
}
fn config(&self) -> ConfigurationRef {
self.window.config()
}
}
impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {}
/// Additional methods on [`WindowBuilder`] that are specific to Android.
pub trait WindowBuilderExtAndroid {}
impl WindowBuilderExtAndroid for WindowBuilder {}
pub trait EventLoopBuilderExtAndroid {
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
///
/// This must be called on Android since the `AndroidApp` is not global state.
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> {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
}
fn handle_volume_keys(&mut self) -> &mut Self {
self.platform_specific.ignore_volume_keys = false;
self
}
}
/// Re-export of the `android_activity` API
///
/// Winit re-exports the `android_activity` API for convenience so that most
/// applications can rely on the Winit crate to resolve the required version of
/// `android_activity` and avoid any chance of a conflict between Winit and the
/// application crate.
///
/// Unlike most libraries there can only be a single implementation
/// of the `android_activity` glue crate linked with an application because
/// it is responsible for the application's `android_main()` entry point.
///
/// Since Winit depends on a specific version of `android_activity` the simplest
/// way to avoid creating a conflict is for applications to avoid explicitly
/// depending on the `android_activity` crate, and instead consume the API that
/// is re-exported by Winit.
///
/// For compatibility applications should then import the `AndroidApp` type for
/// their `android_main(app: AndroidApp)` function like:
/// ```rust
/// #[cfg(target_os="android")]
/// use winit::platform::android::activity::AndroidApp;
/// ```
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::*;
#[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,75 +1,9 @@
//! # iOS / UIKit
//!
//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
//! iOS 9.3.
//!
//! iOS's main `UIApplicationMain` does some init work that's required by all
//! UI-related code (see issue [#1705]). It is best to create your windows
//! inside `Event::Resumed`.
//!
//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
//!
//! ## Building app
//!
//! To build ios app you will need rustc built for this targets:
//!
//! - armv7-apple-ios
//! - armv7s-apple-ios
//! - i386-apple-ios
//! - aarch64-apple-ios
//! - x86_64-apple-ios
//!
//! Then
//!
//! ```
//! cargo build --target=...
//! ```
//! The simplest way to integrate your app into xcode environment is to build it
//! as a static library. Wrap your main function and export it.
//!
//! ```rust, ignore
//! #[no_mangle]
//! pub extern fn start_winit_app() {
//! start_inner()
//! }
//!
//! fn start_inner() {
//! ...
//! }
//! ```
//!
//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
//!
//! ```ignore
//! void start_winit_app();
//! ```
//!
//! Use start_winit_app inside your xcode's main function.
//!
//!
//! ## App lifecycle and events
//!
//! iOS environment is very different from other platforms and you must be very
//! careful with it's events. Familiarize yourself with
//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/).
//!
//! This is how those event are represented in winit:
//!
//! - applicationDidBecomeActive is Resumed
//! - applicationWillResignActive is Suspended
//! - applicationWillTerminate is LoopExiting
//!
//! Keep in mind that after LoopExiting event is received every attempt to draw with
//! opengl will result in segfault.
//!
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
use std::os::raw::c_void; use std::os::raw::c_void;
use crate::{ use crate::{
event_loop::EventLoop, event_loop::EventLoop,
monitor::{MonitorHandle, VideoModeHandle}, monitor::{MonitorHandle, VideoModeHandle},
window::{Window, WindowAttributes}, window::{Window, WindowBuilder},
}; };
/// Additional methods on [`EventLoop`] that are specific to iOS. /// Additional methods on [`EventLoop`] that are specific to iOS.
@@ -225,8 +159,8 @@ impl WindowExtIOS for Window {
} }
} }
/// Additional methods on [`WindowAttributes`] that are specific to iOS. /// Additional methods on [`WindowBuilder`] that are specific to iOS.
pub trait WindowAttributesExtIOS { pub trait WindowBuilderExtIOS {
/// 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
@@ -280,41 +214,42 @@ pub trait WindowAttributesExtIOS {
fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self; fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
} }
impl WindowAttributesExtIOS for WindowAttributes { impl WindowBuilderExtIOS for WindowBuilder {
#[inline] #[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> Self { fn with_scale_factor(mut self, scale_factor: f64) -> Self {
self.platform_specific.scale_factor = Some(scale_factor); self.window.platform_specific.scale_factor = Some(scale_factor);
self self
} }
#[inline] #[inline]
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self { fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self {
self.platform_specific.valid_orientations = valid_orientations; self.window.platform_specific.valid_orientations = valid_orientations;
self self
} }
#[inline] #[inline]
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self { fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self {
self.platform_specific.prefers_home_indicator_hidden = hidden; self.window.platform_specific.prefers_home_indicator_hidden = hidden;
self self
} }
#[inline] #[inline]
fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self { fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
self.platform_specific self.window
.platform_specific
.preferred_screen_edges_deferring_system_gestures = edges; .preferred_screen_edges_deferring_system_gestures = edges;
self self
} }
#[inline] #[inline]
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self { fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self {
self.platform_specific.prefers_status_bar_hidden = hidden; self.window.platform_specific.prefers_status_bar_hidden = hidden;
self self
} }
#[inline] #[inline]
fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self { fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
self.platform_specific.preferred_status_bar_style = status_bar_style; self.window.platform_specific.preferred_status_bar_style = status_bar_style;
self self
} }
} }

View File

@@ -1,28 +1,12 @@
//! # macOS / AppKit
//!
//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
//! itself), and is regularly tested on macOS 10.14.
//!
//! A lot of functionality expects the application to be ready before you
//! start doing anything; this includes creating windows, fetching monitors,
//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
//!
//! If you encounter problems, you should try doing your initialization inside
//! `Event::Resumed`.
//!
//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
use std::os::raw::c_void; use std::os::raw::c_void;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
event_loop::{ActiveEventLoop, EventLoopBuilder}, event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle, monitor::MonitorHandle,
window::{Window, WindowAttributes}, window::{Window, WindowBuilder},
}; };
/// Additional methods on [`Window`] that are specific to MacOS. /// Additional methods on [`Window`] that are specific to MacOS.
@@ -190,15 +174,15 @@ pub enum ActivationPolicy {
Prohibited, Prohibited,
} }
/// Additional methods on [`WindowAttributes`] 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 [`WindowAttributes::with_decorations`] method: /// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method:
/// - `with_titlebar_transparent` /// - `with_titlebar_transparent`
/// - `with_title_hidden` /// - `with_title_hidden`
/// - `with_titlebar_hidden` /// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden` /// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view` /// - `with_fullsize_content_view`
pub trait WindowAttributesExtMacOS { 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) -> Self;
/// Makes the titlebar transparent and allows the content to appear behind it. /// Makes the titlebar transparent and allows the content to appear behind it.
@@ -225,64 +209,65 @@ pub trait WindowAttributesExtMacOS {
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self; fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
} }
impl WindowAttributesExtMacOS for WindowAttributes { impl WindowBuilderExtMacOS for WindowBuilder {
#[inline] #[inline]
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self { fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.platform_specific.movable_by_window_background = movable_by_window_background; self.window.platform_specific.movable_by_window_background = movable_by_window_background;
self self
} }
#[inline] #[inline]
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self { fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
self.platform_specific.titlebar_transparent = titlebar_transparent; self.window.platform_specific.titlebar_transparent = titlebar_transparent;
self self
} }
#[inline] #[inline]
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self { fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
self.platform_specific.titlebar_hidden = titlebar_hidden; self.window.platform_specific.titlebar_hidden = titlebar_hidden;
self self
} }
#[inline] #[inline]
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self { fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden; self.window.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
self self
} }
#[inline] #[inline]
fn with_title_hidden(mut self, title_hidden: bool) -> Self { fn with_title_hidden(mut self, title_hidden: bool) -> Self {
self.platform_specific.title_hidden = title_hidden; self.window.platform_specific.title_hidden = title_hidden;
self self
} }
#[inline] #[inline]
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self { fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
self.platform_specific.fullsize_content_view = fullsize_content_view; self.window.platform_specific.fullsize_content_view = fullsize_content_view;
self self
} }
#[inline] #[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self { fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
self.platform_specific.disallow_hidpi = disallow_hidpi; self.window.platform_specific.disallow_hidpi = disallow_hidpi;
self self
} }
#[inline] #[inline]
fn with_has_shadow(mut self, has_shadow: bool) -> Self { fn with_has_shadow(mut self, has_shadow: bool) -> Self {
self.platform_specific.has_shadow = has_shadow; self.window.platform_specific.has_shadow = has_shadow;
self self
} }
#[inline] #[inline]
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self { fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
self.platform_specific.accepts_first_mouse = accepts_first_mouse; self.window.platform_specific.accepts_first_mouse = accepts_first_mouse;
self self
} }
#[inline] #[inline]
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self { fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.platform_specific self.window
.platform_specific
.tabbing_identifier .tabbing_identifier
.replace(tabbing_identifier.to_string()); .replace(tabbing_identifier.to_string());
self self
@@ -290,7 +275,7 @@ impl WindowAttributesExtMacOS for WindowAttributes {
#[inline] #[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self { fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
self.platform_specific.option_as_alt = option_as_alt; self.window.platform_specific.option_as_alt = option_as_alt;
self self
} }
} }
@@ -390,8 +375,8 @@ impl MonitorHandleExtMacOS for MonitorHandle {
} }
} }
/// Additional methods on [`ActiveEventLoop`] that are specific to macOS. /// Additional methods on [`EventLoopWindowTarget`] that are specific to macOS.
pub trait ActiveEventLoopExtMacOS { pub trait EventLoopWindowTargetExtMacOS {
/// Hide the entire application. In most applications this is typically triggered with Command-H. /// Hide the entire application. In most applications this is typically triggered with Command-H.
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.
@@ -404,7 +389,7 @@ pub trait ActiveEventLoopExtMacOS {
fn allows_automatic_window_tabbing(&self) -> bool; fn allows_automatic_window_tabbing(&self) -> bool;
} }
impl ActiveEventLoopExtMacOS for ActiveEventLoop { impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget {
fn hide_application(&self) { fn hide_application(&self) {
self.p.hide_application() self.p.hide_application()
} }

View File

@@ -46,7 +46,6 @@ pub mod pump_events;
macos_platform, macos_platform,
x11_platform, x11_platform,
wayland_platform, wayland_platform,
orbital_platform,
docsrs docsrs
))] ))]
pub mod modifier_supplement; pub mod modifier_supplement;

View File

@@ -25,7 +25,8 @@ pub trait KeyEventExtModifierSupplement {
impl KeyEventExtModifierSupplement for KeyEvent { impl KeyEventExtModifierSupplement for KeyEvent {
#[inline] #[inline]
fn text_with_all_modifiers(&self) -> Option<&str> { fn text_with_all_modifiers(&self) -> Option<&str> {
self.platform_specific self.extra
.extra
.text_with_all_modifiers .text_with_all_modifiers
.as_ref() .as_ref()
.map(|s| s.as_str()) .map(|s| s.as_str())
@@ -33,6 +34,6 @@ impl KeyEventExtModifierSupplement for KeyEvent {
#[inline] #[inline]
fn key_without_modifiers(&self) -> Key { fn key_without_modifiers(&self) -> Key {
self.platform_specific.key_without_modifiers.clone() self.extra.extra.key_without_modifiers.clone()
} }
} }

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