Compare commits

..

1 Commits

Author SHA1 Message Date
Mads Marquart
e66eba38f5 Add OwnedWindowHandle to avoid lifetime on WindowHandle<'_> 2023-12-24 00:46:13 +01:00
198 changed files with 12285 additions and 13997 deletions

16
.github/CODEOWNERS vendored
View File

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

View File

@@ -48,15 +48,6 @@ jobs:
include:
- toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- toolchain: 'nightly'
platform: {
name: 'web Atomic',
target: wasm32-unknown-unknown,
os: ubuntu-latest,
options: '-Zbuild-std=panic_abort,std',
rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
components: rust-src,
}
env:
# Set more verbose terminal output
@@ -64,7 +55,8 @@ jobs:
RUST_BACKTRACE: 1
# Faster compilation and error on warnings
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings'
RUSTDOCFLAGS: '--deny=warnings'
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
CMD: ${{ matrix.platform.cmd }}
@@ -89,7 +81,7 @@ jobs:
- name: Generate lockfile
# 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
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
@@ -117,12 +109,10 @@ jobs:
with:
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
components: clippy, ${{ matrix.platform.components }}
components: clippy
- name: Check documentation
run: cargo doc --no-deps $OPTIONS --document-private-items
env:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}'
- name: Build crate
run: cargo $CMD build $OPTIONS
@@ -161,12 +151,6 @@ jobs:
matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS --features serde
- name: Check docs.rs documentation
if: matrix.toolchain == 'nightly'
run: cargo doc --no-deps $OPTIONS --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
env:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs'
# See restore step above
- name: Save cache of cargo folder
uses: actions/cache/save@v3

View File

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

View File

@@ -11,115 +11,18 @@ Unreleased` header.
# Unreleased
- Move `dpi` types to its own crate, and re-export it from the root crate.
- 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`.
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
- **Breaking:** Remove `Window::set_cursor_icon`
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`
- Add `Window::set_custom_cursor`
- Add `CustomCursor`
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s.
- Add `{Active,}EventLoop::create_custom_cursor` to load custom cursor image sources.
- On macOS, add services menu.
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
- On Web, fix setting cursor icon overriding cursor visibility.
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
- On Web, add the ability to toggle calling `Event.preventDefault()` on `Window`.
- **Breaking:** Remove `WindowAttributes::fullscreen()` and expose as field directly.
- **Breaking:** Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold static data.
- **Breaking:** No longer export `platform::x11::XNotSupported`.
- **Breaking:** Renamed `platform::x11::XWindowType` to `platform::x11::WindowType`.
- Add the `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases.
- **Breaking:** Rename `TouchpadMagnify` to `PinchGesture`, `SmartMagnify` to `DoubleTapGesture` and `TouchpadRotate` to `RotationGesture` to represent the action rather than the intent.
- on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`.
- on Windows: add `with_system_backdrop`, `with_border_color`, `with_title_background_color`, `with_title_text_color` and `with_corner_preference`
- On Windows, Remove `WS_CAPTION`, `WS_BORDER` and `WS_EX_WINDOWEDGE` styles for child windows without decorations.
- **Breaking:** Removed `EventLoopError::AlreadyRunning`, which can't happen as it is already prevented by the type system.
- Added `EventLoop::builder`, which is intended to replace the (now deprecated) `EventLoopBuilder::new`.
- **Breaking:** Changed the signature of `EventLoop::with_user_event` to return a builder.
- **Breaking:** Removed `EventLoopBuilder::with_user_event`, the functionality is now available in `EventLoop::with_user_event`.
- Add `Window::default_attributes` to get default `WindowAttributes`.
- `log` has been replaced with `tracing`. The old behavior can be emulated by setting the `log` feature on the `tracing` crate.
# 0.29.12
- On X11, fix use after free during xinput2 handling.
- On X11, filter close to zero values in mouse device events
# 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, 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 `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 Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
- On Orbital, fix `logical_key` and `text` not reported in `KeyEvent`.
- On Orbital, implement `KeyEventExtModifierSupplement`.
- On Orbital, map keys to `NamedKey` when possible.
- On Orbital, implement `set_cursor_grab`.
- On Orbital, implement `set_cursor_visible`.
- On Orbital, implement `drag_window`.
- On Orbital, implement `drag_resize_window`.
- On Orbital, implement `set_transparent`.
- On Orbital, implement `set_visible`.
- On Orbital, implement `is_visible`.
- On Orbital, implement `set_resizable`.
- On Orbital, implement `is_resizable`.
- On Orbital, implement `set_maximized`.
- On Orbital, implement `is_maximized`.
- On Orbital, implement `set_decorations`.
- On Orbital, implement `is_decorated`.
- On Orbital, implement `set_window_level`.
- On Orbital, emit `DeviceEvent::MouseMotion`.
# 0.29.10
- On Web, account for canvas being focused already before event loop starts.
- On Web, increase cursor position accuracy.
# 0.29.9
- On X11, fix `NotSupported` error not propagated when creating event loop.
- On Wayland, fix resize not issued when scale changes
- On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`.
- On macOS, report correct logical key when Ctrl or Cmd is pressed.
# 0.29.8
- On X11, fix IME input lagging behind.
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
- On X11, fix keymap not updated from xmodmap.
- On X11, reduce the amount of time spent fetching screen resources.
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
- On Wayland, fix `Window::inner_size` not using the correct rounding.
# 0.29.7
- On X11, fix `Xft.dpi` reload during runtime.
- On X11, fix window minimize.
# 0.29.6
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
- On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window.
- Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation
# 0.29.5
@@ -144,12 +47,11 @@ Unreleased` header.
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
- On Wayland, fix `wl_surface` being destroyed before associated objects.
- On macOS, fix assertion when pressing `Fn` key.
- On Windows, add `WindowBuilderExtWindows::with_clip_children` to control `WS_CLIPCHILDREN` style.
# 0.29.3
- 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, 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}`.
@@ -401,7 +303,7 @@ Unreleased` header.
- **Breaking:**: Removed deprecated method `platform::unix::WindowExtUnix::is_ready`.
- 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 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, 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.
@@ -421,7 +323,7 @@ Unreleased` header.
- 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 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.
- Added `Window::set_ime_purpose` for setting the IME purpose, currently implemented on Wayland only.
@@ -527,7 +429,7 @@ Unreleased` header.
- On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated.
- On Windows, fix focus events being sent to inactive windows.
- **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`.
- On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors 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`.
- 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.
@@ -826,7 +728,7 @@ Unreleased` header.
- On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`.
- On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode.
- 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 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
@@ -979,7 +881,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, 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, 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)
@@ -1259,7 +1161,7 @@ _Yanked_
# 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
- Added method `os::linux::WindowExt::is_ready`
@@ -1273,7 +1175,7 @@ _Yanked_
- Added `Window::set_maximized`, `WindowAttributes::maximized` and `WindowBuilder::with_maximized`.
- Added `Window::set_fullscreen`.
- 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.
- Changed `get_available_monitors()` and `get_primary_monitor()` to be methods of `EventsLoop`
instead of stand-alone methods.

View File

@@ -1,169 +1,59 @@
# Contribution Guidelines
# Winit Contributing Guidelines
This document contains guidelines for contributing code to winit. It has to be
followed in order for your patch to be approved and applied.
## Scope
[See `FEATURES.md`](./FEATURES.md). When requesting or implementing a new Winit feature, you should
consider whether or not it's directly related to window creation or input handling. If it isn't, it
may be worth creating a separate crate that extends Winit's API to add that functionality.
## Contributing
Anyone can contribute to winit, however given that it's a cross platform
windowing toolkit getting certain changes incorporated upstream could be
challenging.
## Reporting an issue
To save your time it's wise to check already opened [pull requests][prs] and
[issues][issues]. In general, bug fixes and missing implementations are always
accepted, however new API proposals should go into the issue first. When in
doubt contact us on [matrix][matrix] or via opening an issue.
When reporting an issue, in order to help the maintainers understand what the problem is, please make
your description of the issue as detailed as possible:
### Submitting your work and handling review
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
reproduce the issue, ideally by providing a minimal program exhibiting the problem
- if it is a feature request, please provide a clear argumentation about why you believe this feature
should be supported by winit
All patches have to be sent on Github as [pull requests][prs]. To simplify your
life during review it's recommended to check the "give contributors write access
to the branch" checkbox.
## Making a pull request
#### Creating commits
When making a code contribution to winit, before opening your pull request, please make sure that:
When creating a commit, follow these general rules:
- Try to limit the first line (title) of the commit message to 50 characters,
but not more than 60 characters.
- The commit starts with an uppercase latter and everything else must be
lowercase, unless you quote a symbol, like `Window`.
- Use the body of the commit message to actually explain what your patch does
and why it is useful. Even if your patch is a one line fix, the description
is not limited in length and may span over multiple paragraphs. Use proper
English syntax, grammar and punctuation. The body is generally wrapped
at 72 characters.
- If you're a downstream user and your fixing your issue, consider to link it
like `Links: URL` in the end of the commit message.
- Try to address only one issue/topic per commit.
- Prefer describing your changes in imperative mood, e.g. *"make xyzzy do
frotz"* instead of *"[This patch] makes xyzzy do frotz"* or *"[I] changed
xyzzy to do frotz"*, as if you are giving orders to the codebase to change
its behaviour.
- When fixing an issue from https://github.com/rust-windowing/winit/issues,
ensure to have it include in the end of the commit. See other commits
on how it's done.
- When in doubt, follow the format and layout of the recent existing commits.
- your patch builds with Winit's minimal supported rust version - Rust 1.70.
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit
- you left comments in your code explaining any part that is not straightforward, so that the
maintainers and future contributors don't have to try to guess what your code is supposed to do
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users.
The maintainers will fixup your commit message when applying the patch, but it's
still recommended to follow the rules above to make the life of maintainers
easier.
You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will
handle those for you when merging (see below).
- if your PR affects the platform compatibility of one or more features or adds another feature, the
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
should be updated.
If you want to communicate something to reviewers, but it doesn't belong to
the commit message, write that under `--` on PR body. Your PR could look like:
Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy
is that a PR must be approved by at least two maintainers of winit before being merged, including
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
```
TITLE (matches commit title)
Once your PR is deemed ready, the merging maintainer will take care of resolving conflicts in
`CHANGELOG.md` (but you must resolve other conflicts yourself). Doing this requires that you check the
"give contributors write access to the branch" checkbox when creating the PR.
Body (matches commit body)
## Maintainers & Testers
--
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
Your arbitrary message goes here.
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!
Various checkboxes PR template required you to fill.
```
#### Handling review
During the review process certain events could require an action from your side,
to communication more efficient the common patterns and reactions are described
below.
_Event:_ The CI fails to build, but it looks like not your fault. Not
communicating so, could result into maintainers not looking into your patch,
unless they CI that CI pass.\
_Desired behavior:_ Write a message saying roughly the following "The CI failure
is unrelated", so maintainers will fix it for you.
_Event:_ Collaborator requested review on your PR.\
_Desired behavior:_ Once you address the request, you _should_ re-request review
with the github's UI. If you don't agree with what maintainer suggested, you
should object that and re-request the review. That will indicate that the
_ball_ is on maintainer's side.
_Event:_ You've opened a PR, but maintainer shortly after commented that they
want to work on that _themselves_.\
_Desired behavior:_ Discusses with maintainer their plans if they were not
outlined in the initial response, because such response means that they
are not interested in reviewing your code. Such thing could happen when
underestimating complexity of the task you're solving or when your patch
mandate certain downstream designs.
[prs]: https://github.com/rust-windowing/winit/pulls
[issues]: https://github.com/rust-windowing/winit/issues
[matrix]: https://matrix.to/#/#rust-windowing:matrix.org
## Maintainers
Winit has plenty of maintainers with different backgrounds, experience level,
and reasons to be winit maintainer in the first place. To ensure that winit's
code quality is not decreasing over time and to make it easier to teach new
maintainers the _winit way of doing things_ the common policies and routines
are defined in this section.
The current maintainers for each platform are listed in [this file][CODEOWNERS].
### Contributions handling
The maintainer must ensure that the external contributions meet the winit's
quality standards. If it's not, it **is maintainer's responsibility** to bring
it on par, which includes:
- Decline the patch if it tries to achieve short term goals instead of solving
the problem in general. In such case maintainer must communicate that **as
early as possible**, so contributors don't spend their time on something
that won't be accepted in the end.
- Ensure that formatting is consistent and `CHANGELOG` messages are clear
for the end users.
- Improve the commit message, so it'll be easier for other maintainers to
understand the motivation without going through all the discussions on the
particular patch/PR.
- Ensure that the proposed patch doesn't break platform parity. If the
breakage is desired by contributor, an issue should be opened to discuss
with other maintainers before merging.
- Always fix CI issues before merging if they don't originate from the
submitted work.
However, maintainer must always give a leeway for external contributors, so they
don't feel discouraged contributing, for example:
- Suggest a patch to resolve style issues, if it's the only issue with the
submitted work. Keep in mind that pushing the resolution yourself is not
desired, because contributor might not agree with what you did.
- Be more explicit on how things should be done if you doesn't like the
approach.
- Suggest to finish PR for them if they're absent for a while and you need the
proposed changes to move forward with something. In such case maintainer
must preserve attribution with `Co-authored-by`, `Suggested-by`, or keep
the original commiter.
- Rebase their work for them when massive changes to winit codebase were
introduced.
When reviewing code of other maintainers all of the above is on the maintainer
who submitted the patch. Interested maintainers could help push the work over
the finish line, but teaching other maintainers should be preferred.
When contributor is _regular_ in winit, the maintainer should slowly start
requiring contributor to match *maintainer* quality standards when writing
patches and commit messages.
### Contributing
When submitting a patch maintainer should follow the general contributing
guidelines, however all soft rules (e.g `Try to`), become strict.
To make life simpler for other maintainers it's suggested to create your branch
under the project repository instead of your own fork. The naming scheme is
`github_user_name/branch_name`. Doing so will make your work easier to rebase
for other maintainers when you're absent.
### Release process
## Release process
Given that winit is a widely used library, we should be able to make a patch
releases at any time we want without blocking the development of new features.
To achieve these goals, a new branch is created for every new release. Releases
and later patch releases are committed and tagged in this branch.
To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch.
The exact steps for an exemplary `0.2.0` release might look like this:
1. Initially, the version on the latest master is `0.1.0`
@@ -173,11 +63,9 @@ The exact steps for an exemplary `0.2.0` release might look like this:
5. The version is pushed to crates.io
6. A GitHub release is created for the `v0.2.0` tag
7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated
When doing a patch release, the process is similar:
1. Initially, the version of the latest release is `0.2.0`
2. Checkout the `v0.2.x` branch
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
4. Follow steps 3-7 of the regular release example
[CODEOWNERS]: .github/CODEOWNERS

View File

@@ -1,16 +1,16 @@
[package]
name = "winit"
version = "0.29.12"
version = "0.29.5"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
keywords = ["windowing"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
rust-version = "1.70.0"
[package.metadata.docs.rs]
features = [
@@ -18,10 +18,10 @@ features = [
"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
@@ -36,12 +36,11 @@ targets = [
"x86_64-apple-ios",
# Android
"aarch64-linux-android",
# Web
# WebAssembly
"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"]
@@ -52,34 +51,33 @@ 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"]
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
[build-dependencies]
cfg_aliases = "0.2.0"
cfg_aliases = "0.1.1"
[dependencies]
bitflags = "2"
cursor-icon = "1.1.0"
log = "0.4"
mint = { version = "0.5.6", optional = true }
once_cell = "1.12"
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5", features = ["std"], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
serde = { workspace = true, optional = true }
serde = { version = "1", optional = true, features = ["serde_derive"] }
smol_str = "0.2.0"
dpi = { path = "dpi" }
tracing = { version = "0.1.40", default_features = false }
[dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] }
tracing = { version = "0.1.40", default_features = false, features = ["log"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
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"] }
softbuffer = "0.3.0"
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.5.0"
@@ -175,7 +173,7 @@ features = [
]
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
ahash = { version = "0.8.3", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.12.3"
libc = "0.2.64"
@@ -190,11 +188,11 @@ wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = tr
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"
xkbcommon-dl = "0.4.0"
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.4.1"
orbclient = { version = "0.3.42", default-features = false }
redox_syscall = "0.3"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
package = "web-sys"
@@ -206,7 +204,6 @@ features = [
'console',
'CssStyleDeclaration',
'Document',
'DomException',
'DomRect',
'DomRectReadOnly',
'Element',
@@ -242,37 +239,17 @@ features = [
]
[target.'cfg(target_family = "wasm")'.dependencies]
atomic-waker = "1"
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 }
web-time = "0.2"
[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"
[workspace]
resolver = "2"
members = [
"dpi",
"run-wasm",
]
[workspace.package]
rust-version = "1.70.0"
repository = "https://github.com/rust-windowing/winit"
license = "Apache-2.0"
edition = "2021"
[workspace.dependencies]
serde = { version = "1", features = ["serde_derive"] }
mint = "0.5.6"

View File

@@ -3,8 +3,8 @@
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:
- Desktop
- Windows
- macOS
- Windows 7+ (10+ is tested regularly)
- macOS 10.7+ (10.14+ is tested regularly)
- Unix
- via X11
- via Wayland
@@ -13,6 +13,9 @@ be used to create both games and applications. It supports the following main gr
- iOS
- Android
- Web
- Chrome
- Firefox
- Safari 13.1+
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
@@ -123,11 +126,6 @@ If your PR makes notable changes to Winit's features, please update this section
* Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
* Changing a system-drawn backdrop
* Setting the window border color
* Setting the title bar background color
* Setting the title color
* Setting the corner rounding preference
### macOS
* Window activation policy
@@ -148,6 +146,7 @@ If your PR makes notable changes to Winit's features, please update this section
* Setting the X11 parent window
### iOS
* `winit` has a minimum OS requirement of iOS 8
* Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor
* Valid orientations
@@ -160,6 +159,9 @@ If your PR makes notable changes to Winit's features, please update this section
### Web
* Get if the systems preferred color scheme is "dark"
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
## Compatibility Matrix
Legend:

133
README.md
View File

@@ -2,13 +2,11 @@
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs
)](https://rust-windowing.github.io/winit/winit/index.html)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
```toml
[dependencies]
winit = "0.29.12"
winit = "0.29.5"
```
## [Documentation](https://docs.rs/winit)
@@ -19,9 +17,10 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
## Contact Us
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit).
Join us in any of these:
The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
[![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org)
[![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit)
## Usage
@@ -33,6 +32,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
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
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
@@ -63,4 +70,118 @@ same MSRV policy.
### 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.
#### WebAssembly
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 with WebAssembly, check out the [web example]. For
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
book].
[web with_canvas]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowBuilderExtWebSys.html#tymethod.with_canvas
[web canvas getter]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowExtWebSys.html#tymethod.canvas
[web example]: ./examples/web.rs
[Rust and WebAssembly book]: https://rustwasm.github.io/book/
#### Android
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/rib/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity`
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
1. Remove `ndk-glue` from your `Cargo.toml`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.5", features = [ "android-native-activity" ] }`
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

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

View File

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

View File

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

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,3 +1,10 @@
#[cfg(all(
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
))]
#[path = "util/fill.rs"]
mod fill;
#[cfg(all(
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
@@ -6,57 +13,57 @@
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
use winit::dpi::{LogicalPosition, LogicalSize, Position};
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::Window;
use winit::{
dpi::{LogicalPosition, LogicalSize, Position},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{EventLoop, EventLoopWindowTarget},
raw_window_handle::HasWindowHandle,
window::{Window, WindowBuilder, WindowId},
};
#[path = "util/fill.rs"]
mod fill;
fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window {
let parent = parent.raw_window_handle().unwrap();
let mut window_attributes = Window::default_attributes()
fn spawn_child_window(
parent: &Window,
event_loop: &EventLoopWindowTarget<()>,
windows: &mut HashMap<WindowId, Window>,
) {
let parent = parent.window_handle().unwrap();
let mut builder = WindowBuilder::new()
.with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window.
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };
builder = 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 event_loop: EventLoop<()> = EventLoop::new().unwrap();
let mut parent_window_id = None;
let parent_window = WindowBuilder::new()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32))
.build(&event_loop)
.unwrap();
event_loop.run(move |event: Event<()>, event_loop| {
match event {
Event::Resumed => {
let attributes = Window::default_attributes()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("parent window: {parent_window:?})");
parent_window_id = Some(window.id());
println!("Parent window id: {parent_window_id:?})");
windows.insert(window.id(), window);
}
Event::WindowEvent { window_id, event } => match event {
event_loop.run(move |event: Event<()>, elwt| {
if let Event::WindowEvent { event, window_id } = event {
match event {
WindowEvent::CloseRequested => {
windows.clear();
event_loop.exit();
elwt.exit();
}
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window is created
// by some key inputs.
// 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:?}");
}
WindowEvent::KeyboardInput {
@@ -67,11 +74,7 @@ fn main() -> Result<(), impl std::error::Error> {
},
..
} => {
let parent_window = windows.get(&parent_window_id.unwrap()).unwrap();
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);
spawn_child_window(&parent_window, elwt, &mut windows);
}
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
@@ -79,16 +82,15 @@ fn main() -> Result<(), impl std::error::Error> {
}
}
_ => (),
},
_ => (),
}
}
})
}
#[cfg(all(
#[cfg(not(all(
feature = "rwh_06",
not(any(x11_platform, macos_platform, windows_platform))
))]
any(x11_platform, macos_platform, windows_platform)
)))]
fn main() {
panic!("This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature enabled.");
}

View File

@@ -1,16 +1,17 @@
#![allow(clippy::single_match)]
use std::thread;
#[cfg(not(web_platform))]
#[cfg(not(wasm_platform))]
use std::time;
#[cfg(web_platform)]
#[cfg(wasm_platform)]
use web_time as time;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::{Key, NamedKey},
window::Window,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
@@ -27,7 +28,7 @@ const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
fn main() -> Result<(), impl std::error::Error> {
tracing_subscriber::fmt::init();
SimpleLogger::new().init().unwrap();
println!("Press '1' to switch to Wait mode.");
println!("Press '2' to switch to WaitUntil mode.");
@@ -36,14 +37,17 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Press 'Esc' to close the window.");
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
.build(&event_loop)
.unwrap();
let mut mode = Mode::Wait;
let mut request_redraw = false;
let mut wait_cancelled = false;
let mut close_requested = false;
let mut window = None;
event_loop.run(move |event, event_loop| {
event_loop.run(move |event, elwt| {
use winit::event::StartCause;
println!("{event:?}");
match event {
@@ -53,12 +57,6 @@ fn main() -> Result<(), impl std::error::Error> {
_ => 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 {
WindowEvent::CloseRequested => {
close_requested = true;
@@ -72,7 +70,7 @@ fn main() -> Result<(), impl std::error::Error> {
},
..
} => 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
Key::Character("1") => {
mode = Mode::Wait;
@@ -96,34 +94,32 @@ fn main() -> Result<(), impl std::error::Error> {
_ => (),
},
WindowEvent::RedrawRequested => {
let window = window.as_ref().unwrap();
window.pre_present_notify();
fill::fill_window(window);
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
if request_redraw && !wait_cancelled && !close_requested {
window.as_ref().unwrap().request_redraw();
window.request_redraw();
}
match mode {
Mode::Wait => event_loop.set_control_flow(ControlFlow::Wait),
Mode::Wait => elwt.set_control_flow(ControlFlow::Wait),
Mode::WaitUntil => {
if !wait_cancelled {
event_loop.set_control_flow(ControlFlow::WaitUntil(
elwt.set_control_flow(ControlFlow::WaitUntil(
time::Instant::now() + WAIT_TIME,
));
}
}
Mode::Poll => {
thread::sleep(POLL_SLEEP_TIME);
event_loop.set_control_flow(ControlFlow::Poll);
elwt.set_control_flow(ControlFlow::Poll);
}
};
if close_requested {
event_loop.exit();
elwt.exit();
}
}
_ => (),

88
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, WindowBuilder},
};
#[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 = WindowBuilder::new().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_icon(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,
];

73
examples/cursor_grab.rs Normal file
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, WindowBuilder},
};
#[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 = WindowBuilder::new()
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
.build(&event_loop)
.unwrap();
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, 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,94 @@
#![allow(clippy::single_match, clippy::disallowed_methods)]
#[cfg(not(wasm_platform))]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{EventLoop, EventLoopWindowTarget},
keyboard::Key,
window::{CustomCursor, WindowBuilder},
};
fn decode_cursor<T>(bytes: &[u8], window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
let samples = img.into_flat_samples();
let (_, w, h) = samples.extents();
let (w, h) = (w as u16, h as u16);
let builder = CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap();
builder.build(window_target)
}
#[cfg(not(wasm_platform))]
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
#[cfg(not(wasm_platform))]
SimpleLogger::new()
.with_level(log::LevelFilter::Info)
.init()
.unwrap();
#[cfg(wasm_platform)]
console_log::init_with_level(log::Level::Debug).unwrap();
let event_loop = EventLoop::new().unwrap();
let builder = WindowBuilder::new().with_title("A fantastic window!");
#[cfg(wasm_platform)]
let builder = {
use winit::platform::web::WindowBuilderExtWebSys;
builder.with_append(true)
};
let window = builder.build(&event_loop).unwrap();
let mut cursor_idx = 0;
let mut cursor_visible = true;
let custom_cursors = [
decode_cursor(include_bytes!("data/cross.png"), &event_loop),
decode_cursor(include_bytes!("data/cross2.png"), &event_loop),
];
event_loop.run(move |event, _elwt| match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key: key,
..
},
..
} => match key.as_ref() {
Key::Character("1") => {
log::debug!("Setting cursor to {:?}", cursor_idx);
window.set_custom_cursor(&custom_cursors[cursor_idx]);
cursor_idx = (cursor_idx + 1) % 2;
}
Key::Character("2") => {
log::debug!("Setting cursor icon to default");
window.set_cursor_icon(Default::default());
}
Key::Character("3") => {
cursor_visible = !cursor_visible;
log::debug!("Setting cursor visibility to {:?}", cursor_visible);
window.set_cursor_visible(cursor_visible);
}
_ => {}
},
WindowEvent::RedrawRequested => {
#[cfg(not(wasm_platform))]
fill::fill_window(&window);
}
WindowEvent::CloseRequested => {
#[cfg(not(wasm_platform))]
_elwt.exit();
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
_ => {}
})
}

62
examples/custom_events.rs Normal file
View File

@@ -0,0 +1,62 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
fn main() -> Result<(), impl std::error::Error> {
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoopBuilder,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
#[derive(Debug, Clone, Copy)]
enum CustomEvent {
Timer,
}
SimpleLogger::new().init().unwrap();
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event()
.build()
.unwrap();
let window = WindowBuilder::new()
.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(wasm_platform)]
fn main() {
panic!("This example is not supported on web.");
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

105
examples/drag_window.rs Normal file
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, WindowBuilder, 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 = WindowBuilder::new().build(&event_loop).unwrap();
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
let mut switched = false;
let mut entered_id = window_2.id();
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
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(wasm_platform))]
use std::time;
#[cfg(wasm_platform)]
use web_time as time;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
#[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 = WindowBuilder::new()
.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));
})
}

163
examples/fullscreen.rs Normal file
View File

@@ -0,0 +1,163 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::dpi::PhysicalSize;
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::EventLoop;
use winit::keyboard::{Key, NamedKey};
use winit::window::{Fullscreen, WindowBuilder};
#[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 = WindowBuilder::new()
.with_title("Hello world!")
.build(&event_loop)
.unwrap();
let mut monitor_index = 0;
let mut monitor = event_loop
.available_monitors()
.next()
.expect("no monitor found!");
println!("Monitor: {:?}", monitor.name());
let mut mode_index = 0;
let mut mode = monitor.video_modes().next().expect("no mode found");
println!("Mode: {mode}");
println!("Keys:");
println!("- Esc\tExit");
println!("- F\tToggle exclusive fullscreen mode");
println!("- B\tToggle borderless mode");
#[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(PhysicalSize::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(PhysicalSize::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::WindowBuilder,
};
#[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 = WindowBuilder::new()
.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
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, WindowBuilder},
};
#[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 = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
.build(&event_loop)
.unwrap();
let mut ime_purpose = ImePurpose::Normal;
let mut ime_allowed = true;
window.set_ime_allowed(ime_allowed);
let mut may_show_ime = false;
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
let mut ime_pos = PhysicalPosition::new(0.0, 0.0);
event_loop.run(move |event, 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);
}
_ => (),
}
}
})
}

62
examples/key_binding.rs Normal file
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::WindowBuilder,
};
#[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 = WindowBuilder::new()
.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);
}
_ => (),
}
};
})
}

58
examples/monitor_list.rs Normal file
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::WindowBuilder};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().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
);
}
}

65
examples/mouse_wheel.rs Normal file
View File

@@ -0,0 +1,65 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
#[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 = WindowBuilder::new()
.with_title("Mouse Wheel events")
.build(&event_loop)
.unwrap();
println!(
r"
When using so called 'natural scrolling' (scrolling that acts like on a touch screen), this is what to expect:
Moving your finger downwards on a scroll wheel should make the window move down, and you should see a positive Y scroll value.
When moving fingers on a trackpad down and to the right, you should see positive X and Y deltas, and the window should move down and to the right.
With reverse scrolling, you should see the inverse behavior.
In both cases the example window should move like the content of a scroll area in any other application.
In other words, the deltas indicate the direction in which to move the content (in this case the window)."
);
event_loop.run(move |event, 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);
}
_ => (),
}
}
})
}

209
examples/multithreaded.rs Normal file
View File

@@ -0,0 +1,209 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_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, WindowBuilder, WindowLevel},
};
const WINDOW_COUNT: usize = 3;
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
for _ in 0..WINDOW_COUNT {
let window = WindowBuilder::new()
.with_inner_size(WINDOW_SIZE)
.build(&event_loop)
.unwrap();
let mut video_modes: Vec<_> = window.current_monitor().unwrap().video_modes().collect();
let mut video_mode_id = 0usize;
let (tx, rx) = mpsc::channel();
window_senders.insert(window.id(), tx);
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_icon(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,
}),
"l" if state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked)
{
println!("error: {err}");
}
}
"g" if state => {
if let Err(err) =
window.set_cursor_grab(CursorGrabMode::Confined)
{
println!("error: {err}");
}
}
"g" | "l" if !state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
println!("error: {err}");
}
}
"h" => window.set_cursor_visible(!state),
"i" => {
println!("Info:");
println!("-> outer_position : {:?}", window.outer_position());
println!("-> inner_position : {:?}", window.inner_position());
println!("-> outer_size : {:?}", window.outer_size());
println!("-> inner_size : {:?}", window.inner_size());
println!("-> fullscreen : {:?}", window.fullscreen());
}
"l" => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE),
false => None,
}),
"m" => window.set_maximized(state),
"p" => window.set_outer_position({
let mut position = window.outer_position().unwrap();
let sign = if state { 1 } else { -1 };
position.x += 10 * sign;
position.y += 10 * sign;
position
}),
"q" => window.request_redraw(),
"r" => window.set_resizable(state),
"s" => {
let _ = window.request_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
),
false => WINDOW_SIZE,
});
}
"w" => {
if let Size::Physical(size) = WINDOW_SIZE.into() {
window
.set_cursor_position(Position::Physical(
PhysicalPosition::new(
size.width as i32 / 2,
size.height as i32 / 2,
),
))
.unwrap()
}
}
"z" => {
window.set_visible(false);
thread::sleep(Duration::from_secs(1));
window.set_visible(true);
}
_ => (),
},
_ => (),
}
}
_ => (),
}
}
});
}
event_loop.run(move |event, 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(wasm_platform)]
fn main() {
panic!("Example not supported on Wasm");
}

64
examples/multiwindow.rs Normal file
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::WindowBuilder,
};
#[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 = WindowBuilder::new()
.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(wasm_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::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = {
let window = WindowBuilder::new()
.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(wasm_platform)]
fn main() {
unimplemented!() // `Window` can't be sent between threads
}

54
examples/resizable.rs Normal file
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::WindowBuilder,
};
#[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 = WindowBuilder::new()
.with_title("Hit space to toggle resizability.")
.with_inner_size(LogicalSize::new(600.0, 300.0))
.with_min_inner_size(LogicalSize::new(400.0, 200.0))
.with_max_inner_size(LogicalSize::new(800.0, 400.0))
.with_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,116 @@
//! 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, WindowBuilder, 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 =
WindowBuilder::new().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
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, WindowBuilder},
};
#[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 = WindowBuilder::new()
.with_title("A fantastic window!")
.with_theme(Some(Theme::Dark))
.build(&event_loop)
.unwrap();
println!("Initial theme: {:?}", window.theme());
println!("debugging keys:");
println!(" (A) Automatic theme");
println!(" (L) Light theme");
println!(" (D) Dark theme");
event_loop.run(move |event, 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
examples/timer.rs Normal file
View File

@@ -0,0 +1,54 @@
#![allow(clippy::single_match)]
use std::time::Duration;
#[cfg(not(wasm_platform))]
use std::time::Instant;
#[cfg(wasm_platform)]
use web_time::Instant;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
#[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 = WindowBuilder::new()
.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,50 @@
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
#[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 = WindowBuilder::new()
.with_title("Touchpad gestures")
.build(&event_loop)
.unwrap();
println!("Only supported on macOS at the moment.");
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::TouchpadMagnify { delta, .. } => {
if delta > 0.0 {
println!("Zoomed in {delta}");
} else {
println!("Zoomed out {delta}");
}
}
WindowEvent::SmartMagnify { .. } => {
println!("Smart zoom");
}
WindowEvent::TouchpadRotate { delta, .. } => {
if delta > 0.0 {
println!("Rotated counterclockwise {delta}");
} else {
println!("Rotated clockwise {delta}");
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
}
})
}

38
examples/transparent.rs Normal file
View File

@@ -0,0 +1,38 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
#[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 = WindowBuilder::new()
.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

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

22
examples/video_modes.rs Normal file
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
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, WindowBuilder},
};
pub fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap();
let builder = WindowBuilder::new().with_title("A fantastic window!");
#[cfg(wasm_platform)]
let builder = {
use winit::platform::web::WindowBuilderExtWebSys;
builder.with_append(true)
};
let window = builder.build(&event_loop).unwrap();
#[cfg(wasm_platform)]
let log_list = wasm::insert_canvas_and_create_log_list(&window);
event_loop.run(move |event, elwt| {
#[cfg(wasm_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(wasm_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(wasm_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, WindowBuilder},
};
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 = WindowBuilder::new()
.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();
}
}

File diff suppressed because it is too large Load Diff

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::{WindowBuilder, 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 = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(300.0, 300.0))
.build(&event_loop)
.unwrap();
eprintln!("Window Button keys:");
eprintln!(" (F) Toggle close button");
eprintln!(" (G) Toggle maximize button");
eprintln!(" (H) Toggle minimize button");
event_loop.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);
}
_ => (),
}
}
})
}

137
examples/window_debug.rs Normal file
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, WindowBuilder},
};
#[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 = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(100.0, 100.0))
.build(&event_loop)
.unwrap();
eprintln!("debugging keys:");
eprintln!(" (E) Enter exclusive fullscreen");
eprintln!(" (F) Toggle borderless fullscreen");
eprintln!(" (P) Toggle borderless fullscreen on system's preffered monitor");
eprintln!(" (M) Toggle minimized");
eprintln!(" (Q) Quit event loop");
eprintln!(" (V) Toggle visibility");
eprintln!(" (X) Toggle maximized");
let mut minimized = false;
let mut visible = true;
event_loop.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, WindowBuilder},
};
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 = WindowBuilder::new()
.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_icon(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,
},
})
}

60
examples/window_icon.rs Normal file
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, WindowBuilder},
};
#[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 = WindowBuilder::new()
.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

@@ -5,12 +5,14 @@
fn main() -> Result<(), impl std::error::Error> {
use std::time::Duration;
use simple_logger::SimpleLogger;
use winit::{
error::EventLoopError,
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::run_on_demand::EventLoopExtRunOnDemand,
window::{Window, WindowId},
window::{Window, WindowBuilder, WindowId},
};
#[path = "util/fill.rs"]
@@ -22,13 +24,13 @@ fn main() -> Result<(), impl std::error::Error> {
window: Option<Window>,
}
tracing_subscriber::fmt::init();
SimpleLogger::new().init().unwrap();
let mut event_loop = EventLoop::new().unwrap();
fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> {
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);
if let Some(window) = &app.window {
@@ -38,7 +40,6 @@ fn main() -> Result<(), impl std::error::Error> {
window_id,
} if window.id() == window_id => {
println!("--------------------------------------------------------- Window {idx} CloseRequested");
fill::cleanup_window(window);
app.window = None;
}
Event::AboutToWait => window.request_redraw(),
@@ -58,15 +59,16 @@ fn main() -> Result<(), impl std::error::Error> {
} if id == window_id => {
println!("--------------------------------------------------------- Window {idx} Destroyed");
app.window_id = None;
event_loop.exit();
elwt.exit();
}
_ => (),
}
} else if let Event::Resumed = event {
let window_attributes = Window::default_attributes()
let window = WindowBuilder::new()
.with_title("Fantastic window number one!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0));
let window = event_loop.create_window(window_attributes).unwrap();
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(elwt)
.unwrap();
app.window_id = Some(window.id());
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::WindowBuilder,
};
#[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 = WindowBuilder::new()
.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

@@ -11,11 +11,12 @@
fn main() -> std::process::ExitCode {
use std::{process::ExitCode, thread::sleep, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
window::Window,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
@@ -23,41 +24,39 @@ fn main() -> std::process::ExitCode {
let mut event_loop = EventLoop::new().unwrap();
tracing_subscriber::fmt::init();
SimpleLogger::new().init().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let mut window = None;
loop {
'main: loop {
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 {
// Print only Window events to reduce noise
println!("{event:?}");
}
match event {
Event::Resumed => {
let window_attributes =
Window::default_attributes().with_title("A fantastic window!");
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 => fill::fill_window(window),
_ => (),
}
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => elwt.exit(),
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 {
break ExitCode::from(exit_code as u8);
break 'main ExitCode::from(exit_code as u8);
}
// Sleep for 1/60 second to simulate application work
@@ -69,7 +68,7 @@ fn main() -> std::process::ExitCode {
}
}
#[cfg(any(ios_platform, web_platform, orbital_platform))]
#[cfg(any(ios_platform, wasm_platform, orbital_platform))]
fn main() {
println!("This platform doesn't support pump_events.");
}

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::WindowBuilder,
};
#[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 = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(128.0, 128.0))
.with_resize_increments(LogicalSize::new(25.0, 25.0))
.build(&event_loop)
.unwrap();
let mut has_increments = true;
event_loop.run(move |event, 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(),
_ => (),
})
}

107
examples/window_tabbing.rs Normal file
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, WindowBuilder},
};
#[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 = WindowBuilder::new()
.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

@@ -1,60 +1,70 @@
//! 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>> {
#[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::WindowAttributesExtX11,
window::Window,
platform::x11::WindowBuilderExtX11,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
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>()?;
// 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()?;
tracing_subscriber::fmt::init();
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();
let window = WindowBuilder::new()
.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 {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
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);
fill::fill_window(&window);
}
_ => (),
}
}
Event::AboutToWait => {
window.as_ref().unwrap().request_redraw();
}
_ => (),
})?;
})?;
Ok(())
Ok(())
}
}
#[cfg(not(x11_platform))]
fn main() -> Result<(), Box<dyn Error>> {
println!("This example is only supported on X11 platforms.");
Ok(())
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

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

View File

@@ -3,44 +3,16 @@ use std::hash::Hasher;
use std::sync::Arc;
use std::{error::Error, hash::Hash};
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`].
pub const MAX_CURSOR_SIZE: u16 = 2048;
const PIXEL_SIZE: usize = 4;
/// See [`Window::set_cursor()`](crate::window::Window::set_cursor) for more details.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum Cursor {
Icon(CursorIcon),
Custom(CustomCursor),
}
impl Default for Cursor {
fn default() -> Self {
Self::Icon(CursorIcon::default())
}
}
impl From<CursorIcon> for Cursor {
fn from(icon: CursorIcon) -> Self {
Self::Icon(icon)
}
}
impl From<CustomCursor> for Cursor {
fn from(custom: CustomCursor) -> Self {
Self::Custom(custom)
}
}
/// Use a custom image as a cursor (mouse pointer).
///
/// Is guaranteed to be cheap to clone.
///
/// ## Platform-specific
///
/// **Web**: Some browsers have limits on cursor sizes usually at 128x128.
@@ -48,28 +20,31 @@ impl From<CustomCursor> for Cursor {
/// # Example
///
/// ```no_run
/// # use winit::event_loop::ActiveEventLoop;
/// # use winit::window::Window;
/// # fn scope(event_loop: &ActiveEventLoop, window: &Window) {
/// use winit::window::CustomCursor;
/// use winit::{
/// event::{Event, WindowEvent},
/// event_loop::{ControlFlow, EventLoop},
/// window::{CustomCursor, Window},
/// };
///
/// let mut event_loop = EventLoop::new().unwrap();
///
/// let w = 10;
/// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize];
///
/// #[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")]
/// let source = {
/// let builder = {
/// use winit::platform::web::CustomCursorExtWebSys;
/// 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);
///
/// window.set_cursor(custom_cursor.clone());
/// # }
/// let window = Window::new(&event_loop).unwrap();
/// window.set_custom_cursor(&custom_cursor);
/// ```
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CustomCursor {
@@ -80,25 +55,19 @@ pub struct CustomCursor {
impl CustomCursor {
/// Creates a new cursor from an rgba buffer.
///
/// The alpha channel is assumed to be **not** premultiplied.
/// ## Platform-specific
///
/// - **Web:** Setting cursor could be delayed due to the creation of `Blob` objects,
/// which are async by nature.
pub fn from_rgba(
rgba: impl Into<Vec<u8>>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<CustomCursorSource, BadImage> {
let _span = tracing::debug_span!(
"winit::Cursor::from_rgba",
width,
height,
hotspot_x,
hotspot_y
)
.entered();
Ok(CustomCursorSource {
inner: PlatformCustomCursorSource::from_rgba(
) -> Result<CustomCursorBuilder, BadImage> {
Ok(CustomCursorBuilder {
inner: PlatformCustomCursorBuilder::from_rgba(
rgba.into(),
width,
height,
@@ -109,12 +78,20 @@ impl CustomCursor {
}
}
/// Source for [`CustomCursor`].
/// Builds a [`CustomCursor`].
///
/// See [`CustomCursor`] for more details.
#[derive(Debug)]
pub struct CustomCursorSource {
pub(crate) inner: PlatformCustomCursorSource,
pub struct CustomCursorBuilder {
pub(crate) inner: PlatformCustomCursorBuilder,
}
impl CustomCursorBuilder {
pub fn build<T>(self, window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
CustomCursor {
inner: PlatformCustomCursor::build(self.inner, &window_target.p),
}
}
}
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
@@ -175,14 +152,12 @@ impl fmt::Display for BadImage {
impl Error for BadImage {}
/// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with
/// images.
#[allow(dead_code)]
/// Platforms export this directly as `PlatformCustomCursorBuilder` if they need to only work with images.
#[derive(Debug)]
pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
pub(crate) struct OnlyCursorImageBuilder(pub(crate) CursorImage);
#[allow(dead_code)]
impl OnlyCursorImageSource {
impl OnlyCursorImageBuilder {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
@@ -212,6 +187,16 @@ impl PartialEq for OnlyCursorImage {
impl Eq for OnlyCursorImage {}
#[allow(dead_code)]
impl OnlyCursorImage {
fn build<T>(
builder: OnlyCursorImageBuilder,
_: &platform_impl::EventLoopWindowTarget<T>,
) -> Self {
Self(Arc::new(builder.0))
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub(crate) struct CursorImage {
@@ -286,4 +271,8 @@ impl NoCustomCursor {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
Ok(Self)
}
fn build<T>(self, _: &platform_impl::EventLoopWindowTarget<T>) -> NoCustomCursor {
self
}
}

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?
//!
@@ -35,11 +35,14 @@
//!
//! ### 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
//! 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
//! 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
@@ -48,27 +51,58 @@
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
//! 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
//! [Serde](https://crates.io/crates/serde).
//! * `mint`: Enables mint (math interoperability standard types) conversions.
//! ## How is the scale factor calculated?
//!
//! 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)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
#![cfg_attr(
docsrs,
feature(doc_auto_cfg, doc_cfg_hide),
doc(cfg_hide(doc, docsrs))
)]
#![forbid(unsafe_code)]
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged
//! [`window.scale_factor()`]: crate::window::Window::scale_factor
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
pub trait Pixel: Copy + Into<f64> {
fn from_f64(f: f64) -> Self;
@@ -131,7 +165,7 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
/// A position represented in logical pixels.
///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
/// fractional part, which can cause 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.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -550,13 +584,15 @@ impl<P: Pixel> From<LogicalPosition<P>> for Position {
#[cfg(test)]
mod tests {
use super::*;
use crate::dpi;
use std::collections::HashSet;
macro_rules! test_pixel_int_impl {
($($name:ident => $ty:ty),*) => {$(
#[test]
fn $name() {
use dpi::Pixel;
assert_eq!(
<$ty as Pixel>::from_f64(37.0),
37,
@@ -625,6 +661,8 @@ mod tests {
($($name:ident => $ty:ty),*) => {$(
#[test]
fn $name() {
use dpi::Pixel;
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.0),
37.0,
@@ -717,40 +755,46 @@ mod tests {
#[test]
fn test_validate_scale_factor() {
assert!(validate_scale_factor(1.0));
assert!(validate_scale_factor(2.0));
assert!(validate_scale_factor(3.0));
assert!(validate_scale_factor(1.5));
assert!(validate_scale_factor(0.5));
assert!(dpi::validate_scale_factor(1.0));
assert!(dpi::validate_scale_factor(2.0));
assert!(dpi::validate_scale_factor(3.0));
assert!(dpi::validate_scale_factor(1.5));
assert!(dpi::validate_scale_factor(0.5));
assert!(!validate_scale_factor(0.0));
assert!(!validate_scale_factor(-1.0));
assert!(!validate_scale_factor(f64::INFINITY));
assert!(!validate_scale_factor(f64::NAN));
assert!(!validate_scale_factor(f64::NEG_INFINITY));
assert!(!dpi::validate_scale_factor(0.0));
assert!(!dpi::validate_scale_factor(-1.0));
assert!(!dpi::validate_scale_factor(f64::INFINITY));
assert!(!dpi::validate_scale_factor(f64::NAN));
assert!(!dpi::validate_scale_factor(f64::NEG_INFINITY));
}
#[test]
fn test_logical_position() {
let log_pos = LogicalPosition::new(1.0, 2.0);
assert_eq!(log_pos.to_physical::<u32>(1.0), PhysicalPosition::new(1, 2));
assert_eq!(log_pos.to_physical::<u32>(2.0), PhysicalPosition::new(2, 4));
assert_eq!(log_pos.cast::<u32>(), LogicalPosition::new(1, 2));
let log_pos = dpi::LogicalPosition::new(1.0, 2.0);
assert_eq!(
log_pos.to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
log_pos.to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(2, 4)
);
assert_eq!(log_pos.cast::<u32>(), dpi::LogicalPosition::new(1, 2));
assert_eq!(
log_pos,
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!(
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!(
LogicalPosition::from((2.0, 2.0)),
LogicalPosition::new(2.0, 2.0)
dpi::LogicalPosition::from((2.0, 2.0)),
dpi::LogicalPosition::new(2.0, 2.0)
);
assert_eq!(
LogicalPosition::from([2.0, 3.0]),
LogicalPosition::new(2.0, 3.0)
dpi::LogicalPosition::from([2.0, 3.0]),
dpi::LogicalPosition::new(2.0, 3.0)
);
let x: (f64, f64) = log_pos.into();
@@ -762,44 +806,56 @@ mod tests {
#[test]
fn test_physical_position() {
assert_eq!(
PhysicalPosition::from_logical(LogicalPosition::new(1.0, 2.0), 1.0),
PhysicalPosition::new(1, 2)
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(1.0, 2.0), 1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
PhysicalPosition::from_logical(LogicalPosition::new(2.0, 4.0), 0.5),
PhysicalPosition::new(1, 2)
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(2.0, 4.0), 0.5),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
PhysicalPosition::from((2.0, 2.0)),
PhysicalPosition::new(2.0, 2.0)
dpi::PhysicalPosition::from((2.0, 2.0)),
dpi::PhysicalPosition::new(2.0, 2.0)
);
assert_eq!(
PhysicalPosition::from([2.0, 3.0]),
PhysicalPosition::new(2.0, 3.0)
dpi::PhysicalPosition::from([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));
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]);
}
#[test]
fn test_logical_size() {
let log_size = LogicalSize::new(1.0, 2.0);
assert_eq!(log_size.to_physical::<u32>(1.0), PhysicalSize::new(1, 2));
assert_eq!(log_size.to_physical::<u32>(2.0), PhysicalSize::new(2, 4));
assert_eq!(log_size.cast::<u32>(), LogicalSize::new(1, 2));
let log_size = dpi::LogicalSize::new(1.0, 2.0);
assert_eq!(
log_size.to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
log_size.to_physical::<u32>(2.0),
dpi::PhysicalSize::new(2, 4)
);
assert_eq!(log_size.cast::<u32>(), dpi::LogicalSize::new(1, 2));
assert_eq!(
log_size,
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!(
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();
assert_eq!(x, (1.0, 2.0));
@@ -810,130 +866,136 @@ mod tests {
#[test]
fn test_physical_size() {
assert_eq!(
PhysicalSize::from_logical(LogicalSize::new(1.0, 2.0), 1.0),
PhysicalSize::new(1, 2)
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(1.0, 2.0), 1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
PhysicalSize::from_logical(LogicalSize::new(2.0, 4.0), 0.5),
PhysicalSize::new(1, 2)
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(2.0, 4.0), 0.5),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::PhysicalSize::from((2.0, 2.0)),
dpi::PhysicalSize::new(2.0, 2.0)
);
assert_eq!(
dpi::PhysicalSize::from([2.0, 3.0]),
dpi::PhysicalSize::new(2.0, 3.0)
);
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));
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]);
}
#[test]
fn test_size() {
assert_eq!(
Size::new(PhysicalSize::new(1, 2)),
Size::Physical(PhysicalSize::new(1, 2))
dpi::Size::new(dpi::PhysicalSize::new(1, 2)),
dpi::Size::Physical(dpi::PhysicalSize::new(1, 2))
);
assert_eq!(
Size::new(LogicalSize::new(1.0, 2.0)),
Size::Logical(LogicalSize::new(1.0, 2.0))
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)),
dpi::Size::Logical(dpi::LogicalSize::new(1.0, 2.0))
);
assert_eq!(
Size::new(PhysicalSize::new(1, 2)).to_logical::<f64>(1.0),
LogicalSize::new(1.0, 2.0)
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(1.0),
dpi::LogicalSize::new(1.0, 2.0)
);
assert_eq!(
Size::new(PhysicalSize::new(1, 2)).to_logical::<f64>(2.0),
LogicalSize::new(0.5, 1.0)
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(2.0),
dpi::LogicalSize::new(0.5, 1.0)
);
assert_eq!(
Size::new(LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0),
LogicalSize::new(1.0, 2.0)
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0),
dpi::LogicalSize::new(1.0, 2.0)
);
assert_eq!(
Size::new(PhysicalSize::new(1, 2)).to_physical::<u32>(1.0),
PhysicalSize::new(1, 2)
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
Size::new(PhysicalSize::new(1, 2)).to_physical::<u32>(2.0),
PhysicalSize::new(1, 2)
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(2.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
Size::new(LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0),
PhysicalSize::new(1, 2)
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
Size::new(LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0),
PhysicalSize::new(2, 4)
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0),
dpi::PhysicalSize::new(2, 4)
);
let small = Size::Physical((1, 2).into());
let medium = Size::Logical((3, 4).into());
let medium_physical = Size::new(medium.to_physical::<u32>(1.0));
let large = Size::Physical((5, 6).into());
assert_eq!(Size::clamp(medium, small, large, 1.0), medium_physical);
assert_eq!(Size::clamp(small, medium, large, 1.0), medium_physical);
assert_eq!(Size::clamp(large, small, medium, 1.0), medium_physical);
let small = dpi::Size::Physical((1, 2).into());
let medium = dpi::Size::Logical((3, 4).into());
let medium_physical = dpi::Size::new(medium.to_physical::<u32>(1.0));
let large = dpi::Size::Physical((5, 6).into());
assert_eq!(dpi::Size::clamp(medium, small, large, 1.0), medium_physical);
assert_eq!(dpi::Size::clamp(small, medium, large, 1.0), medium_physical);
assert_eq!(dpi::Size::clamp(large, small, medium, 1.0), medium_physical);
}
#[test]
fn test_position() {
assert_eq!(
Position::new(PhysicalPosition::new(1, 2)),
Position::Physical(PhysicalPosition::new(1, 2))
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)),
dpi::Position::Physical(dpi::PhysicalPosition::new(1, 2))
);
assert_eq!(
Position::new(LogicalPosition::new(1.0, 2.0)),
Position::Logical(LogicalPosition::new(1.0, 2.0))
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)),
dpi::Position::Logical(dpi::LogicalPosition::new(1.0, 2.0))
);
assert_eq!(
Position::new(PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0),
LogicalPosition::new(1.0, 2.0)
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0),
dpi::LogicalPosition::new(1.0, 2.0)
);
assert_eq!(
Position::new(PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0),
LogicalPosition::new(0.5, 1.0)
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0),
dpi::LogicalPosition::new(0.5, 1.0)
);
assert_eq!(
Position::new(LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0),
LogicalPosition::new(1.0, 2.0)
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0),
dpi::LogicalPosition::new(1.0, 2.0)
);
assert_eq!(
Position::new(PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0),
PhysicalPosition::new(1, 2)
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
Position::new(PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0),
PhysicalPosition::new(1, 2)
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
Position::new(LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0),
PhysicalPosition::new(1, 2)
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
Position::new(LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0),
PhysicalPosition::new(2, 4)
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(2, 4)
);
}
// Eat coverage for the Debug impls et al
#[test]
fn ensure_attrs_do_not_panic() {
let _ = format!("{:?}", LogicalPosition::<u32>::default().clone());
HashSet::new().insert(LogicalPosition::<u32>::default());
let _ = format!("{:?}", dpi::LogicalPosition::<u32>::default().clone());
HashSet::new().insert(dpi::LogicalPosition::<u32>::default());
let _ = format!("{:?}", PhysicalPosition::<u32>::default().clone());
HashSet::new().insert(PhysicalPosition::<u32>::default());
let _ = format!("{:?}", dpi::PhysicalPosition::<u32>::default().clone());
HashSet::new().insert(dpi::PhysicalPosition::<u32>::default());
let _ = format!("{:?}", LogicalSize::<u32>::default().clone());
HashSet::new().insert(LogicalSize::<u32>::default());
let _ = format!("{:?}", dpi::LogicalSize::<u32>::default().clone());
HashSet::new().insert(dpi::LogicalSize::<u32>::default());
let _ = format!("{:?}", PhysicalSize::<u32>::default().clone());
HashSet::new().insert(PhysicalSize::<u32>::default());
let _ = format!("{:?}", dpi::PhysicalSize::<u32>::default().clone());
HashSet::new().insert(dpi::PhysicalSize::<u32>::default());
let _ = format!("{:?}", Size::Physical((1, 2).into()).clone());
let _ = format!("{:?}", Position::Physical((1, 2).into()).clone());
let _ = format!("{:?}", dpi::Size::Physical((1, 2).into()).clone());
let _ = format!("{:?}", dpi::Position::Physical((1, 2).into()).clone());
}
}

View File

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

View File

@@ -34,13 +34,11 @@
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use std::path::PathBuf;
use std::sync::{Mutex, Weak};
#[cfg(not(web_platform))]
#[cfg(not(wasm_platform))]
use std::time::Instant;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
#[cfg(web_platform)]
#[cfg(wasm_platform)]
use web_time::Instant;
use crate::error::ExternalError;
@@ -309,7 +307,7 @@ pub enum WindowEvent {
/// The activation token was delivered back and now could be used.
///
#[cfg_attr(
not(any(x11_platform, wayland_platform)),
not(any(x11_platform, wayland_platfrom)),
allow(rustdoc::broken_intra_doc_links)
)]
/// Delivered in response to [`request_activation_token`].
@@ -447,23 +445,21 @@ pub enum WindowEvent {
button: MouseButton,
},
/// Two-finger pinch gesture, often used for magnification.
/// Touchpad magnification event with two-finger pinch gesture.
///
/// Positive delta values indicate magnification (zooming in) and
/// negative delta values indicate shrinking (zooming out).
///
/// ## Platform-specific
///
/// - Only available on **macOS** and **iOS**.
/// - On iOS, not recognized by default. It must be enabled when needed.
PinchGesture {
/// - Only available on **macOS**.
TouchpadMagnify {
device_id: DeviceId,
/// Positive values indicate magnification (zooming in) and negative
/// values indicate shrinking (zooming out).
///
/// This value may be NaN.
delta: f64,
phase: TouchPhase,
},
/// Double tap gesture.
/// Smart magnification event.
///
/// On a Mac, smart magnification is triggered by a double tap with two fingers
/// on the trackpad and is commonly used to zoom on a certain object
@@ -473,26 +469,24 @@ pub enum WindowEvent {
/// The event is general enough that its generating gesture is allowed to vary
/// 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)
/// support this gesture or any other gesture with the same effect.
///
/// ## Platform-specific
///
/// - Only available on **macOS 10.8** and later, and **iOS**.
/// - On iOS, not recognized by default. It must be enabled when needed.
DoubleTapGesture { device_id: DeviceId },
/// - Only available on **macOS 10.8** and later.
SmartMagnify { device_id: DeviceId },
/// Two-finger rotation gesture.
/// Touchpad rotation event with two-finger rotation gesture.
///
/// Positive delta values indicate rotation counterclockwise and
/// negative delta values indicate rotation clockwise.
///
/// ## Platform-specific
///
/// - Only available on **macOS** and **iOS**.
/// - On iOS, not recognized by default. It must be enabled when needed.
RotationGesture {
/// - Only available on **macOS**.
TouchpadRotate {
device_id: DeviceId,
delta: f32,
phase: TouchPhase,
@@ -536,10 +530,11 @@ pub enum WindowEvent {
/// * Changing the display's scale factor (e.g. in Control Panel on Windows).
/// * 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
/// resized to the value suggested by the OS, but it can be changed to any value.
/// After this event callback has been processed, the window will be resized to whatever 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 {
scale_factor: f64,
/// Handle to update inner size during scale changes.
@@ -579,7 +574,7 @@ pub enum WindowEvent {
/// ### Others
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **Android / Wayland / Windows / Orbital:** Unsupported.
/// - **Android / Windows / Orbital:** Unsupported.
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
@@ -781,31 +776,6 @@ pub struct KeyEvent {
/// On some systems, holding down a key for some period of time causes that key to be repeated
/// as though it were being pressed and released repeatedly. This field is `true` if and only if
/// this event is the result of one of those repeats.
///
/// # Example
///
/// In games, you often want to ignore repated key events - this can be
/// done by ignoring events where this property is set.
///
/// ```
/// use winit::event::{WindowEvent, KeyEvent, ElementState};
/// use winit::keyboard::{KeyCode, PhysicalKey};
/// # let window_event = WindowEvent::RedrawRequested; // To make the example compile
/// match window_event {
/// WindowEvent::KeyboardInput {
/// event: KeyEvent {
/// physical_key: PhysicalKey::Code(KeyCode::KeyW),
/// state: ElementState::Pressed,
/// repeat: false,
/// ..
/// },
/// ..
/// } => {
/// // The physical key `W` was pressed, and it was not a repeat
/// }
/// _ => {} // Handle other events
/// }
/// ```
pub repeat: bool,
/// Platform-specific key event information.
@@ -1137,7 +1107,7 @@ pub enum MouseScrollDelta {
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`].
#[derive(Debug, Clone)]
pub struct InnerSizeWriter {
@@ -1150,7 +1120,7 @@ impl InnerSizeWriter {
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(
&mut self,
new_inner_size: PhysicalSize<u32>,
@@ -1229,13 +1199,13 @@ mod tests {
state: event::ElementState::Pressed,
button: event::MouseButton::Other(0),
});
with_window_event(PinchGesture {
with_window_event(TouchpadMagnify {
device_id: did,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(DoubleTapGesture { device_id: did });
with_window_event(RotationGesture {
with_window_event(SmartMagnify { device_id: did });
with_window_event(TouchpadRotate {
device_id: did,
delta: 0.0,
phase: event::TouchPhase::Started,

View File

@@ -8,18 +8,18 @@
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
use std::marker::PhantomData;
use std::ops::Deref;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::{error, fmt};
#[cfg(not(web_platform))]
#[cfg(not(wasm_platform))]
use std::time::{Duration, Instant};
#[cfg(web_platform)]
#[cfg(wasm_platform)]
use web_time::{Duration, Instant};
use crate::error::{EventLoopError, OsError};
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
use crate::error::EventLoopError;
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
/// Provides a way to retrieve events from the system and from the windows that were registered to
@@ -45,9 +45,11 @@ pub struct EventLoop<T: 'static> {
/// Target that associates windows with an [`EventLoop`].
///
/// This type exists to allow you to create new windows while Winit executes
/// your callback.
pub struct ActiveEventLoop {
pub(crate) p: platform_impl::ActiveEventLoop,
/// your callback. [`EventLoop`] will coerce into this type (`impl<T> Deref for
/// EventLoop<T>`), so functions that take this as a parameter can also take
/// `&EventLoop`.
pub struct EventLoopWindowTarget<T: 'static> {
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
}
@@ -55,26 +57,33 @@ pub struct ActiveEventLoop {
///
/// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported.
///
/// This can be created using [`EventLoop::new`] or [`EventLoop::with_user_event`].
#[derive(Default)]
pub struct EventLoopBuilder<T: 'static> {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
_p: PhantomData<T>,
}
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl EventLoopBuilder<()> {
/// Start building a new event loop.
#[inline]
#[deprecated = "use `EventLoop::builder` instead"]
pub fn new() -> Self {
EventLoop::builder()
Self::with_user_event()
}
}
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl<T> EventLoopBuilder<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
#[inline]
pub fn with_user_event() -> Self {
Self {
platform_specific: Default::default(),
_p: PhantomData,
}
}
/// Builds a new event loop.
///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
@@ -109,8 +118,6 @@ impl<T> EventLoopBuilder<T> {
)]
#[inline]
pub fn build(&mut self) -> Result<EventLoop<T>, EventLoopError> {
let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
return Err(EventLoopError::RecreationAttempt);
}
@@ -123,7 +130,7 @@ impl<T> EventLoopBuilder<T> {
})
}
#[cfg(web_platform)]
#[cfg(wasm_platform)]
pub(crate) fn allow_event_loop_recreation() {
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
}
@@ -135,13 +142,13 @@ impl<T> fmt::Debug for EventLoop<T> {
}
}
impl fmt::Debug for ActiveEventLoop {
impl<T> fmt::Debug for EventLoopWindowTarget<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("ActiveEventLoop { .. }")
f.pad("EventLoopWindowTarget { .. }")
}
}
/// Set through [`ActiveEventLoop::set_control_flow()`].
/// Set through [`EventLoopWindowTarget::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
///
@@ -186,38 +193,27 @@ impl ControlFlow {
}
impl EventLoop<()> {
/// Create the event loop.
/// Alias for [`EventLoopBuilder::new().build()`].
///
/// This is an alias of `EventLoop::builder().build()`.
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
#[inline]
pub fn new() -> Result<EventLoop<()>, EventLoopError> {
Self::builder().build()
}
/// Start building a new event loop.
///
/// This returns an [`EventLoopBuilder`], to allow configuring the event loop before creation.
///
/// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that.
#[inline]
pub fn builder() -> EventLoopBuilder<()> {
Self::with_user_event()
EventLoopBuilder::new().build()
}
}
impl<T> EventLoop<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
pub fn with_user_event() -> EventLoopBuilder<T> {
EventLoopBuilder {
platform_specific: Default::default(),
_p: PhantomData,
}
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
pub fn with_user_event() -> Result<EventLoop<T>, EventLoopError> {
EventLoopBuilder::<T>::with_user_event().build()
}
/// Runs the event loop in the calling thread and calls the given `event_handler` closure
/// to dispatch any pending events.
///
/// Since the closure is `'static`, it must be a `move` closure if it needs to
/// access any data from the calling context.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// ## Platform-specific
@@ -230,10 +226,10 @@ impl<T> EventLoop<T> {
///
/// Web applications are recommended to use
#[cfg_attr(
web_platform,
wasm_platform,
doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]"
)]
#[cfg_attr(not(web_platform), doc = "`EventLoopExtWebSys::spawn()`")]
#[cfg_attr(not(wasm_platform), doc = "`EventLoopExtWebSys::spawn()`")]
/// [^1] instead of [`run()`] to avoid the need
/// for the Javascript exception trick, and to make it clearer that the event loop runs
/// asynchronously (via the browser's own, internal, event loop) and doesn't block the
@@ -241,94 +237,30 @@ impl<T> EventLoop<T> {
///
/// 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()
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on Web.
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on WASM.
#[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &ActiveEventLoop),
F: FnMut(Event<T>, &EventLoopWindowTarget<T>),
{
let _span = tracing::debug_span!("winit::EventLoop::run").entered();
self.event_loop.run(event_handler)
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events to the main event loop.
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
event_loop_proxy: self.event_loop.create_proxy(),
}
}
/// 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) {
let _span = tracing::debug_span!(
"winit::EventLoop::listen_device_events",
allowed = ?allowed
)
.entered();
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 _span = tracing::debug_span!(
"winit::EventLoop::create_window",
window_attributes = ?window_attributes
)
.entered();
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")]
impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
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)
}
}
@@ -336,7 +268,7 @@ impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
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)
}
}
@@ -368,39 +300,17 @@ impl<T> AsRawFd for EventLoop<T> {
}
}
impl ActiveEventLoop {
/// Create the window.
///
/// Possible causes of error include denied permission, incompatible system, and lack of memory.
///
/// ## 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 _span = tracing::debug_span!(
"winit::ActiveEventLoop::create_window",
window_attributes = ?window_attributes
)
.entered();
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 {
let _span = tracing::debug_span!("winit::ActiveEventLoop::create_custom_cursor",).entered();
self.p.create_custom_cursor(custom_cursor)
impl<T> Deref for EventLoop<T> {
type Target = EventLoopWindowTarget<T>;
fn deref(&self) -> &EventLoopWindowTarget<T> {
self.event_loop.window_target()
}
}
impl<T> EventLoopWindowTarget<T> {
/// Returns the list of all the monitors available on the system.
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
let _span = tracing::debug_span!("winit::ActiveEventLoop::available_monitors",).entered();
#[allow(clippy::useless_conversion)] // false positive on some platforms
self.p
.available_monitors()
@@ -417,8 +327,6 @@ impl ActiveEventLoop {
/// **Wayland / Web:** Always returns `None`.
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
let _span = tracing::debug_span!("winit::ActiveEventLoop::primary_monitor",).entered();
self.p
.primary_monitor()
.map(|inner| MonitorHandle { inner })
@@ -436,12 +344,6 @@ impl ActiveEventLoop {
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn listen_device_events(&self, allowed: DeviceEvents) {
let _span = tracing::debug_span!(
"winit::ActiveEventLoop::listen_device_events",
allowed = ?allowed
)
.entered();
self.p.listen_device_events(allowed);
}
@@ -459,8 +361,6 @@ impl ActiveEventLoop {
///
/// See [`LoopExiting`](Event::LoopExiting).
pub fn exit(&self) {
let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered();
self.p.exit()
}
@@ -470,19 +370,10 @@ impl ActiveEventLoop {
pub fn exiting(&self) -> bool {
self.p.exiting()
}
/// Gets a persistent reference to the underlying platform display.
///
/// See the [`OwnedDisplayHandle`] type for more information.
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle {
platform: self.p.owned_display_handle(),
}
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
impl<T> rwh_06::HasDisplayHandle for EventLoopWindowTarget<T> {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.p.raw_display_handle_rwh_06()?;
// SAFETY: The display will never be deallocated while the event loop is alive.
@@ -491,59 +382,13 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
}
#[cfg(feature = "rwh_05")]
unsafe impl rwh_05::HasRawDisplayHandle for ActiveEventLoop {
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoopWindowTarget<T> {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
self.p.raw_display_handle_rwh_05()
}
}
/// A proxy for the underlying display handle.
///
/// The purpose of this type is to provide a cheaply clonable handle to the underlying
/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`]
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as
/// needed.
///
/// For all platforms, this is one of the following:
///
/// - A zero-sized type that is likely optimized out.
/// - A reference-counted pointer to the underlying type.
#[derive(Clone)]
pub struct OwnedDisplayHandle {
#[cfg_attr(not(any(feature = "rwh_05", feature = "rwh_06")), allow(dead_code))]
platform: platform_impl::OwnedDisplayHandle,
}
impl fmt::Debug for OwnedDisplayHandle {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive()
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
#[inline]
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.platform.raw_display_handle_rwh_06()?;
// SAFETY: The underlying display handle should be safe.
let handle = unsafe { rwh_06::DisplayHandle::borrow_raw(raw) };
Ok(handle)
}
}
#[cfg(feature = "rwh_05")]
unsafe impl rwh_05::HasRawDisplayHandle for OwnedDisplayHandle {
#[inline]
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
self.platform.raw_display_handle_rwh_05()
}
}
/// Used to send custom events to [`EventLoop`].
pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>,
@@ -566,8 +411,6 @@ impl<T: 'static> EventLoopProxy<T> {
///
/// [`UserEvent(event)`]: Event::UserEvent
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
let _span = tracing::debug_span!("winit::EventLoopProxy::send_event",).entered();
self.event_loop_proxy.send_event(event)
}
}
@@ -610,22 +453,22 @@ pub enum DeviceEvents {
/// 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
/// 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: usize,
serial: u64,
}
impl AsyncRequestSerial {
// TODO(kchibisov): Remove `cfg` when the clipboard will be added.
// TODO(kchibisov) remove `cfg` when the clipboard will be added.
#[allow(dead_code)]
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.
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 }
}

View File

@@ -118,8 +118,6 @@ impl Icon {
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
let _span = tracing::debug_span!("winit::Icon::from_rgba", width, height).entered();
Ok(Icon {
inner: PlatformIcon::from_rgba(rgba, width, height)?,
})

View File

@@ -20,7 +20,7 @@
// the W3C short notice apply to the `Key` and `KeyCode` enums and their variants and the
// documentation attached to their variants.
// --------- BEGINNING OF W3C LICENSE --------------------------------------------------------------
// --------- BEGGINING OF W3C LICENSE --------------------------------------------------------------
//
// License
//
@@ -55,7 +55,7 @@
//
// --------- END OF W3C LICENSE --------------------------------------------------------------------
// --------- BEGINNING OF W3C SHORT NOTICE ---------------------------------------------------------
// --------- BEGGINING OF W3C SHORT NOTICE ---------------------------------------------------------
//
// winit: https://github.com/rust-windowing/winit
//
@@ -69,9 +69,6 @@
//
// --------- END OF W3C SHORT NOTICE ---------------------------------------------------------------
use bitflags::bitflags;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub use smol_str::SmolStr;
/// Contains the platform-native physical key identifier
@@ -744,7 +741,7 @@ pub enum KeyCode {
/// - 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.)
/// - The `Space` variant here, can be identified by the character it generates in the
/// specification.
/// specificaiton.
///
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
#[non_exhaustive]
@@ -914,7 +911,7 @@ pub enum NamedKey {
Standby,
/// The WakeUp key. (`KEYCODE_WAKEUP`)
WakeUp,
/// Initiate the multi-candidate mode.
/// Initate the multi-candidate mode.
AllCandidates,
Alphanumeric,
/// Initiate the Code Input mode to allow characters to be entered by
@@ -1459,7 +1456,7 @@ pub enum NamedKey {
/// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
/// additions:
/// - 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
/// dead-key twice.
///

View File

@@ -2,15 +2,22 @@
//!
//! # Building windows
//!
//! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with
//! the [`EventLoop::new()`] function.
//! Before you can build a [`Window`], you first need to build an [`EventLoop`]. This is done with the
//! [`EventLoop::new()`] function.
//!
//! ```no_run
//! use winit::event_loop::EventLoop;
//! 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 = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
//!
//! The first method is the simplest and will give you default values for everything. The second
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
//!
//! # Event handling
//!
@@ -56,10 +63,11 @@
//! use winit::{
//! event::{Event, WindowEvent},
//! event_loop::{ControlFlow, EventLoop},
//! window::Window,
//! window::WindowBuilder,
//! };
//!
//! let event_loop = EventLoop::new().unwrap();
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
//!
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
@@ -70,19 +78,14 @@
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait);
//!
//! let mut window = None;
//!
//! event_loop.run(move |event, event_loop| {
//! event_loop.run(move |event, elwt| {
//! match event {
//! Event::Resumed => {
//! window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
//! }
//! Event::WindowEvent {
//! event: WindowEvent::CloseRequested,
//! ..
//! } => {
//! println!("The close button was pressed; stopping");
//! event_loop.exit();
//! elwt.exit();
//! },
//! Event::AboutToWait => {
//! // Application update code.
@@ -92,7 +95,7 @@
//! // You only need to call this if you've determined that you need to redraw in
//! // applications which do not always need to. Applications that redraw continuously
//! // can render here instead.
//! window.as_ref().unwrap().request_redraw();
//! window.request_redraw();
//! },
//! Event::WindowEvent {
//! event: WindowEvent::RedrawRequested,
@@ -123,54 +126,19 @@
//! Note that many platforms will display garbage data in the window's client area if the
//! application doesn't render anything to the window by the time the desktop compositor is ready to
//! display the window to the user. If you notice this happening, you should create the window with
//! [`visible` set to `false`](crate::window::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.
//!
//! # 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::new()`]: event_loop::EventLoop::new
//! [`EventLoop::run()`]: event_loop::EventLoop::run
//! [`exit()`]: event_loop::ActiveEventLoop::exit
//! [`exit()`]: event_loop::EventLoopWindowTarget::exit
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
//! [`WindowAttributes`]: window::WindowAttributes
//! [`WindowBuilder`]: window::WindowBuilder
//! [window_new]: window::Window::new
//! [`create_window`]: event_loop::ActiveEventLoop::create_window
//! [window_builder_new]: window::WindowBuilder::new
//! [window_builder_build]: window::WindowBuilder::build
//! [`Window::id()`]: window::Window::id
//! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent
@@ -184,22 +152,24 @@
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)]
#![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
#![cfg_attr(
docsrs,
feature(doc_auto_cfg, doc_cfg_hide),
doc(cfg_hide(doc, docsrs))
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![allow(clippy::missing_safety_doc)]
#[cfg(feature = "rwh_06")]
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)]
pub use dpi;
#[allow(unused_imports)]
#[macro_use]
extern crate log;
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
#[macro_use]
extern crate bitflags;
pub mod dpi;
#[macro_use]
pub mod error;
mod cursor;
@@ -209,7 +179,21 @@ mod icon;
pub mod keyboard;
pub mod monitor;
mod platform_impl;
mod utils;
pub mod window;
pub mod platform;
/// Wrapper for objects which winit will access on the main thread so they are effectively `Send`
/// and `Sync`, since they always execute on a single thread.
///
/// # Safety
///
/// Winit can run only one event loop at a time, and the event loop itself is tied to some thread.
/// The objects could be sent across the threads, but once passed to winit, they execute on the
/// main thread if the platform demands it. Thus, marking such objects as `Send + Sync` is safe.
#[doc(hidden)]
#[derive(Clone, Debug)]
pub(crate) struct SendSyncWrapper<T>(pub(crate) T);
unsafe impl<T> Send for SendSyncWrapper<T> {}
unsafe impl<T> Sync for SendSyncWrapper<T> {}

View File

@@ -3,39 +3,35 @@
//! If you want to get basic information about a monitor, you can use the
//! [`MonitorHandle`] type. This is retrieved from one of the following
//! methods, which return an iterator of [`MonitorHandle`]:
//! - [`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).
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
platform_impl,
};
/// Deprecated! Use `VideoModeHandle` instead.
#[deprecated = "Renamed to `VideoModeHandle`"]
pub type VideoMode = VideoModeHandle;
/// Describes a fullscreen video mode of a monitor.
///
/// Can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) video_mode: platform_impl::VideoModeHandle,
pub struct VideoMode {
pub(crate) video_mode: platform_impl::VideoMode,
}
impl std::fmt::Debug for VideoModeHandle {
impl std::fmt::Debug for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.video_mode.fmt(f)
}
}
impl PartialOrd for VideoModeHandle {
fn partial_cmp(&self, other: &VideoModeHandle) -> Option<std::cmp::Ordering> {
impl PartialOrd for VideoMode {
fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VideoModeHandle {
fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering {
impl Ord for VideoMode {
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
self.monitor().cmp(&other.monitor()).then(
self.size()
.cmp(&other.size())
@@ -49,7 +45,7 @@ impl Ord for VideoModeHandle {
}
}
impl VideoModeHandle {
impl VideoMode {
/// Returns the resolution of this video mode.
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
@@ -85,7 +81,7 @@ impl VideoModeHandle {
}
}
impl std::fmt::Display for VideoModeHandle {
impl std::fmt::Display for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
@@ -135,8 +131,8 @@ impl MonitorHandle {
/// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor
/// the window is on is removed.
///
/// When using exclusive fullscreen, the refresh rate of the [`VideoModeHandle`] that was
/// used to enter fullscreen should be used instead.
/// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to
/// enter fullscreen should be used instead.
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
self.inner.refresh_rate_millihertz()
@@ -145,7 +141,7 @@ impl MonitorHandle {
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
/// 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
///
@@ -165,9 +161,9 @@ impl MonitorHandle {
///
/// - **Web:** Always returns an empty iterator
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.inner
.video_modes()
.map(|video_mode| VideoModeHandle { video_mode })
.map(|video_mode| VideoMode { video_mode })
}
}

View File

@@ -1,83 +1,17 @@
//! # 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.12", 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},
event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget},
window::{Window, WindowBuilder},
};
use self::activity::{AndroidApp, ConfigurationRef, Rect};
use android_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 [`EventLoopWindowTarget`] that are specific to Android.
pub trait EventLoopWindowTargetExtAndroid {}
/// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid {
@@ -96,12 +30,12 @@ impl WindowExtAndroid for Window {
}
}
impl ActiveEventLoopExtAndroid for ActiveEventLoop {}
impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
/// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowAttributesExtAndroid {}
/// Additional methods on [`WindowBuilder`] that are specific to Android.
pub trait WindowBuilderExtAndroid {}
impl WindowAttributesExtAndroid for WindowAttributes {}
impl WindowBuilderExtAndroid for WindowBuilder {}
pub trait EventLoopBuilderExtAndroid {
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
@@ -155,16 +89,5 @@ pub mod activity {
// feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page.
#[doc(no_inline)]
#[cfg(android_platform)]
pub use android_activity::*;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct Rect;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct ConfigurationRef;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct AndroidApp;
}

View File

@@ -1,75 +1,12 @@
//! # 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 icrate::Foundation::MainThreadMarker;
use objc2::rc::Id;
use crate::{
event_loop::EventLoop,
monitor::{MonitorHandle, VideoModeHandle},
window::{Window, WindowAttributes},
monitor::{MonitorHandle, VideoMode},
window::{Window, WindowBuilder},
};
/// Additional methods on [`EventLoop`] that are specific to iOS.
@@ -151,21 +88,6 @@ pub trait WindowExtIOS {
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
/// is also called for you.
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle);
/// Sets whether the [`Window`] should recognize pinch gestures.
///
/// The default is to not recognize gestures.
fn recognize_pinch_gesture(&self, should_recognize: bool);
/// Sets whether the [`Window`] should recognize double tap gestures.
///
/// The default is to not recognize gestures.
fn recognize_doubletap_gesture(&self, should_recognize: bool);
/// Sets whether the [`Window`] should recognize rotation gestures.
///
/// The default is to not recognize gestures.
fn recognize_rotation_gesture(&self, should_recognize: bool);
}
impl WindowExtIOS for Window {
@@ -205,28 +127,10 @@ impl WindowExtIOS for Window {
self.window
.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
}
#[inline]
fn recognize_pinch_gesture(&self, should_recognize: bool) {
self.window
.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
}
#[inline]
fn recognize_doubletap_gesture(&self, should_recognize: bool) {
self.window
.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
}
#[inline]
fn recognize_rotation_gesture(&self, should_recognize: bool) {
self.window
.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
}
}
/// Additional methods on [`WindowAttributes`] that are specific to iOS.
pub trait WindowAttributesExtIOS {
/// Additional methods on [`WindowBuilder`] that are specific to iOS.
pub trait WindowBuilderExtIOS {
/// 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
@@ -280,7 +184,7 @@ pub trait WindowAttributesExtIOS {
fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
}
impl WindowAttributesExtIOS for WindowAttributes {
impl WindowBuilderExtIOS for WindowBuilder {
#[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> Self {
self.platform_specific.scale_factor = Some(scale_factor);
@@ -326,23 +230,23 @@ pub trait MonitorHandleExtIOS {
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
fn ui_screen(&self) -> *mut c_void;
/// Returns the preferred [`VideoModeHandle`] for this monitor.
/// Returns the preferred [`VideoMode`] for this monitor.
///
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
fn preferred_video_mode(&self) -> VideoModeHandle;
fn preferred_video_mode(&self) -> VideoMode;
}
impl MonitorHandleExtIOS for MonitorHandle {
#[inline]
fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() };
objc2::rc::Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
}
#[inline]
fn preferred_video_mode(&self) -> VideoModeHandle {
VideoModeHandle {
fn preferred_video_mode(&self) -> VideoMode {
VideoMode {
video_mode: self.inner.preferred_video_mode(),
}
}
@@ -379,7 +283,7 @@ pub enum Idiom {
CarPlay,
}
bitflags::bitflags! {
bitflags! {
/// The [edges] of a screen.
///
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc

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;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id;
use crate::{
event_loop::{ActiveEventLoop, EventLoopBuilder},
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowAttributes},
window::{Window, WindowBuilder},
};
/// Additional methods on [`Window`] that are specific to MacOS.
@@ -190,15 +174,15 @@ pub enum ActivationPolicy {
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_title_hidden`
/// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view`
pub trait WindowAttributesExtMacOS {
pub trait WindowBuilderExtMacOS {
/// 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;
/// Makes the titlebar transparent and allows the content to appear behind it.
@@ -225,7 +209,7 @@ pub trait WindowAttributesExtMacOS {
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
}
impl WindowAttributesExtMacOS for WindowAttributes {
impl WindowBuilderExtMacOS for WindowBuilder {
#[inline]
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.platform_specific.movable_by_window_background = movable_by_window_background;
@@ -383,15 +367,13 @@ impl MonitorHandleExtMacOS for MonitorHandle {
fn ns_screen(&self) -> Option<*mut c_void> {
// SAFETY: We only use the marker to get a pointer
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() };
self.inner
.ns_screen(mtm)
.map(|s| objc2::rc::Id::as_ptr(&s) as _)
let mtm = unsafe { MainThreadMarker::new_unchecked() };
self.inner.ns_screen(mtm).map(|s| Id::as_ptr(&s) as _)
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to macOS.
pub trait ActiveEventLoopExtMacOS {
/// Additional methods on [`EventLoopWindowTarget`] that are specific to macOS.
pub trait EventLoopWindowTargetExtMacOS {
/// Hide the entire application. In most applications this is typically triggered with Command-H.
fn hide_application(&self);
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
@@ -404,7 +386,7 @@ pub trait ActiveEventLoopExtMacOS {
fn allows_automatic_window_tabbing(&self) -> bool;
}
impl ActiveEventLoopExtMacOS for ActiveEventLoop {
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
fn hide_application(&self) {
self.p.hide_application()
}

View File

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

View File

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

View File

@@ -1,6 +1 @@
//! # 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

@@ -2,7 +2,7 @@ use std::time::Duration;
use crate::{
event::Event,
event_loop::{ActiveEventLoop, EventLoop},
event_loop::{EventLoop, EventLoopWindowTarget},
};
/// The return status for `pump_events`
@@ -32,6 +32,68 @@ pub trait EventLoopExtPumpEvents {
/// Passing a `timeout` of `None` means that it may wait indefinitely for new
/// events before returning control back to the external loop.
///
/// ## Example
///
/// ```rust,no_run
/// # // Copied from examples/window_pump_events.rs
/// # #[cfg(any(
/// # windows_platform,
/// # macos_platform,
/// # x11_platform,
/// # wayland_platform,
/// # android_platform,
/// # ))]
/// fn main() -> std::process::ExitCode {
/// # use std::{process::ExitCode, thread::sleep, time::Duration};
/// #
/// # use simple_logger::SimpleLogger;
/// # use winit::{
/// # event::{Event, WindowEvent},
/// # event_loop::EventLoop,
/// # platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
/// # window::WindowBuilder,
/// # };
/// let mut event_loop = EventLoop::new().unwrap();
/// #
/// # SimpleLogger::new().init().unwrap();
/// let window = WindowBuilder::new()
/// .with_title("A fantastic window!")
/// .build(&event_loop)
/// .unwrap();
///
/// 'main: loop {
/// let timeout = Some(Duration::ZERO);
/// let status = event_loop.pump_events(timeout, |event, elwt| {
/// # if let Event::WindowEvent { event, .. } = &event {
/// # // Print only Window events to reduce noise
/// # println!("{event:?}");
/// # }
/// #
/// match event {
/// Event::WindowEvent {
/// event: WindowEvent::CloseRequested,
/// window_id,
/// } if window_id == window.id() => elwt.exit(),
/// Event::AboutToWait => {
/// window.request_redraw();
/// }
/// _ => (),
/// }
/// });
/// if let PumpStatus::Exit(exit_code) = status {
/// break 'main ExitCode::from(exit_code as u8);
/// }
///
/// // Sleep for 1/60 second to simulate application work
/// //
/// // Since `pump_events` doesn't block it will be important to
/// // throttle the loop in the app somehow.
/// println!("Update()");
/// sleep(Duration::from_millis(16));
/// }
/// }
/// ```
///
/// **Note:** This is not a portable API, and its usage involves a number of
/// caveats and trade offs that should be considered before using this API!
///
@@ -75,14 +137,12 @@ pub trait EventLoopExtPumpEvents {
/// other lifecycle events occur while the event is buffered.
///
/// ## Supported Platforms
///
/// - Windows
/// - Linux
/// - MacOS
/// - Android
///
/// ## Unsupported Platforms
///
/// - **Web:** This API is fundamentally incompatible with the event-based way in which
/// Web browsers work because it's not possible to have a long-running external
/// loop that would block the browser and there is nothing that can be
@@ -92,7 +152,6 @@ pub trait EventLoopExtPumpEvents {
/// there's no way to support the same approach to polling as on MacOS.
///
/// ## Platform-specific
///
/// - **Windows**: The implementation will use `PeekMessage` when checking for
/// window messages to avoid blocking your external event loop.
///
@@ -115,7 +174,7 @@ pub trait EventLoopExtPumpEvents {
/// callback.
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
}
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
@@ -123,7 +182,7 @@ impl<T> EventLoopExtPumpEvents for EventLoop<T> {
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{
self.event_loop.pump_events(timeout, event_handler)
}

View File

@@ -1,7 +1,7 @@
use crate::{
error::EventLoopError,
event::Event,
event_loop::{ActiveEventLoop, EventLoop},
event_loop::{EventLoop, EventLoopWindowTarget},
};
#[cfg(doc)]
@@ -55,18 +55,18 @@ pub trait EventLoopExtRunOnDemand {
/// loop that would block the browser and there is nothing that can be
/// polled to ask for new events. Events are delivered via callbacks based
/// on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS.
///
#[cfg_attr(
not(web_platform),
not(wasm_platform),
doc = "[^1]: `spawn()` is only available on `wasm` platforms."
)]
///
/// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
/// [`exit()`]: EventLoopWindowTarget::exit()
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
}
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
@@ -74,29 +74,8 @@ impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{
self.event_loop.window_target().clear_exit();
self.event_loop.run_on_demand(event_handler)
}
}
impl ActiveEventLoop {
/// Clear exit status.
pub(crate) fn clear_exit(&self) {
self.p.clear_exit()
}
}
/// ```compile_fail
/// use winit::event_loop::EventLoop;
/// use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
///
/// let mut event_loop = EventLoop::new().unwrap();
/// event_loop.run_on_demand(|_, _| {
/// // Attempt to run the event loop re-entrantly; this must fail.
/// event_loop.run_on_demand(|_, _| {});
/// });
/// ```
#[allow(dead_code)]
fn test_run_on_demand_cannot_access_event_loop() {}

View File

@@ -1,3 +1,5 @@
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
use crate::keyboard::{KeyCode, PhysicalKey};
// TODO: Describe what this value contains for each platform
@@ -9,7 +11,7 @@ use crate::keyboard::{KeyCode, PhysicalKey};
pub trait PhysicalKeyExtScancode {
/// The raw value of the platform-specific physical key identifier.
///
/// Returns `Some(key_id)` if the conversion was successful; returns `None` otherwise.
/// Returns `Some(key_id)` if the conversion was succesful; returns `None` otherwise.
///
/// ## Platform-specific
/// - **Windows:** A 16bit extended scancode
@@ -27,24 +29,17 @@ pub trait PhysicalKeyExtScancode {
fn from_scancode(scancode: u32) -> PhysicalKey;
}
impl PhysicalKeyExtScancode for PhysicalKey {
fn to_scancode(self) -> Option<u32> {
crate::platform_impl::physicalkey_to_scancode(self)
}
fn from_scancode(scancode: u32) -> PhysicalKey {
crate::platform_impl::scancode_to_physicalkey(scancode)
}
}
impl PhysicalKeyExtScancode for KeyCode {
#[inline]
fn to_scancode(self) -> Option<u32> {
<PhysicalKey as PhysicalKeyExtScancode>::to_scancode(PhysicalKey::Code(self))
}
impl PhysicalKeyExtScancode for KeyCode
where
PhysicalKey: PhysicalKeyExtScancode,
{
#[inline]
fn from_scancode(scancode: u32) -> PhysicalKey {
<PhysicalKey as PhysicalKeyExtScancode>::from_scancode(scancode)
}
#[inline]
fn to_scancode(self) -> Option<u32> {
<PhysicalKey as PhysicalKeyExtScancode>::to_scancode(PhysicalKey::Code(self))
}
}

View File

@@ -24,8 +24,8 @@
use std::env;
use crate::error::NotSupportedError;
use crate::event_loop::{ActiveEventLoop, AsyncRequestSerial};
use crate::window::{ActivationToken, Window, WindowAttributes};
use crate::event_loop::{AsyncRequestSerial, EventLoopWindowTarget};
use crate::window::{ActivationToken, Window, WindowBuilder};
/// The variable which is used mostly on X11.
const X11_VAR: &str = "DESKTOP_STARTUP_ID";
@@ -47,7 +47,7 @@ pub trait WindowExtStartupNotify {
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError>;
}
pub trait WindowAttributesExtStartupNotify {
pub trait WindowBuilderExtStartupNotify {
/// Use this [`ActivationToken`] during window creation.
///
/// Not using such a token upon a window could make your window not gaining
@@ -55,13 +55,13 @@ pub trait WindowAttributesExtStartupNotify {
fn with_activation_token(self, token: ActivationToken) -> Self;
}
impl EventLoopExtStartupNotify for ActiveEventLoop {
impl<T> EventLoopExtStartupNotify for EventLoopWindowTarget<T> {
fn read_token_from_env(&self) -> Option<ActivationToken> {
match self.p {
#[cfg(wayland_platform)]
crate::platform_impl::ActiveEventLoop::Wayland(_) => env::var(WAYLAND_VAR),
crate::platform_impl::EventLoopWindowTarget::Wayland(_) => env::var(WAYLAND_VAR),
#[cfg(x11_platform)]
crate::platform_impl::ActiveEventLoop::X(_) => env::var(X11_VAR),
crate::platform_impl::EventLoopWindowTarget::X(_) => env::var(X11_VAR),
}
.ok()
.map(ActivationToken::_new)
@@ -74,7 +74,7 @@ impl WindowExtStartupNotify for Window {
}
}
impl WindowAttributesExtStartupNotify for WindowAttributes {
impl WindowBuilderExtStartupNotify for WindowBuilder {
fn with_activation_token(mut self, token: ActivationToken) -> Self {
self.platform_specific.activation_token = Some(token);
self

View File

@@ -1,33 +1,20 @@
//! # Wayland
//!
//! **Note:** Windows don't appear on Wayland until you draw/present to them.
//!
//! By default, Winit loads system libraries using `dlopen`. This can be
//! disabled by disabling the `"wayland-dlopen"` cargo feature.
//!
//! ## Client-side decorations
//!
//! Winit provides client-side decorations by default, but the behaviour can
//! be controlled with the following feature flags:
//!
//! * `wayland-csd-adwaita` (default).
//! * `wayland-csd-adwaita-crossfont`.
//! * `wayland-csd-adwaita-notitle`.
use crate::{
event_loop::{ActiveEventLoop, EventLoopBuilder},
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowAttributes},
window::{Window, WindowBuilder},
};
use crate::platform_impl::{ApplicationName, Backend};
pub use crate::window::Theme;
/// Additional methods on [`ActiveEventLoop`] that are specific to Wayland.
pub trait ActiveEventLoopExtWayland {
/// True if the [`ActiveEventLoop`] uses Wayland.
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Wayland.
pub trait EventLoopWindowTargetExtWayland {
/// True if the [`EventLoopWindowTarget`] uses Wayland.
fn is_wayland(&self) -> bool;
}
impl ActiveEventLoopExtWayland for ActiveEventLoop {
impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
#[inline]
fn is_wayland(&self) -> bool {
self.p.is_wayland()
@@ -49,7 +36,7 @@ pub trait EventLoopBuilderExtWayland {
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
#[inline]
fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland);
self.platform_specific.forced_backend = Some(Backend::Wayland);
self
}
@@ -65,25 +52,22 @@ pub trait WindowExtWayland {}
impl WindowExtWayland for Window {}
/// Additional methods on [`WindowAttributes`] that are specific to Wayland.
pub trait WindowAttributesExtWayland {
/// Additional methods on [`WindowBuilder`] that are specific to Wayland.
pub trait WindowBuilderExtWayland {
/// Build window with the given name.
///
/// The `general` name sets an application ID, which should match the `.desktop`
/// file distributed with your program. The `instance` is a `no-op`.
/// file destributed with your program. The `instance` is a `no-op`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
}
impl WindowAttributesExtWayland for WindowAttributes {
impl WindowBuilderExtWayland for WindowBuilder {
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(crate::platform_impl::ApplicationName::new(
general.into(),
instance.into(),
));
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
self
}
}

View File

@@ -1,31 +1,12 @@
//! # Web
//!
//! The officially supported browsers are Chrome, Firefox and Safari 13.1+,
//! though forks of these should work fine.
//!
//! 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][with_canvas], or
//! [let Winit create a `<canvas>` element which you can then retrieve][get]
//! and insert it into the DOM yourself.
//!
//! Currently, there is no example code using Winit on Web, see [#3473]. For
//! information on using Rust on WebAssembly, check out the [Rust and
//! WebAssembly book].
//!
//! [with_canvas]: WindowAttributesExtWebSys::with_canvas
//! [get]: WindowExtWebSys::canvas
//! [#3473]: https://github.com/rust-windowing/winit/issues/3473
//! [Rust and WebAssembly book]: https://rustwasm.github.io/book/
//!
//! ## CSS properties
//! The web target does not automatically insert the canvas element object into the web page, to
//! allow end users to determine how the page should be laid out. Use the [`WindowExtWebSys`] trait
//! to retrieve the canvas from the Window. Alternatively, use the [`WindowBuilderExtWebSys`] trait
//! to provide your own canvas.
//!
//! It is recommended **not** to apply certain CSS properties to the canvas:
//! - [`transform`](https://developer.mozilla.org/en-US/docs/Web/CSS/transform)
//! - [`border`](https://developer.mozilla.org/en-US/docs/Web/CSS/border)
//! - [`padding`](https://developer.mozilla.org/en-US/docs/Web/CSS/padding)
//! - [`transform`]
//! - [`border`]
//! - [`padding`]
//!
//! The following APIs can't take them into account and will therefore provide inaccurate results:
//! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`]
@@ -35,55 +16,31 @@
//! - [`Window::set_outer_position()`]
//!
//! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized
//! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size
//! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size()
//! [`WindowEvent::Occluded`]: crate::event::WindowEvent::Occluded
//! [`WindowEvent::CursorMoved`]: crate::event::WindowEvent::CursorMoved
//! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered
//! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft
//! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch
//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position
//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position()
//! [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use crate::cursor::CustomCursorBuilder;
use crate::event::Event;
use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget;
use crate::platform_impl::PlatformCustomCursorBuilder;
use crate::window::CustomCursor;
use crate::window::{Window, WindowBuilder};
use crate::SendSyncWrapper;
#[cfg(web_platform)]
use web_sys::HtmlCanvasElement;
use crate::cursor::CustomCursorSource;
use crate::event::Event;
use crate::event_loop::{ActiveEventLoop, EventLoop};
#[cfg(web_platform)]
use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture;
use crate::platform_impl::PlatformCustomCursorSource;
use crate::window::{CustomCursor, Window, WindowAttributes};
#[cfg(not(web_platform))]
#[doc(hidden)]
pub struct HtmlCanvasElement;
pub trait WindowExtWebSys {
/// Only returns the canvas if called from inside the window context (the
/// main thread).
/// Only returns the canvas if called from inside the window.
fn canvas(&self) -> Option<HtmlCanvasElement>;
/// Returns [`true`] if calling `event.preventDefault()` is enabled.
///
/// See [`Window::set_prevent_default()`] for more details.
fn prevent_default(&self) -> bool;
/// Sets whether `event.preventDefault()` should be called on events on the
/// canvas that have side effects.
///
/// For example, by default using the mouse wheel would cause the page to scroll, enabling this
/// would prevent that.
///
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
/// context menu with Shift+Rightclick.
fn set_prevent_default(&self, prevent_default: bool);
}
impl WindowExtWebSys for Window {
@@ -91,34 +48,25 @@ impl WindowExtWebSys for Window {
fn canvas(&self) -> Option<HtmlCanvasElement> {
self.window.canvas()
}
fn prevent_default(&self) -> bool {
self.window.prevent_default()
}
fn set_prevent_default(&self, prevent_default: bool) {
self.window.set_prevent_default(prevent_default)
}
}
pub trait WindowAttributesExtWebSys {
pub trait WindowBuilderExtWebSys {
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`],
/// [`WindowAttributes::default()`] will create one.
/// [`WindowBuilder::build()`] will create one.
///
/// In any case, the canvas won't be automatically inserted into the web page.
///
/// [`None`] by default.
#[cfg_attr(
not(web_platform),
doc = "",
doc = "[`HtmlCanvasElement`]: #only-available-on-wasm"
)]
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
/// Sets whether `event.preventDefault()` should be called on events on the
/// canvas that have side effects.
/// Whether `event.preventDefault` should be automatically called to prevent event propagation
/// when appropriate.
///
/// See [`Window::set_prevent_default()`] for more details.
/// For example, mouse wheel events are only handled by the canvas by default. This avoids
/// the default behavior of scrolling the page.
///
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
/// context menu with Shift+Rightclick.
///
/// Enabled by default.
fn with_prevent_default(self, prevent_default: bool) -> Self;
@@ -135,9 +83,9 @@ pub trait WindowAttributesExtWebSys {
fn with_append(self, append: bool) -> Self;
}
impl WindowAttributesExtWebSys for WindowAttributes {
impl WindowBuilderExtWebSys for WindowBuilder {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.set_canvas(canvas);
self.platform_specific.canvas = SendSyncWrapper(canvas);
self
}
@@ -166,11 +114,11 @@ pub trait EventLoopExtWebSys {
///
/// Unlike
#[cfg_attr(
all(web_platform, target_feature = "exception-handling"),
all(wasm_platform, target_feature = "exception-handling"),
doc = "`run()`"
)]
#[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")),
not(all(wasm_platform, target_feature = "exception-handling")),
doc = "[`run()`]"
)]
/// [^1], this returns immediately, and doesn't throw an exception in order to
@@ -182,13 +130,13 @@ pub trait EventLoopExtWebSys {
/// event loop when switching between tabs on a single page application.
///
#[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")),
not(all(wasm_platform, target_feature = "exception-handling")),
doc = "[`run()`]: EventLoop::run()"
)]
/// [^1]: `run()` is _not_ available on WASM when the target supports `exception-handling`.
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
}
impl<T> EventLoopExtWebSys for EventLoop<T> {
@@ -196,13 +144,13 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{
self.event_loop.spawn(event_handler)
}
}
pub trait ActiveEventLoopExtWebSys {
pub trait EventLoopWindowTargetExtWebSys {
/// Sets the strategy for [`ControlFlow::Poll`].
///
/// See [`PollStrategy`].
@@ -216,18 +164,9 @@ pub trait ActiveEventLoopExtWebSys {
///
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn poll_strategy(&self) -> PollStrategy;
/// Async version of [`ActiveEventLoop::create_custom_cursor()`] which waits until the
/// cursor has completely finished loading.
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture;
}
impl ActiveEventLoopExtWebSys for ActiveEventLoop {
#[inline]
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture {
self.p.create_custom_cursor_async(source)
}
impl<T> EventLoopWindowTargetExtWebSys for EventLoopWindowTarget<T> {
#[inline]
fn set_poll_strategy(&self, strategy: PollStrategy) {
self.p.set_poll_strategy(strategy);
@@ -266,109 +205,22 @@ pub enum PollStrategy {
}
pub trait CustomCursorExtWebSys {
/// Returns if this cursor is an animation.
fn is_animation(&self) -> bool;
/// Creates a new cursor from a URL pointing to an image.
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
///
/// [PNG]: https://en.wikipedia.org/wiki/PNG
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource;
/// Crates a new animated cursor from multiple [`CustomCursor`]s.
/// Supplied `cursors` can't be empty or other animations.
fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<CustomCursorSource, BadAnimation>;
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder;
}
impl CustomCursorExtWebSys for CustomCursor {
fn is_animation(&self) -> bool {
self.inner.animation
}
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource {
CustomCursorSource {
inner: PlatformCustomCursorSource::Url {
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder {
CustomCursorBuilder {
inner: PlatformCustomCursorBuilder::Url {
url,
hotspot_x,
hotspot_y,
},
}
}
fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<CustomCursorSource, BadAnimation> {
if cursors.is_empty() {
return Err(BadAnimation::Empty);
}
if cursors.iter().any(CustomCursor::is_animation) {
return Err(BadAnimation::Animation);
}
Ok(CustomCursorSource {
inner: PlatformCustomCursorSource::Animation { duration, cursors },
})
}
}
/// An error produced when using [`CustomCursor::from_animation`] with invalid arguments.
#[derive(Debug, Clone)]
pub enum BadAnimation {
/// Produced when no cursors were supplied.
Empty,
/// Produced when a supplied cursor is an animation.
Animation,
}
impl fmt::Display for BadAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "No cursors supplied"),
Self::Animation => write!(f, "A supplied cursor is an animtion"),
}
}
}
impl Error for BadAnimation {}
#[cfg(not(web_platform))]
struct PlatformCustomCursorFuture;
#[derive(Debug)]
pub struct CustomCursorFuture(pub(crate) PlatformCustomCursorFuture);
impl Future for CustomCursorFuture {
type Output = Result<CustomCursor, CustomCursorError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0)
.poll(cx)
.map_ok(|cursor| CustomCursor { inner: cursor })
}
}
#[derive(Clone, Debug)]
pub enum CustomCursorError {
Blob,
Decode(String),
Animation,
}
impl Display for CustomCursorError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Blob => write!(f, "failed to create `Blob`"),
Self::Decode(error) => write!(f, "failed to decode image: {error}"),
Self::Animation => write!(
f,
"found `CustomCursor` that is an animation when building an animation"
),
}
}
}

View File

@@ -1,15 +1,14 @@
//! # Windows
//!
//! The supported OS version is Windows 7 or higher, though Windows 10 is
//! tested regularly.
use std::{ffi::c_void, path::Path};
use crate::{
dpi::PhysicalSize,
event::DeviceId,
event::{DeviceId, KeyEvent},
event_loop::EventLoopBuilder,
keyboard::Key,
monitor::MonitorHandle,
window::{BadIcon, Icon, Window, WindowAttributes},
platform::modifier_supplement::KeyEventExtModifierSupplement,
platform_impl::WinIcon,
window::{BadIcon, Icon, Window, WindowBuilder},
};
/// Window Handle type used by Win32 API
@@ -19,92 +18,6 @@ pub type HMENU = isize;
/// Monitor Handle type used by Win32 API
pub type HMONITOR = isize;
/// Describes a system-drawn backdrop material of a window.
///
/// For a detailed explanation, see [`DWM_SYSTEMBACKDROP_TYPE docs`].
///
/// [`DWM_SYSTEMBACKDROP_TYPE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BackdropType {
/// Corresponds to `DWMSBT_AUTO`.
///
/// Usually draws a default backdrop effect on the title bar.
#[default]
Auto = 0,
/// Corresponds to `DWMSBT_NONE`.
None = 1,
/// Corresponds to `DWMSBT_MAINWINDOW`.
///
/// Draws the Mica backdrop material.
MainWindow = 2,
/// Corresponds to `DWMSBT_TRANSIENTWINDOW`.
///
/// Draws the Background Acrylic backdrop material.
TransientWindow = 3,
/// Corresponds to `DWMSBT_TABBEDWINDOW`.
///
/// Draws the Alt Mica backdrop material.
TabbedWindow = 4,
}
/// Describes a color used by Windows
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Color(u32);
impl Color {
/// Use the system's default color
pub const SYSTEM_DEFAULT: Color = Color(0xFFFFFFFF);
//Special constant only valid for the window border and therefore modeled using Option<Color> for user facing code
const NONE: Color = Color(0xFFFFFFFE);
/// Create a new color from the given RGB values
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self((r as u32) | ((g as u32) << 8) | ((b as u32) << 16))
}
}
impl Default for Color {
fn default() -> Self {
Self::SYSTEM_DEFAULT
}
}
/// Describes how the corners of a window should look like.
///
/// For a detailed explanation, see [`DWM_WINDOW_CORNER_PREFERENCE docs`].
///
/// [`DWM_WINDOW_CORNER_PREFERENCE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
#[repr(i32)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CornerPreference {
/// Corresponds to `DWMWCP_DEFAULT`.
///
/// Let the system decide when to round window corners.
#[default]
Default = 0,
/// Corresponds to `DWMWCP_DONOTROUND`.
///
/// Never round window corners.
DoNotRound = 1,
/// Corresponds to `DWMWCP_ROUND`.
///
/// Round the corners, if appropriate.
Round = 2,
/// Corresponds to `DWMWCP_ROUNDSMALL`.
///
/// Round the corners if appropriate, with a small radius.
RoundSmall = 3,
}
/// Additional methods on `EventLoop` that are specific to Windows.
pub trait EventLoopBuilderExtWindows {
/// Whether to allow the event loop to be created off of the main thread.
@@ -202,7 +115,7 @@ pub trait WindowExtWindows {
///
/// A window must be enabled before it can be activated.
/// If an application has create a modal dialog box by disabling its owner window
/// (as described in [`WindowAttributesExtWindows::with_owner_window`]), the application must enable
/// (as described in [`WindowBuilderExtWindows::with_owner_window`]), the application must enable
/// the owner window before destroying the dialog box.
/// Otherwise, another window will receive the keyboard focus and be activated.
///
@@ -223,31 +136,6 @@ pub trait WindowExtWindows {
///
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn set_undecorated_shadow(&self, shadow: bool);
/// Sets system-drawn backdrop type.
///
/// Requires Windows 11 build 22523+.
fn set_system_backdrop(&self, backdrop_type: BackdropType);
/// Sets the color of the window border.
///
/// Supported starting with Windows 11 Build 22000.
fn set_border_color(&self, color: Option<Color>);
/// Sets the background color of the title bar.
///
/// Supported starting with Windows 11 Build 22000.
fn set_title_background_color(&self, color: Option<Color>);
/// Sets the color of the window title.
///
/// Supported starting with Windows 11 Build 22000.
fn set_title_text_color(&self, color: Color);
/// Sets the preferred style of the window corners.
///
/// Supported starting with Windows 11 Build 22000.
fn set_corner_preference(&self, preference: CornerPreference);
}
impl WindowExtWindows for Window {
@@ -270,41 +158,13 @@ impl WindowExtWindows for Window {
fn set_undecorated_shadow(&self, shadow: bool) {
self.window.set_undecorated_shadow(shadow)
}
#[inline]
fn set_system_backdrop(&self, backdrop_type: BackdropType) {
self.window.set_system_backdrop(backdrop_type)
}
#[inline]
fn set_border_color(&self, color: Option<Color>) {
self.window.set_border_color(color.unwrap_or(Color::NONE))
}
#[inline]
fn set_title_background_color(&self, color: Option<Color>) {
// The windows docs don't mention NONE as a valid options but it works in practice and is useful
// to circumvent the Windows option "Show accent color on title bars and window borders"
self.window
.set_title_background_color(color.unwrap_or(Color::NONE))
}
#[inline]
fn set_title_text_color(&self, color: Color) {
self.window.set_title_text_color(color)
}
#[inline]
fn set_corner_preference(&self, preference: CornerPreference) {
self.window.set_corner_preference(preference)
}
}
/// Additional methods on `WindowAttributes` that are specific to Windows.
/// Additional methods on `WindowBuilder` that are specific to Windows.
#[allow(rustdoc::broken_intra_doc_links)]
pub trait WindowAttributesExtWindows {
pub trait WindowBuilderExtWindows {
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
/// This only works when [`WindowAttributes::with_parent_window`] isn't called or set to `None`.
/// This only works when [`WindowBuilder::with_parent_window`] isn't called or set to `None`.
/// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable)
/// on the owner window to create a modal dialog box.
///
@@ -325,14 +185,7 @@ pub trait WindowAttributesExtWindows {
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
///
#[cfg_attr(
platform_windows,
doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
)]
#[cfg_attr(
not(platform_windows),
doc = "[`CreateMenu`]: #only-available-on-windows"
)]
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu
fn with_menu(self, menu: HMENU) -> Self;
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
@@ -360,37 +213,9 @@ pub trait WindowAttributesExtWindows {
/// The shadow is hidden by default.
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn with_undecorated_shadow(self, shadow: bool) -> Self;
/// Sets system-drawn backdrop type.
///
/// Requires Windows 11 build 22523+.
fn with_system_backdrop(self, backdrop_type: BackdropType) -> Self;
/// This sets or removes `WS_CLIPCHILDREN` style.
fn with_clip_children(self, flag: bool) -> Self;
/// Sets the color of the window border.
///
/// Supported starting with Windows 11 Build 22000.
fn with_border_color(self, color: Option<Color>) -> Self;
/// Sets the background color of the title bar.
///
/// Supported starting with Windows 11 Build 22000.
fn with_title_background_color(self, color: Option<Color>) -> Self;
/// Sets the color of the window title.
///
/// Supported starting with Windows 11 Build 22000.
fn with_title_text_color(self, color: Color) -> Self;
/// Sets the preferred style of the window corners.
///
/// Supported starting with Windows 11 Build 22000.
fn with_corner_preference(self, corners: CornerPreference) -> Self;
}
impl WindowAttributesExtWindows for WindowAttributes {
impl WindowBuilderExtWindows for WindowBuilder {
#[inline]
fn with_owner_window(mut self, parent: HWND) -> Self {
self.platform_specific.owner = Some(parent);
@@ -438,42 +263,6 @@ impl WindowAttributesExtWindows for WindowAttributes {
self.platform_specific.decoration_shadow = shadow;
self
}
#[inline]
fn with_system_backdrop(mut self, backdrop_type: BackdropType) -> Self {
self.platform_specific.backdrop_type = backdrop_type;
self
}
#[inline]
fn with_clip_children(mut self, flag: bool) -> Self {
self.platform_specific.clip_children = flag;
self
}
#[inline]
fn with_border_color(mut self, color: Option<Color>) -> Self {
self.platform_specific.border_color = Some(color.unwrap_or(Color::NONE));
self
}
#[inline]
fn with_title_background_color(mut self, color: Option<Color>) -> Self {
self.platform_specific.title_background_color = Some(color.unwrap_or(Color::NONE));
self
}
#[inline]
fn with_title_text_color(mut self, color: Color) -> Self {
self.platform_specific.title_text_color = Some(color);
self
}
#[inline]
fn with_corner_preference(mut self, corners: CornerPreference) -> Self {
self.platform_specific.corner_preference = Some(corners);
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Windows.
@@ -539,12 +328,27 @@ impl IconExtWindows for Icon {
path: P,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
let win_icon = crate::platform_impl::WinIcon::from_path(path, size)?;
let win_icon = WinIcon::from_path(path, size)?;
Ok(Icon { inner: win_icon })
}
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
let win_icon = WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon })
}
}
impl KeyEventExtModifierSupplement for KeyEvent {
#[inline]
fn text_with_all_modifiers(&self) -> Option<&str> {
self.platform_specific
.text_with_all_modifers
.as_ref()
.map(|s| s.as_str())
}
#[inline]
fn key_without_modifiers(&self) -> Key {
self.platform_specific.key_without_modifiers.clone()
}
}

View File

@@ -1,58 +1,13 @@
//! # X11
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
event_loop::{ActiveEventLoop, EventLoopBuilder},
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowAttributes},
window::{Window, WindowBuilder},
};
use crate::dpi::Size;
use crate::platform_impl::{ApplicationName, Backend, XLIB_ERROR_HOOKS};
/// X window type. Maps directly to
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum WindowType {
/// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the
/// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying
/// root window clicks.
Desktop,
/// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows.
Dock,
/// Toolbar windows. "Torn off" from the main application.
Toolbar,
/// Pinnable menu windows. "Torn off" from the main application.
Menu,
/// A small persistent utility window, such as a palette or toolbox.
Utility,
/// The window is a splash screen displayed as an application is starting up.
Splash,
/// This is a dialog window.
Dialog,
/// A dropdown menu that usually appears when the user clicks on an item in a menu bar.
/// This property is typically used on override-redirect windows.
DropdownMenu,
/// A popup menu that usually appears when the user right clicks on an object.
/// This property is typically used on override-redirect windows.
PopupMenu,
/// A tooltip window. Usually used to show additional information when hovering over an object with the cursor.
/// This property is typically used on override-redirect windows.
Tooltip,
/// The window is a notification.
/// This property is typically used on override-redirect windows.
Notification,
/// This should be used on the windows that are popped up by combo boxes.
/// This property is typically used on override-redirect windows.
Combo,
/// This indicates the the window is being dragged.
/// This property is typically used on override-redirect windows.
Dnd,
/// This is a normal, top-level window.
#[default]
Normal,
}
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
/// The first argument in the provided hook will be the pointer to `XDisplay`
/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
@@ -62,7 +17,7 @@ pub enum WindowType {
pub type XlibErrorHook =
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
/// A unique identifier for an X11 visual.
/// A unique identifer for an X11 visual.
pub type XVisualID = u32;
/// A unique identifier for an X11 window.
@@ -70,7 +25,7 @@ pub type XWindow = u32;
/// Hook to winit's xlib error handling callback.
///
/// This method is provided as a safe way to handle the errors coming from X11
/// This method is provided as a safe way to handle the errors comming from X11
/// when using xlib in external crates, like glutin for GLX access. Trying to
/// handle errors by speculating with `XSetErrorHandler` is [`unsafe`].
///
@@ -83,20 +38,17 @@ pub type XWindow = u32;
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook.
unsafe {
crate::platform_impl::XLIB_ERROR_HOOKS
.lock()
.unwrap()
.push(hook);
XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to X11.
pub trait ActiveEventLoopExtX11 {
/// True if the [`ActiveEventLoop`] uses X11.
/// Additional methods on [`EventLoopWindowTarget`] that are specific to X11.
pub trait EventLoopWindowTargetExtX11 {
/// True if the [`EventLoopWindowTarget`] uses X11.
fn is_x11(&self) -> bool;
}
impl ActiveEventLoopExtX11 for ActiveEventLoop {
impl<T> EventLoopWindowTargetExtX11 for EventLoopWindowTarget<T> {
#[inline]
fn is_x11(&self) -> bool {
!self.p.is_wayland()
@@ -118,7 +70,7 @@ pub trait EventLoopBuilderExtX11 {
impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
#[inline]
fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X);
self.platform_specific.forced_backend = Some(Backend::X);
self
}
@@ -134,8 +86,8 @@ pub trait WindowExtX11 {}
impl WindowExtX11 for Window {}
/// Additional methods on [`WindowAttributes`] that are specific to X11.
pub trait WindowAttributesExtX11 {
/// Additional methods on [`WindowBuilder`] that are specific to X11.
pub trait WindowBuilderExtX11 {
/// Create this window with a specific X11 visual.
fn with_x11_visual(self, visual_id: XVisualID) -> Self;
@@ -144,29 +96,29 @@ pub trait WindowAttributesExtX11 {
/// Build window with the given `general` and `instance` names.
///
/// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "instance", "general"`.
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
/// Build window with override-redirect flag; defaults to false.
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`.
fn with_x11_window_type(self, x11_window_type: Vec<WindowType>) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
/// Build window with base size hint.
/// Build window with base size hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::Window;
/// # use winit::platform::x11::WindowAttributesExtX11;
/// # use winit::window::WindowBuilder;
/// # use winit::platform::x11::WindowBuilderExtX11;
/// // Specify the size in logical dimensions like this:
/// Window::default_attributes().with_base_size(LogicalSize::new(400.0, 200.0));
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// Window::default_attributes().with_base_size(PhysicalSize::new(400, 200));
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
/// ```
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
@@ -175,19 +127,20 @@ pub trait WindowAttributesExtX11 {
/// # Example
///
/// ```no_run
/// use winit::window::Window;
/// use winit::event_loop::ActiveEventLoop;
/// use winit::platform::x11::{XWindow, WindowAttributesExtX11};
/// # fn create_window(event_loop: &ActiveEventLoop) -> Result<(), Box<dyn std::error::Error>> {
/// use winit::window::WindowBuilder;
/// use winit::platform::x11::{XWindow, WindowBuilderExtX11};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let event_loop = winit::event_loop::EventLoop::new().unwrap();
/// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?;
/// let window_attributes = Window::default_attributes().with_embed_parent_window(parent_window_id);
/// let window = event_loop.create_window(window_attributes)?;
/// let window = WindowBuilder::new()
/// .with_embed_parent_window(parent_window_id)
/// .build(&event_loop)?;
/// # Ok(()) }
/// ```
fn with_embed_parent_window(self, parent_window_id: XWindow) -> Self;
}
impl WindowAttributesExtX11 for WindowAttributes {
impl WindowBuilderExtX11 for WindowBuilder {
#[inline]
fn with_x11_visual(mut self, visual_id: XVisualID) -> Self {
self.platform_specific.x11.visual_id = Some(visual_id);
@@ -202,10 +155,7 @@ impl WindowAttributesExtX11 for WindowAttributes {
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(crate::platform_impl::ApplicationName::new(
general.into(),
instance.into(),
));
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
self
}
@@ -216,7 +166,7 @@ impl WindowAttributesExtX11 for WindowAttributes {
}
#[inline]
fn with_x11_window_type(mut self, x11_window_types: Vec<WindowType>) -> Self {
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11.x11_window_types = x11_window_types;
self
}

View File

@@ -174,7 +174,7 @@ pub fn character_map_and_combine_key(
let key_map = match app.device_key_character_map(device_id) {
Ok(key_map) => key_map,
Err(err) => {
tracing::warn!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}");
log::warn!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}");
return None;
}
};
@@ -188,7 +188,7 @@ pub fn character_map_and_combine_key(
Ok(Some(key)) => Some(key),
Ok(None) => None,
Err(err) => {
tracing::warn!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
log::warn!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
None
}
}
@@ -213,7 +213,7 @@ pub fn character_map_and_combine_key(
None
}
Err(err) => {
tracing::warn!("KeyEvent: Failed to get key map character: {err:?}");
log::warn!("KeyEvent: Failed to get key map character: {err:?}");
*combining_accent = None;
None
}

View File

@@ -4,10 +4,9 @@ use std::{
cell::Cell,
collections::VecDeque,
hash::Hash,
marker::PhantomData,
sync::{
atomic::{AtomicBool, Ordering},
mpsc, Arc, Mutex,
mpsc, Arc, Mutex, RwLock,
},
time::{Duration, Instant},
};
@@ -16,29 +15,23 @@ use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
use android_activity::{
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
};
use tracing::{debug, trace, warn};
use once_cell::sync::Lazy;
use crate::{
cursor::Cursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error,
event::{self, Force, InnerSizeWriter, StartCause},
event_loop::{self, ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents},
event_loop::{self, ControlFlow, DeviceEvents, EventLoopWindowTarget as RootELW},
platform::pump_events::PumpStatus,
window::{
self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme,
WindowButtons, WindowLevel,
self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel,
},
};
use crate::{error::EventLoopError, platform_impl::Fullscreen};
mod keycodes;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorSource;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
static HAS_FOCUS: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true));
/// Returns the minimum `Option<Duration>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
@@ -145,7 +138,7 @@ pub struct KeyEventExtra {}
pub struct EventLoop<T: 'static> {
android_app: AndroidApp,
window_target: event_loop::ActiveEventLoop,
window_target: event_loop::EventLoopWindowTarget<T>,
redraw_flag: SharedFlag,
user_events_sender: mpsc::Sender<T>,
user_events_receiver: PeekableReceiver<T>, //must wake looper whenever something gets sent
@@ -183,8 +176,8 @@ impl<T: 'static> EventLoop<T> {
Ok(Self {
android_app: android_app.clone(),
window_target: event_loop::ActiveEventLoop {
p: ActiveEventLoop {
window_target: event_loop::EventLoopWindowTarget {
p: EventLoopWindowTarget {
app: android_app.clone(),
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(false),
@@ -192,8 +185,9 @@ impl<T: 'static> EventLoop<T> {
&redraw_flag,
android_app.create_waker(),
),
_marker: std::marker::PhantomData,
},
_marker: PhantomData,
_marker: std::marker::PhantomData,
},
redraw_flag,
user_events_sender,
@@ -209,7 +203,7 @@ impl<T: 'static> EventLoop<T> {
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F)
where
F: FnMut(event::Event<T>, &RootAEL),
F: FnMut(event::Event<T>, &RootELW<T>),
{
trace!("Mainloop iteration");
@@ -235,7 +229,7 @@ impl<T: 'static> EventLoop<T> {
warn!("TODO: find a way to notify application of content rect change");
}
MainEvent::GainedFocus => {
HAS_FOCUS.store(true, Ordering::Relaxed);
*HAS_FOCUS.write().unwrap() = true;
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
@@ -245,7 +239,7 @@ impl<T: 'static> EventLoop<T> {
);
}
MainEvent::LostFocus => {
HAS_FOCUS.store(false, Ordering::Relaxed);
*HAS_FOCUS.write().unwrap() = false;
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
@@ -330,7 +324,7 @@ impl<T: 'static> EventLoop<T> {
}
},
Err(err) => {
tracing::warn!("Failed to get input events iterator: {err:?}");
log::warn!("Failed to get input events iterator: {err:?}");
}
}
@@ -381,7 +375,7 @@ impl<T: 'static> EventLoop<T> {
callback: &mut F,
) -> InputStatus
where
F: FnMut(event::Event<T>, &RootAEL),
F: FnMut(event::Event<T>, &RootELW<T>),
{
let mut input_status = InputStatus::Handled;
match event {
@@ -486,15 +480,19 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>),
{
self.run_on_demand(event_handler)
}
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>),
{
if self.loop_running {
return Err(EventLoopError::AlreadyRunning);
}
loop {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
@@ -512,7 +510,7 @@ impl<T: 'static> EventLoop<T> {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(event::Event<T>, &RootAEL),
F: FnMut(event::Event<T>, &RootELW<T>),
{
if !self.loop_running {
self.loop_running = true;
@@ -545,7 +543,7 @@ impl<T: 'static> EventLoop<T> {
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(event::Event<T>, &RootAEL),
F: FnMut(event::Event<T>, &RootELW<T>),
{
let start = Instant::now();
@@ -621,7 +619,7 @@ impl<T: 'static> EventLoop<T> {
});
}
pub fn window_target(&self) -> &event_loop::ActiveEventLoop {
pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget<T> {
&self.window_target
}
@@ -665,25 +663,19 @@ impl<T> EventLoopProxy<T> {
}
}
pub struct ActiveEventLoop {
pub struct EventLoopWindowTarget<T: 'static> {
app: AndroidApp,
control_flow: Cell<ControlFlow>,
exit: Cell<bool>,
redraw_requester: RedrawRequester,
_marker: std::marker::PhantomData<T>,
}
impl ActiveEventLoop {
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(self.app.clone()))
}
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor {
let _ = source.inner;
CustomCursor {
inner: PlatformCustomCursor,
}
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut v = VecDeque::with_capacity(1);
v.push_back(MonitorHandle::new(self.app.clone()));
@@ -721,36 +713,9 @@ impl ActiveEventLoop {
self.exit.set(true)
}
pub(crate) fn clear_exit(&self) {
self.exit.set(false)
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get()
}
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle
}
}
#[derive(Clone)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::AndroidDisplayHandle::empty().into()
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::AndroidDisplayHandle::new().into())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
@@ -784,7 +749,19 @@ impl DeviceId {
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowAttributes;
pub struct PlatformSpecificWindowBuilderAttributes;
#[derive(Debug, Clone)]
pub(crate) struct OwnedWindowHandle {}
impl OwnedWindowHandle {
#[cfg(feature = "rwh_06")]
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
// Parent windows are currently unsupported, though owned window
// handles would be implementable.
Self {}
}
}
pub(crate) struct Window {
app: AndroidApp,
@@ -792,9 +769,10 @@ pub(crate) struct Window {
}
impl Window {
pub(crate) fn new(
el: &ActiveEventLoop,
pub(crate) fn new<T: 'static>(
el: &EventLoopWindowTarget<T>,
_window_attrs: window::WindowAttributes,
_: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, error::OsError> {
// FIXME this ignores requested window attributes
@@ -938,7 +916,9 @@ impl Window {
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
pub fn set_cursor(&self, _: Cursor) {}
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
@@ -1014,7 +994,7 @@ impl Window {
if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle()
} else {
tracing::error!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
log::error!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
Err(rwh_06::HandleError::Unavailable)
}
}
@@ -1045,7 +1025,7 @@ impl Window {
pub fn set_content_protected(&self, _protected: bool) {}
pub fn has_focus(&self) -> bool {
HAS_FOCUS.load(Ordering::Relaxed)
*HAS_FOCUS.read().unwrap()
}
pub fn title(&self) -> String {
@@ -1065,6 +1045,10 @@ impl Display for OsError {
}
}
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MonitorHandle {
app: AndroidApp,
@@ -1114,11 +1098,11 @@ impl MonitorHandle {
None
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
let size = self.size().into();
// FIXME this is not the real refresh rate
// (it is guaranteed to support 32 bit color though)
std::iter::once(VideoModeHandle {
std::iter::once(VideoMode {
size,
bit_depth: 32,
refresh_rate_millihertz: 60000,
@@ -1128,14 +1112,14 @@ impl MonitorHandle {
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct VideoModeHandle {
pub struct VideoMode {
size: (u32, u32),
bit_depth: u16,
refresh_rate_millihertz: u32,
monitor: MonitorHandle,
}
impl VideoModeHandle {
impl VideoMode {
pub fn size(&self) -> PhysicalSize<u32> {
self.size.into()
}

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

@@ -3,10 +3,10 @@
use std::{
cell::{RefCell, RefMut},
collections::HashSet,
fmt, mem,
mem,
os::raw::c_void,
ptr,
sync::{Arc, Mutex, OnceLock},
sync::{Arc, Mutex},
time::Instant,
};
@@ -22,13 +22,15 @@ use icrate::Foundation::{
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{msg_send, sel};
use once_cell::sync::Lazy;
use super::event_loop::{EventHandler, Never};
use super::uikit::UIView;
use super::window::WinitUIWindow;
use super::view::WinitUIWindow;
use crate::{
dpi::PhysicalSize,
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow},
event_loop::ControlFlow,
window::WindowId as RootWindowId,
};
@@ -45,32 +47,8 @@ macro_rules! bug_assert {
}
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;
pub(crate) struct EventLoopHandler {
#[allow(clippy::type_complexity)]
pub(crate) handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
pub(crate) event_loop: RootActiveEventLoop,
}
impl fmt::Debug for EventLoopHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopHandler")
.field("handler", &"...")
.field("event_loop", &self.event_loop)
.finish()
}
}
impl EventLoopHandler {
fn handle_event(&mut self, event: Event<HandlePendingUserEvents>) {
(self.handler)(event, &self.event_loop)
}
}
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event<HandlePendingUserEvents>),
pub enum EventWrapper {
StaticEvent(Event<Never>),
ScaleFactorChanged(ScaleFactorChanged),
}
@@ -83,7 +61,7 @@ pub struct ScaleFactorChanged {
enum UserCallbackTransitionResult<'a> {
Success {
handler: EventLoopHandler,
event_handler: Box<dyn EventHandler>,
active_control_flow: ControlFlow,
processing_redraws: bool,
},
@@ -92,7 +70,7 @@ enum UserCallbackTransitionResult<'a> {
},
}
impl Event<HandlePendingUserEvents> {
impl Event<Never> {
fn is_redraw(&self) -> bool {
matches!(
self,
@@ -116,11 +94,11 @@ enum AppStateImpl {
Launching {
queued_windows: Vec<Id<WinitUIWindow>>,
queued_events: Vec<EventWrapper>,
queued_handler: EventLoopHandler,
queued_event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
},
ProcessingEvents {
handler: EventLoopHandler,
event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
active_control_flow: ControlFlow,
},
@@ -130,15 +108,15 @@ enum AppStateImpl {
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
},
ProcessingRedraws {
handler: EventLoopHandler,
event_handler: Box<dyn EventHandler>,
active_control_flow: ControlFlow,
},
Waiting {
waiting_handler: EventLoopHandler,
waiting_event_handler: Box<dyn EventHandler>,
start: Instant,
},
PollFinished {
waiting_handler: EventLoopHandler,
waiting_event_handler: Box<dyn EventHandler>,
},
Terminated,
}
@@ -226,7 +204,7 @@ impl AppState {
matches!(self.state(), AppStateImpl::Terminated)
}
fn will_launch_transition(&mut self, queued_handler: EventLoopHandler) {
fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::NotLaunched {
queued_windows,
@@ -238,28 +216,28 @@ impl AppState {
self.set_state(AppStateImpl::Launching {
queued_windows,
queued_events,
queued_handler,
queued_event_handler,
queued_gpu_redraws,
});
}
fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) {
let (windows, events, handler, queued_gpu_redraws) = match self.take_state() {
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching {
queued_windows,
queued_events,
queued_handler,
queued_event_handler,
queued_gpu_redraws,
} => (
queued_windows,
queued_events,
queued_handler,
queued_event_handler,
queued_gpu_redraws,
),
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
handler,
event_handler,
active_control_flow: self.control_flow,
queued_gpu_redraws,
});
@@ -273,19 +251,24 @@ impl AppState {
return None;
}
let (handler, event) = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => (
waiting_handler,
let (event_handler, event) = match (self.control_flow, self.take_state()) {
(
ControlFlow::Poll,
AppStateImpl::PollFinished {
waiting_event_handler,
},
) => (
waiting_event_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)),
),
(
ControlFlow::Wait,
AppStateImpl::Waiting {
waiting_handler,
waiting_event_handler,
start,
},
) => (
waiting_handler,
waiting_event_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: None,
@@ -294,7 +277,7 @@ impl AppState {
(
ControlFlow::WaitUntil(requested_resume),
AppStateImpl::Waiting {
waiting_handler,
waiting_event_handler,
start,
},
) => {
@@ -309,13 +292,13 @@ impl AppState {
requested_resume: Some(requested_resume),
}))
};
(waiting_handler, event)
(waiting_event_handler, event)
}
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
handler,
event_handler,
queued_gpu_redraws: Default::default(),
active_control_flow: self.control_flow,
});
@@ -360,20 +343,25 @@ impl AppState {
}
}
let (handler, queued_gpu_redraws, active_control_flow, processing_redraws) =
let (event_handler, queued_gpu_redraws, active_control_flow, processing_redraws) =
match self.take_state() {
AppStateImpl::Launching { .. }
| AppStateImpl::NotLaunched { .. }
| AppStateImpl::InUserCallback { .. } => unreachable!(),
AppStateImpl::ProcessingEvents {
handler,
event_handler,
queued_gpu_redraws,
active_control_flow,
} => (handler, queued_gpu_redraws, active_control_flow, false),
AppStateImpl::ProcessingRedraws {
handler,
} => (
event_handler,
queued_gpu_redraws,
active_control_flow,
} => (handler, Default::default(), active_control_flow, true),
false,
),
AppStateImpl::ProcessingRedraws {
event_handler,
active_control_flow,
} => (event_handler, Default::default(), active_control_flow, true),
AppStateImpl::PollFinished { .. }
| AppStateImpl::Waiting { .. }
| AppStateImpl::Terminated => unreachable!(),
@@ -383,23 +371,23 @@ impl AppState {
queued_gpu_redraws,
});
UserCallbackTransitionResult::Success {
handler,
event_handler,
active_control_flow,
processing_redraws,
}
}
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> {
let (handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents {
handler,
event_handler,
queued_gpu_redraws,
active_control_flow,
} => (handler, queued_gpu_redraws, active_control_flow),
} => (event_handler, queued_gpu_redraws, active_control_flow),
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingRedraws {
handler,
event_handler,
active_control_flow,
});
queued_gpu_redraws
@@ -409,11 +397,11 @@ impl AppState {
if !self.has_launched() || self.has_terminated() {
return;
}
let (waiting_handler, old) = match self.take_state() {
let (waiting_event_handler, old) = match self.take_state() {
AppStateImpl::ProcessingRedraws {
handler,
event_handler,
active_control_flow,
} => (handler, active_control_flow),
} => (event_handler, active_control_flow),
s => bug!("unexpected state {:?}", s),
};
@@ -422,7 +410,7 @@ impl AppState {
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting {
waiting_handler,
waiting_event_handler,
start,
});
}
@@ -431,14 +419,14 @@ impl AppState {
{
let start = Instant::now();
self.set_state(AppStateImpl::Waiting {
waiting_handler,
waiting_event_handler,
start,
});
}
(_, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting {
waiting_handler,
waiting_event_handler,
start,
});
self.waker.stop()
@@ -446,22 +434,24 @@ impl AppState {
(_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting {
waiting_handler,
waiting_event_handler,
start,
});
self.waker.start_at(new_instant)
}
// Unlike on macOS, handle Poll to Poll transition here to call the waker
(_, ControlFlow::Poll) => {
self.set_state(AppStateImpl::PollFinished { waiting_handler });
self.set_state(AppStateImpl::PollFinished {
waiting_event_handler,
});
self.waker.start()
}
}
}
fn terminated_transition(&mut self) -> EventLoopHandler {
fn terminated_transition(&mut self) -> Box<dyn EventHandler> {
match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { handler, .. } => handler,
AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler,
s => bug!("`LoopExiting` happened while not processing events {:?}", s),
}
}
@@ -526,8 +516,8 @@ pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUI
}
}
pub(crate) fn will_launch(mtm: MainThreadMarker, queued_handler: EventLoopHandler) {
AppState::get_mut(mtm).will_launch_transition(queued_handler)
pub fn will_launch(mtm: MainThreadMarker, queued_event_handler: Box<dyn EventHandler>) {
AppState::get_mut(mtm).will_launch_transition(queued_event_handler)
}
pub fn did_finish_launching(mtm: MainThreadMarker) {
@@ -550,7 +540,7 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
//
// relevant iOS log:
// ```
// [ApplicationLifecycle] Windows were created before application initialization
// [ApplicationLifecycle] Windows were created before application initialzation
// completed. This may result in incorrect visual appearance.
// ```
let screen = window.screen();
@@ -604,17 +594,17 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
return;
}
let (mut handler, active_control_flow, processing_redraws) =
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
queued_events.extend(events);
return;
}
UserCallbackTransitionResult::Success {
handler,
event_handler,
active_control_flow,
processing_redraws,
} => (handler, active_control_flow, processing_redraws),
} => (event_handler, active_control_flow, processing_redraws),
};
drop(this);
@@ -622,16 +612,18 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
log::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handler.handle_event(event)
event_handler.handle_nonuser_event(event)
}
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
}
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
}
}
@@ -658,12 +650,12 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
"redraw queued while processing redraws"
);
AppStateImpl::ProcessingRedraws {
handler,
event_handler,
active_control_flow,
}
} else {
AppStateImpl::ProcessingEvents {
handler,
event_handler,
queued_gpu_redraws,
active_control_flow,
}
@@ -676,16 +668,18 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
log::warn!(
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handler.handle_event(event)
event_handler.handle_nonuser_event(event)
}
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
}
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
}
}
}
@@ -693,23 +687,23 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
fn handle_user_events(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let (mut handler, active_control_flow, processing_redraws) =
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
bug!("unexpected attempted to process an event")
}
UserCallbackTransitionResult::Success {
handler,
event_handler,
active_control_flow,
processing_redraws,
} => (handler, active_control_flow, processing_redraws),
} => (event_handler, active_control_flow, processing_redraws),
};
if processing_redraws {
bug!("user events attempted to be sent out while `ProcessingRedraws`");
}
drop(this);
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
event_handler.handle_user_events();
loop {
let mut this = AppState::get_mut(mtm);
@@ -729,7 +723,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
_ => unreachable!(),
};
this.app_state = Some(AppStateImpl::ProcessingEvents {
handler,
event_handler,
queued_gpu_redraws,
active_control_flow,
});
@@ -739,12 +733,13 @@ fn handle_user_events(mtm: MainThreadMarker) {
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => handler.handle_event(event),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
EventWrapper::StaticEvent(event) => event_handler.handle_nonuser_event(event),
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
}
}
}
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
event_handler.handle_user_events();
}
}
@@ -784,13 +779,13 @@ pub fn handle_events_cleared(mtm: MainThreadMarker) {
pub fn terminated(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let mut handler = this.terminated_transition();
let mut event_handler = this.terminated_transition();
drop(this);
handler.handle_event(Event::LoopExiting)
event_handler.handle_nonuser_event(Event::LoopExiting)
}
fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) {
fn handle_hidpi_proxy(event_handler: &mut Box<dyn EventHandler>, event: ScaleFactorChanged) {
let ScaleFactorChanged {
suggested_size,
scale_factor,
@@ -804,7 +799,7 @@ fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged)
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
},
};
handler.handle_event(event);
event_handler.handle_nonuser_event(event);
let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
@@ -901,17 +896,17 @@ macro_rules! os_capabilities {
os_version: NSOperatingSystemVersion,
}
impl OSCapabilities {
fn from_os_version(os_version: NSOperatingSystemVersion) -> Self {
impl From<NSOperatingSystemVersion> for OSCapabilities {
fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities {
$(let $name = meets_requirements(os_version, $major, $minor);)*
Self { $($name,)* os_version, }
OSCapabilities { $($name,)* os_version, }
}
}
impl OSCapabilities {$(
$(#[$attr])*
pub fn $error_name(&self, extra_msg: &str) {
tracing::warn!(
log::warn!(
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
extra_msg
@@ -949,29 +944,25 @@ fn meets_requirements(
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
}
fn get_version() -> NSOperatingSystemVersion {
unsafe {
let process_info = NSProcessInfo::processInfo();
let atleast_ios_8: bool = msg_send![
&process_info,
respondsToSelector: sel!(operatingSystemVersion)
];
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
// Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
// debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
// has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
process_info.operatingSystemVersion()
}
}
pub fn os_capabilities() -> OSCapabilities {
// Cache the version lookup for efficiency
static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new();
OS_CAPABILITIES
.get_or_init(|| OSCapabilities::from_os_version(get_version()))
.clone()
static OS_CAPABILITIES: Lazy<OSCapabilities> = Lazy::new(|| {
let version: NSOperatingSystemVersion = unsafe {
let process_info = NSProcessInfo::processInfo();
let atleast_ios_8: bool = msg_send![
&process_info,
respondsToSelector: sel!(operatingSystemVersion)
];
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
// Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
// debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
// has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
process_info.operatingSystemVersion()
};
version.into()
});
OS_CAPABILITIES.clone()
}

View File

@@ -1,6 +1,7 @@
use std::{
collections::VecDeque,
ffi::c_void,
fmt::{self, Debug},
marker::PhantomData,
ptr,
sync::mpsc::{self, Receiver, Sender},
@@ -20,33 +21,25 @@ use crate::{
error::EventLoopError,
event::Event,
event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed,
ControlFlow, DeviceEvents, EventLoopClosed,
EventLoopWindowTarget as RootEventLoopWindowTarget,
},
platform::ios::Idiom,
platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents},
window::{CustomCursor, CustomCursorSource},
};
use super::{app_delegate::AppDelegate, uikit::UIUserInterfaceIdiom};
use super::{app_state, monitor, MonitorHandle};
use super::{app_state, monitor, view, MonitorHandle};
use super::{
app_state::AppState,
uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen},
};
#[derive(Debug)]
pub struct ActiveEventLoop {
pub struct EventLoopWindowTarget<T: 'static> {
pub(super) mtm: MainThreadMarker,
p: PhantomData<T>,
}
impl ActiveEventLoop {
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor {
let _ = source.inner;
CustomCursor {
inner: super::PlatformCustomCursor,
}
}
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::uiscreens(self.mtm)
}
@@ -84,57 +77,20 @@ impl ActiveEventLoop {
pub(crate) fn exit(&self) {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programmatically
tracing::warn!("`ControlFlow::Exit` ignored on iOS");
// it is not possible to quit an iOS app gracefully and programatically
warn!("`ControlFlow::Exit` ignored on iOS");
}
pub(crate) fn exiting(&self) -> bool {
false
}
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle
}
}
#[derive(Clone)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::UiKitDisplayHandle::empty().into()
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::UiKitDisplayHandle::new().into())
}
}
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootActiveEventLoop),
receiver: mpsc::Receiver<T>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
}
}
}
}
pub struct EventLoop<T: 'static> {
mtm: MainThreadMarker,
sender: Sender<T>,
receiver: Receiver<T>,
window_target: RootActiveEventLoop,
window_target: RootEventLoopWindowTarget<T>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -166,60 +122,60 @@ impl<T: 'static> EventLoop<T> {
mtm,
sender,
receiver,
window_target: RootActiveEventLoop {
p: ActiveEventLoop { mtm },
window_target: RootEventLoopWindowTarget {
p: EventLoopWindowTarget {
mtm,
p: PhantomData,
},
_marker: PhantomData,
},
})
}
pub fn run<F>(self, handler: F) -> !
pub fn run<F>(self, event_handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{
let application = UIApplication::shared(self.mtm);
assert!(
application.is_none(),
"\
unsafe {
let application = UIApplication::shared(self.mtm);
assert!(
application.is_none(),
"\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS",
);
);
let handler = map_user_event(handler, self.receiver);
let event_handler = std::mem::transmute::<
Box<dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>)>,
Box<EventHandlerCallback<T>>,
>(Box::new(event_handler));
let handler = unsafe {
std::mem::transmute::<
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
>(Box::new(handler))
};
let handler = EventLoopHandler {
f: event_handler,
receiver: self.receiver,
event_loop: self.window_target,
};
let handler = EventLoopHandler {
handler,
event_loop: self.window_target,
};
app_state::will_launch(self.mtm, Box::new(handler));
app_state::will_launch(self.mtm, handler);
// Ensure application delegate is initialized
view::WinitApplicationDelegate::class();
// Ensure application delegate is initialized
let _ = AppDelegate::class();
unsafe {
UIApplicationMain(
0,
ptr::null(),
None,
Some(&NSString::from_str(AppDelegate::NAME)),
)
};
unreachable!()
Some(&NSString::from_str("WinitApplicationDelegate")),
);
unreachable!()
}
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
}
pub fn window_target(&self) -> &RootActiveEventLoop {
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
&self.window_target
}
}
@@ -227,14 +183,7 @@ impl<T: 'static> EventLoop<T> {
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
match UIDevice::current(self.mtm).userInterfaceIdiom() {
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
UIUserInterfaceIdiom::Phone => Idiom::Phone,
UIUserInterfaceIdiom::Pad => Idiom::Pad,
UIUserInterfaceIdiom::TV => Idiom::TV,
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
_ => Idiom::Unspecified,
}
UIDevice::current(self.mtm).userInterfaceIdiom().into()
}
}
@@ -244,7 +193,6 @@ pub struct EventLoopProxy<T> {
}
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
unsafe impl<T: Send> Sync for EventLoopProxy<T> {}
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> EventLoopProxy<T> {
@@ -394,3 +342,39 @@ fn setup_control_flow_observers() {
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
}
}
#[derive(Debug)]
pub enum Never {}
type EventHandlerCallback<T> = dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>) + 'static;
pub trait EventHandler: Debug {
fn handle_nonuser_event(&mut self, event: Event<Never>);
fn handle_user_events(&mut self);
}
struct EventLoopHandler<T: 'static> {
f: Box<EventHandlerCallback<T>>,
receiver: Receiver<T>,
event_loop: RootEventLoopWindowTarget<T>,
}
impl<T: 'static> Debug for EventLoopHandler<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopHandler")
.field("event_loop", &self.event_loop)
.finish()
}
}
impl<T: 'static> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<Never>) {
(self.f)(event.map_nonuser_event().unwrap(), &self.event_loop);
}
fn handle_user_events(&mut self) {
for event in self.receiver.try_iter() {
(self.f)(Event::UserEvent(event), &self.event_loop);
}
}
}

View File

@@ -0,0 +1,78 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::convert::TryInto;
use icrate::Foundation::{NSInteger, NSUInteger};
use objc2::encode::{Encode, Encoding};
use crate::platform::ios::{Idiom, ScreenEdge};
#[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);
}
impl From<Idiom> for UIUserInterfaceIdiom {
fn from(idiom: Idiom) -> UIUserInterfaceIdiom {
match idiom {
Idiom::Unspecified => UIUserInterfaceIdiom::Unspecified,
Idiom::Phone => UIUserInterfaceIdiom::Phone,
Idiom::Pad => UIUserInterfaceIdiom::Pad,
Idiom::TV => UIUserInterfaceIdiom::TV,
Idiom::CarPlay => UIUserInterfaceIdiom::CarPlay,
}
}
}
impl From<UIUserInterfaceIdiom> for Idiom {
fn from(ui_idiom: UIUserInterfaceIdiom) -> Idiom {
match ui_idiom {
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
UIUserInterfaceIdiom::Phone => Idiom::Phone,
UIUserInterfaceIdiom::Pad => Idiom::Pad,
UIUserInterfaceIdiom::TV => Idiom::TV,
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
_ => unreachable!(),
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(NSUInteger);
impl UIRectEdge {
pub(crate) const NONE: Self = Self(0);
}
unsafe impl Encode for UIRectEdge {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
impl From<ScreenEdge> for UIRectEdge {
fn from(screen_edge: ScreenEdge) -> UIRectEdge {
assert_eq!(
screen_edge.bits() & !ScreenEdge::ALL.bits(),
0,
"invalid `ScreenEdge`"
);
UIRectEdge(screen_edge.bits().into())
}
}
impl From<UIRectEdge> for ScreenEdge {
fn from(ui_rect_edge: UIRectEdge) -> ScreenEdge {
let bits: u8 = ui_rect_edge.0.try_into().expect("invalid `UIRectEdge`");
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
}
}

View File

@@ -1,46 +1,102 @@
//! iOS support
//!
//! # 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.
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)]
mod app_delegate;
mod app_state;
mod event_loop;
mod ffi;
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,
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
},
monitor::{MonitorHandle, VideoModeHandle},
window::{PlatformSpecificWindowAttributes, Window, WindowId},
monitor::{MonitorHandle, VideoMode},
window::{OwnedWindowHandle, PlatformSpecificWindowBuilderAttributes, Window, WindowId},
};
use self::uikit::UIScreen;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorSource;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
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;
pub struct DeviceId {
uiscreen: *const UIScreen,
}
impl DeviceId {
pub const unsafe fn dummy() -> Self {
DeviceId
DeviceId {
uiscreen: std::ptr::null(),
}
}
}
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
unsafe impl Send for DeviceId {}
unsafe impl Sync for DeviceId {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {}

View File

@@ -13,7 +13,7 @@ use objc2::Message;
use super::uikit::{UIScreen, UIScreenMode};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoModeHandle as RootVideoModeHandle,
monitor::VideoMode as RootVideoMode,
platform_impl::platform::app_state,
};
@@ -48,7 +48,7 @@ impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoModeHandle {
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32,
@@ -56,15 +56,15 @@ pub struct VideoModeHandle {
pub(crate) monitor: MonitorHandle,
}
impl VideoModeHandle {
impl VideoMode {
fn new(
uiscreen: Id<UIScreen>,
screen_mode: Id<UIScreenMode>,
mtm: MainThreadMarker,
) -> VideoModeHandle {
) -> VideoMode {
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size = screen_mode.size();
VideoModeHandle {
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate_millihertz,
@@ -135,13 +135,24 @@ impl Ord for MonitorHandle {
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MonitorHandle")
.field("name", &self.name())
.field("size", &self.size())
.field("position", &self.position())
.field("scale_factor", &self.scale_factor())
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz())
.finish_non_exhaustive()
// TODO: Do this using the proper fmt API
#[derive(Debug)]
#[allow(dead_code)]
struct MonitorHandle {
name: Option<String>,
size: PhysicalSize<u32>,
position: PhysicalPosition<i32>,
scale_factor: f64,
}
let monitor_id_proxy = MonitorHandle {
name: self.name(),
size: self.size(),
position: self.position(),
scale_factor: self.scale_factor(),
};
monitor_id_proxy.fmt(f)
}
}
@@ -196,16 +207,16 @@ impl MonitorHandle {
)
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
MainThreadMarker::run_on_main(|mtm| {
let ui_screen = self.ui_screen(mtm);
// Use Ord impl of RootVideoModeHandle
// Use Ord impl of RootVideoMode
let modes: BTreeSet<_> = ui_screen
.availableModes()
.into_iter()
.map(|mode| RootVideoModeHandle {
video_mode: VideoModeHandle::new(ui_screen.clone(), mode, mtm),
.map(|mode| RootVideoMode {
video_mode: VideoMode::new(ui_screen.clone(), mode, mtm),
})
.collect();
@@ -217,9 +228,9 @@ impl MonitorHandle {
self.ui_screen.get(mtm)
}
pub fn preferred_video_mode(&self) -> VideoModeHandle {
pub fn preferred_video_mode(&self) -> VideoMode {
MainThreadMarker::run_on_main(|mtm| {
VideoModeHandle::new(
VideoMode::new(
self.ui_screen(mtm).clone(),
self.ui_screen(mtm).preferredMode().unwrap(),
mtm,

View File

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

View File

@@ -9,8 +9,6 @@ mod application;
mod coordinate_space;
mod device;
mod event;
mod geometry;
mod gesture_recognizer;
mod responder;
mod screen;
mod screen_mode;
@@ -23,13 +21,8 @@ mod window;
pub(crate) use self::application::UIApplication;
pub(crate) use self::coordinate_space::UICoordinateSpace;
pub(crate) use self::device::{UIDevice, UIUserInterfaceIdiom};
pub(crate) use self::device::UIDevice;
pub(crate) use self::event::UIEvent;
pub(crate) use self::geometry::UIRectEdge;
pub(crate) use self::gesture_recognizer::{
UIGestureRecognizer, UIGestureRecognizerState, UIPinchGestureRecognizer,
UIRotationGestureRecognizer, UITapGestureRecognizer,
};
pub(crate) use self::responder::UIResponder;
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
pub(crate) use self::screen_mode::UIScreenMode;

View File

@@ -1,3 +1,4 @@
use crate::platform::ios::StatusBarStyle;
use icrate::Foundation::NSInteger;
use objc2::encode::{Encode, Encoding};
@@ -11,6 +12,16 @@ pub enum UIStatusBarStyle {
DarkContent = 3,
}
impl From<StatusBarStyle> for UIStatusBarStyle {
fn from(value: StatusBarStyle) -> Self {
match value {
StatusBarStyle::Default => Self::Default,
StatusBarStyle::LightContent => Self::LightContent,
StatusBarStyle::DarkContent => Self::DarkContent,
}
}
}
unsafe impl Encode for UIStatusBarStyle {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View File

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

View File

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

View File

@@ -1,33 +1,32 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::RefCell;
use std::cell::Cell;
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet};
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet};
use objc2::rc::Id;
use objc2::runtime::AnyClass;
use objc2::{
declare_class, extern_methods, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType, DeclaredClass,
};
use super::app_state::{self, EventWrapper};
use super::uikit::{
UIEvent, UIForceTouchCapability, UIGestureRecognizerState, UIPinchGestureRecognizer,
UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITouch, UITouchPhase,
UITouchType, UITraitCollection, UIView,
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask,
UIResponder, UIStatusBarStyle, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView,
UIViewController, UIWindow,
};
use super::window::WinitUIWindow;
use super::window::WindowId;
use crate::{
dpi::PhysicalPosition,
event::{Event, Force, Touch, TouchPhase, WindowEvent},
platform_impl::platform::DEVICE_ID,
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::ValidOrientations,
platform_impl::platform::{
ffi::{UIRectEdge, UIUserInterfaceIdiom},
window::PlatformSpecificWindowBuilderAttributes,
DeviceId, Fullscreen,
},
window::{WindowAttributes, WindowId as RootWindowId},
};
pub struct WinitViewState {
pinch_gesture_recognizer: RefCell<Option<Id<UIPinchGestureRecognizer>>>,
doubletap_gesture_recognizer: RefCell<Option<Id<UITapGestureRecognizer>>>,
rotation_gesture_recognizer: RefCell<Option<Id<UIRotationGestureRecognizer>>>,
}
declare_class!(
pub(crate) struct WinitView;
@@ -38,9 +37,7 @@ declare_class!(
const NAME: &'static str = "WinitUIView";
}
impl DeclaredClass for WinitView {
type Ivars = WinitViewState;
}
impl DeclaredClass for WinitView {}
unsafe impl WinitView {
#[method(drawRect:)]
@@ -162,79 +159,6 @@ declare_class!(
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[method(pinchGesture:)]
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
let window = self.window().unwrap();
let phase = match recognizer.state() {
UIGestureRecognizerState::Began => TouchPhase::Started,
UIGestureRecognizerState::Changed => TouchPhase::Moved,
UIGestureRecognizerState::Ended => TouchPhase::Ended,
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
TouchPhase::Cancelled
}
state => panic!("unexpected recognizer state: {:?}", state),
};
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::PinchGesture {
device_id: DEVICE_ID,
delta: recognizer.velocity() as _,
phase,
},
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[method(doubleTapGesture:)]
fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) {
let window = self.window().unwrap();
if recognizer.state() == UIGestureRecognizerState::Ended {
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::DoubleTapGesture {
device_id: DEVICE_ID,
},
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
}
#[method(rotationGesture:)]
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
let window = self.window().unwrap();
let phase = match recognizer.state() {
UIGestureRecognizerState::Began => TouchPhase::Started,
UIGestureRecognizerState::Changed => TouchPhase::Moved,
UIGestureRecognizerState::Ended => TouchPhase::Ended,
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
TouchPhase::Cancelled
}
state => panic!("unexpected recognizer state: {:?}", state),
};
// Flip the velocity to match macOS.
let delta = -recognizer.velocity() as _;
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::RotationGesture {
device_id: DEVICE_ID,
delta,
phase,
},
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
}
);
@@ -258,73 +182,24 @@ extern_methods!(
impl WinitView {
pub(crate) fn new(
_mtm: MainThreadMarker,
window_attributes: &WindowAttributes,
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
) -> Id<Self> {
let this = Self::alloc().set_ivars(WinitViewState {
pinch_gesture_recognizer: RefCell::new(None),
doubletap_gesture_recognizer: RefCell::new(None),
rotation_gesture_recognizer: RefCell::new(None),
});
let this: Id<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
this.setMultipleTouchEnabled(true);
if let Some(scale_factor) = window_attributes.platform_specific.scale_factor {
if let Some(scale_factor) = platform_attributes.scale_factor {
this.setContentScaleFactor(scale_factor as _);
}
this
}
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
if should_recognize {
if self.ivars().pinch_gesture_recognizer.borrow().is_none() {
let pinch: Id<UIPinchGestureRecognizer> = unsafe {
msg_send_id![UIPinchGestureRecognizer::alloc(), initWithTarget: self, action: sel!(pinchGesture:)]
};
self.addGestureRecognizer(&pinch);
self.ivars().pinch_gesture_recognizer.replace(Some(pinch));
}
} else if let Some(recognizer) = self.ivars().pinch_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer);
}
}
pub(crate) fn recognize_doubletap_gesture(&self, should_recognize: bool) {
if should_recognize {
if self.ivars().doubletap_gesture_recognizer.borrow().is_none() {
let tap: Id<UITapGestureRecognizer> = unsafe {
msg_send_id![UITapGestureRecognizer::alloc(), initWithTarget: self, action: sel!(doubleTapGesture:)]
};
tap.setNumberOfTapsRequired(2);
tap.setNumberOfTouchesRequired(1);
self.addGestureRecognizer(&tap);
self.ivars().doubletap_gesture_recognizer.replace(Some(tap));
}
} else if let Some(recognizer) = self.ivars().doubletap_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer);
}
}
pub(crate) fn recognize_rotation_gesture(&self, should_recognize: bool) {
if should_recognize {
if self.ivars().rotation_gesture_recognizer.borrow().is_none() {
let rotation: Id<UIRotationGestureRecognizer> = unsafe {
msg_send_id![UIRotationGestureRecognizer::alloc(), initWithTarget: self, action: sel!(rotationGesture:)]
};
self.addGestureRecognizer(&rotation);
self.ivars()
.rotation_gesture_recognizer
.replace(Some(rotation));
}
} else if let Some(recognizer) = self.ivars().rotation_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer);
}
}
fn handle_touches(&self, touches: &NSSet<UITouch>) {
let window = self.window().unwrap();
let uiscreen = window.screen();
let mut touch_events = Vec::new();
let os_supports_force = app_state::os_capabilities().force_touch;
for touch in touches {
@@ -377,7 +252,9 @@ impl WinitView {
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Touch(Touch {
device_id: DEVICE_ID,
device_id: RootDeviceId(DeviceId {
uiscreen: Id::as_ptr(&uiscreen),
}),
id: touch_id,
location: physical_location,
force,
@@ -389,3 +266,326 @@ impl WinitView {
app_state::handle_nonuser_events(mtm, touch_events);
}
}
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: UIStatusBarStyle) {
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: UIRectEdge) {
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,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
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(platform_attributes.prefers_status_bar_hidden);
this.set_preferred_status_bar_style(platform_attributes.preferred_status_bar_style.into());
this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations);
this.set_prefers_home_indicator_auto_hidden(
platform_attributes.prefers_home_indicator_hidden,
);
this.set_preferred_screen_edges_deferring_system_gestures(
platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into(),
);
this.setView(Some(view));
this
}
}
declare_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct WinitUIWindow;
unsafe impl ClassType for WinitUIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIWindow;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitUIWindow";
}
impl DeclaredClass for WinitUIWindow {}
unsafe impl WinitUIWindow {
#[method(becomeKeyWindow)]
fn become_key_window(&self) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(true),
}),
);
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
}
#[method(resignKeyWindow)]
fn resign_key_window(&self) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(false),
}),
);
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
}
}
);
impl WinitUIWindow {
pub(crate) fn new(
mtm: MainThreadMarker,
window_attributes: &WindowAttributes,
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
view_controller: &UIViewController,
) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
this.setRootViewController(Some(view_controller));
match window_attributes.fullscreen.0.clone().map(Into::into) {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let monitor = video_mode.monitor();
let screen = monitor.ui_screen(mtm);
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
this.setScreen(screen);
}
Some(Fullscreen::Borderless(Some(ref monitor))) => {
let screen = monitor.ui_screen(mtm);
this.setScreen(screen);
}
_ => (),
}
this
}
pub(crate) fn id(&self) -> WindowId {
(self as *const Self as usize as u64).into()
}
}
declare_class!(
pub struct WinitApplicationDelegate;
unsafe impl ClassType for WinitApplicationDelegate {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitApplicationDelegate";
}
impl DeclaredClass for WinitApplicationDelegate {}
// UIApplicationDelegate protocol
unsafe impl WinitApplicationDelegate {
#[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 WinitApplicationDelegate {
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,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

@@ -4,102 +4,37 @@ use std::collections::VecDeque;
use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker};
use objc2::rc::Id;
use objc2::runtime::{AnyObject, NSObject};
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass};
use tracing::{debug, warn};
use objc2::runtime::AnyObject;
use objc2::{class, msg_send};
use super::app_state::EventWrapper;
use super::uikit::{
UIApplication, UIResponder, UIScreen, UIScreenOverscanCompensation, UIViewController, UIWindow,
};
use super::view::WinitView;
use super::view_controller::WinitViewController;
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{
cursor::Cursor,
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon,
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
platform_impl::platform::{app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle},
platform_impl::platform::{
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, PlatformCustomCursor,
},
window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowId as RootWindowId, WindowLevel,
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
},
};
declare_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct WinitUIWindow;
#[derive(Debug, Clone)]
pub(crate) struct OwnedWindowHandle {}
unsafe impl ClassType for WinitUIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIWindow;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitUIWindow";
}
impl DeclaredClass for WinitUIWindow {}
unsafe impl WinitUIWindow {
#[method(becomeKeyWindow)]
fn become_key_window(&self) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(true),
}),
);
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
}
#[method(resignKeyWindow)]
fn resign_key_window(&self) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(false),
}),
);
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
}
}
);
impl WinitUIWindow {
pub(crate) fn new(
mtm: MainThreadMarker,
window_attributes: &WindowAttributes,
frame: CGRect,
view_controller: &UIViewController,
) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
this.setRootViewController(Some(view_controller));
match window_attributes.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let monitor = video_mode.monitor();
let screen = monitor.ui_screen(mtm);
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
this.setScreen(screen);
}
Some(Fullscreen::Borderless(Some(ref monitor))) => {
let screen = monitor.ui_screen(mtm);
this.setScreen(screen);
}
_ => (),
}
this
}
pub(crate) fn id(&self) -> WindowId {
(self as *const Self as usize as u64).into()
impl OwnedWindowHandle {
#[cfg(feature = "rwh_06")]
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
// Parent windows are currently unsupported, though owned window
// handles would be implementable (would work similar to macOS).
warn!("parent windows are unsupported on iOS");
Self {}
}
}
@@ -251,8 +186,12 @@ impl Inner {
self.view.contentScaleFactor() as _
}
pub fn set_cursor(&self, _cursor: Cursor) {
debug!("`Window::set_cursor` ignored on iOS")
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
debug!("`Window::set_cursor_icon` ignored on iOS")
}
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {
debug!("`Window::set_custom_cursor` ignored on iOS")
}
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
@@ -443,6 +382,15 @@ impl Inner {
rwh_06::RawWindowHandle::UiKit(window_handle)
}
#[cfg(feature = "rwh_06")]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
}
pub fn theme(&self) -> Option<Theme> {
warn!("`Window::theme` is ignored on iOS");
None
@@ -474,9 +422,10 @@ pub struct Window {
}
impl Window {
pub(crate) fn new(
event_loop: &ActiveEventLoop,
pub(crate) fn new<T>(
event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> {
let mtm = event_loop.mtm;
@@ -490,7 +439,7 @@ impl Window {
// TODO: transparency, visible
let main_screen = UIScreen::main(mtm);
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
let fullscreen = window_attributes.fullscreen.0.clone().map(Into::into);
let screen = match fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
@@ -514,7 +463,7 @@ impl Window {
None => screen_bounds,
};
let view = WinitView::new(mtm, &window_attributes, frame);
let view = WinitView::new(mtm, &window_attributes, &platform_attributes, frame);
let gl_or_metal_backed = unsafe {
let layer_class = WinitView::layerClass();
@@ -523,8 +472,15 @@ impl Window {
is_metal || is_gl
};
let view_controller = WinitViewController::new(mtm, &window_attributes, &view);
let window = WinitUIWindow::new(mtm, &window_attributes, frame, &view_controller);
let view_controller =
WinitViewController::new(mtm, &window_attributes, &platform_attributes, &view);
let window = WinitUIWindow::new(
mtm,
&window_attributes,
&platform_attributes,
frame,
&view_controller,
);
app_state::set_key_window(mtm, &window);
@@ -537,7 +493,7 @@ impl Window {
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = LogicalSize {
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
@@ -591,16 +547,6 @@ impl Window {
Err(rwh_06::HandleError::Unavailable)
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
}
}
// WindowExtIOS
@@ -628,7 +574,7 @@ impl Inner {
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
self.view_controller
.set_preferred_screen_edges_deferring_system_gestures(edges);
.set_preferred_screen_edges_deferring_system_gestures(edges.into());
}
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
@@ -637,19 +583,7 @@ impl Inner {
pub fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
self.view_controller
.set_preferred_status_bar_style(status_bar_style);
}
pub fn recognize_pinch_gesture(&self, should_recognize: bool) {
self.view.recognize_pinch_gesture(should_recognize);
}
pub fn recognize_doubletap_gesture(&self, should_recognize: bool) {
self.view.recognize_doubletap_gesture(should_recognize);
}
pub fn recognize_rotation_gesture(&self, should_recognize: bool) {
self.view.recognize_rotation_gesture(should_recognize);
.set_preferred_status_bar_style(status_bar_style.into());
}
}
@@ -753,8 +687,8 @@ impl From<&AnyObject> for WindowId {
}
}
#[derive(Clone, Debug, Default)]
pub struct PlatformSpecificWindowAttributes {
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub scale_factor: Option<f64>,
pub valid_orientations: ValidOrientations,
pub prefers_home_indicator_hidden: bool,

View File

@@ -1,36 +1,18 @@
//! XKB keymap.
use std::ffi::c_char;
use std::ops::Deref;
use std::ptr::{self, NonNull};
#[cfg(x11_platform)]
use x11_dl::xlib_xcb::xcb_connection_t;
#[cfg(wayland_platform)]
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
use xkb::XKB_MOD_INVALID;
use xkbcommon_dl::{
self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t,
xkb_layout_index_t, xkb_mod_index_t,
};
//! Convert XKB keys to Winit keys.
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
#[cfg(x11_platform)]
use crate::platform_impl::common::xkb::XKBXH;
use crate::platform_impl::common::xkb::{XkbContext, XKBH};
/// Map the raw X11-style keycode to the `KeyCode` enum.
///
/// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses.
pub fn raw_keycode_to_physicalkey(keycode: u32) -> PhysicalKey {
scancode_to_physicalkey(keycode.saturating_sub(8))
scancode_to_keycode(keycode.saturating_sub(8))
}
/// Map the linux scancode to Keycode.
///
/// Both X11 and Wayland use keys with `+ 8` offset to linux scancode.
pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
pub fn scancode_to_keycode(scancode: u32) -> PhysicalKey {
// The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as
// libxkbcommon's documentation seems to suggest that the keycode values we're interested in
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
@@ -522,7 +504,7 @@ pub fn keysym_to_key(keysym: u32) -> Key {
keysyms::KP_F4 => NamedKey::F4,
keysyms::KP_Home => NamedKey::Home,
keysyms::KP_Left => NamedKey::ArrowLeft,
keysyms::KP_Up => NamedKey::ArrowUp,
keysyms::KP_Up => NamedKey::ArrowLeft,
keysyms::KP_Right => NamedKey::ArrowRight,
keysyms::KP_Down => NamedKey::ArrowDown,
// keysyms::KP_Prior => NamedKey::PageUp,
@@ -912,140 +894,3 @@ pub fn keysym_location(keysym: u32) -> KeyLocation {
_ => KeyLocation::Standard,
}
}
#[derive(Debug)]
pub struct XkbKeymap {
keymap: NonNull<xkb_keymap>,
_mods_indices: ModsIndices,
pub _core_keyboard_id: i32,
}
impl XkbKeymap {
#[cfg(wayland_platform)]
pub fn from_fd(context: &XkbContext, fd: OwnedFd, size: usize) -> Option<Self> {
let map = unsafe { MmapOptions::new().len(size).map_copy_read_only(&fd).ok()? };
let keymap = unsafe {
let keymap = (XKBH.xkb_keymap_new_from_string)(
(*context).as_ptr(),
map.as_ptr() as *const _,
xkb::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
);
NonNull::new(keymap)?
};
Some(Self::new_inner(keymap, 0))
}
#[cfg(x11_platform)]
pub fn from_x11_keymap(
context: &XkbContext,
xcb: *mut xcb_connection_t,
core_keyboard_id: i32,
) -> Option<Self> {
let keymap = unsafe {
(XKBXH.xkb_x11_keymap_new_from_device)(
context.as_ptr(),
xcb,
core_keyboard_id,
xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
)
};
let keymap = NonNull::new(keymap)?;
Some(Self::new_inner(keymap, core_keyboard_id))
}
fn new_inner(keymap: NonNull<xkb_keymap>, _core_keyboard_id: i32) -> Self {
let mods_indices = ModsIndices {
shift: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_SHIFT),
caps: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CAPS),
ctrl: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CTRL),
alt: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_ALT),
num: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_NUM),
mod3: mod_index_for_name(keymap, b"Mod3\0"),
logo: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_LOGO),
mod5: mod_index_for_name(keymap, b"Mod5\0"),
};
Self {
keymap,
_mods_indices: mods_indices,
_core_keyboard_id,
}
}
#[cfg(x11_platform)]
pub fn mods_indices(&self) -> ModsIndices {
self._mods_indices
}
pub fn first_keysym_by_level(
&mut self,
layout: xkb_layout_index_t,
keycode: xkb_keycode_t,
) -> xkb_keysym_t {
unsafe {
let mut keysyms = ptr::null();
let count = (XKBH.xkb_keymap_key_get_syms_by_level)(
self.keymap.as_ptr(),
keycode,
layout,
// NOTE: The level should be zero to ignore modifiers.
0,
&mut keysyms,
);
if count == 1 {
*keysyms
} else {
0
}
}
}
/// Check whether the given key repeats.
pub fn key_repeats(&mut self, keycode: xkb_keycode_t) -> bool {
unsafe { (XKBH.xkb_keymap_key_repeats)(self.keymap.as_ptr(), keycode) == 1 }
}
}
impl Drop for XkbKeymap {
fn drop(&mut self) {
unsafe {
(XKBH.xkb_keymap_unref)(self.keymap.as_ptr());
};
}
}
impl Deref for XkbKeymap {
type Target = NonNull<xkb_keymap>;
fn deref(&self) -> &Self::Target {
&self.keymap
}
}
/// Modifier index in the keymap.
#[derive(Default, Debug, Clone, Copy)]
pub struct ModsIndices {
pub shift: Option<xkb_mod_index_t>,
pub caps: Option<xkb_mod_index_t>,
pub ctrl: Option<xkb_mod_index_t>,
pub alt: Option<xkb_mod_index_t>,
pub num: Option<xkb_mod_index_t>,
pub mod3: Option<xkb_mod_index_t>,
pub logo: Option<xkb_mod_index_t>,
pub mod5: Option<xkb_mod_index_t>,
}
fn mod_index_for_name(keymap: NonNull<xkb_keymap>, name: &[u8]) -> Option<xkb_mod_index_t> {
unsafe {
let mod_index =
(XKBH.xkb_keymap_mod_get_index)(keymap.as_ptr(), name.as_ptr() as *const c_char);
if mod_index == XKB_MOD_INVALID {
None
} else {
Some(mod_index)
}
}
}

View File

@@ -1 +1,2 @@
pub mod xkb;
pub mod keymap;
pub mod xkb_state;

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 smol_str::SmolStr;
#[cfg(wayland_platform)]
use std::os::unix::io::OwnedFd;
use tracing::warn;
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| {
tracing::warn!(
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
bytes
)
})
.ok()
}

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