Compare commits

..

214 Commits

Author SHA1 Message Date
Mads Marquart
926fb051de Rename window example to full, to signify that it's a large example 2024-03-01 09:45:56 +01:00
Mads Marquart
21fecf2cb2 Inline example helper functionality
It is more clear what's happening when you don't have to jump to the bottom of the file to look at the implementation.
2024-03-01 09:45:54 +01:00
Mads Marquart
c6dfe620ff Remove Display impl for Action 2024-03-01 09:23:52 +01:00
Mads Marquart
1bef238c2a Remove option_as_alt example state 2024-03-01 09:22:25 +01:00
Mads Marquart
1edf4f1238 Remove a level of indirection in main window example
We already have the `Action` enum that abstracts key and mouse event bindings; on top of that, putting each action handler in its own functions seems excessive.
2024-03-01 09:22:24 +01:00
Mads Marquart
72482048a0 Move monitor functionality out of the main window example 2024-03-01 08:37:25 +01:00
Kirill Chibisov
f0b4889227 Add LICENSE file to dpi crate
While the license was set to follow workspace the file itself should
be present to make package shippable in e.g. Debian/Fedora.
2024-02-28 23:13:14 +04:00
Kirill Chibisov
c81448e0b8 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-02-28 22:54:37 +04:00
Dimitris Apostolou
f498ff0369 Fix feature = "cargo-clippy" deprecation 2024-02-28 21:50:05 +04:00
Mads Marquart
c4e6e94b80 Remove a few unnecessary usages of Lazy (#3531)
* Convert usage of Lazy to OnceLock on macOS and iOS

* Remove a few uses of Lazy that wrapped Mutex or RwLock

The `new` functions on these were made `const` in Rust 1.63.0

* Use AtomicBool instead of RwLock
2024-02-28 12:28:26 +01:00
Mads Marquart
a5dbd3ee52 macOS: Refactor event handler storage (#3532)
This makes our use of `unsafe` to make the event handler temporarily 'static be local to a module, in a way that's (hopefully) much easier to reason about.
2024-02-28 04:33:47 +01:00
Mads Marquart
a4480a0652 Clean up iOS ffi.rs (#3530)
This makes it easier to transition to a future autogenerated version of UIKit.
2024-02-27 21:07:52 +01:00
Mads Marquart
e41f0eabb1 Split dpi module out into a separate crate (#3518)
Co-authored-by: John Nunley <dev@notgull.net>
2024-02-26 14:52:00 +01:00
Kirill Chibisov
7e28d7615e On X11, replay modifiers consumed by XIM 2024-02-26 12:59:41 +04:00
daxpedda
010787a430 Fix nightly CI (#3526) 2024-02-26 09:46:12 +01:00
James Liu
352e70b8ac m: Remove once_cell dependency
Removes the once_cell dependency, instead using std::sync::OnceLock and a
minimal polyfill for std::sync::LazyLock, which may be stabilized soon
(see rust-lang/rust#121377).

This should not require a bump in MSRV, as OnceLock was stabilized in 1.70,
which this crate is using.
2024-02-25 08:19:27 -08:00
Friz64
990bbf178f Update documentation regarding set_cursor_position (#3521) 2024-02-24 23:15:38 +01:00
Mads Marquart
97cfdd4b09 Move some of our documentation to docs.rs (#3478)
* Move platform-specific documentation to `winit::platform` module

* Document cargo features in crate docs

* Move version requirements to crate-level docs
2024-02-23 15:35:18 +01:00
Mads Marquart
a127bd6f66 iOS: Split classes in view.rs into separate files (#3511)
* Move application delegate to its own file
* Move window subclass to window.rs
* Split view controller to separate file
2024-02-22 22:28:49 +01:00
Kirill Chibisov
4a8648be57 On X11, force resend modifiers when focus changes
Given that `ModifiersChanged` is a window event, it means that clients
may track it for each window individually, thus not sending it between
focus changes may result in modifiers getting desynced on the consumer
side.
2024-02-22 08:30:39 +04:00
Kirill Chibisov
a8c7109e89 Pin bumpalo to 3.14.0 on CI 2024-02-21 14:44:29 +04:00
Kirill Chibisov
7abd427216 Create custom cursor with directly with event loop
Replace the `CustomCursorBuilder` with the `CustomCursorSource` and
perform the loading of the cursor via the
`EventLoop::create_custom_cursor` instead of passing it to the builder
itself.

This follows the `EventLoop::create_window` API.
2024-02-21 14:44:29 +04:00
Kirill Chibisov
3fb93b4f83 Deprecate window creation with stale event loop
Creating window when event loop is not running generally doesn't work,
since a bunch of events and sync OS requests can't be processed. This
is also an issue on e.g. Android, since window can't be created outside
event loop easily.

Thus deprecate the window creation when event loop is not running,
as well as other resource creation to running event loop.

Given that all the examples use the bad pattern of creating the window
when event loop is not running and also most example existence is
questionable, since they show single thing and the majority of their
code is window/event loop initialization, they wore merged into
a single example 'window.rs' example that showcases very simple
application using winit.

Fixes #3399.
2024-02-21 14:44:29 +04:00
Andriy
19190a95a0 On Wayland, send DeviceEvent::Motion 2024-02-20 13:34:05 +04:00
Bruce Mitchener
c4310af83c Fix various typos
Mainly fix typos in comments, but also some minor code changes:

* Rename `apply_on_poiner` to `apply_on_pointer`.
* Rename `ImeState::Commited` to `ImeState::Committed`
* Correct `cfg_attr` usage: `wayland_platfrom` -> `wayland_platform`.
2024-02-19 08:58:44 +04:00
Kirill Chibisov
542d1938ce Fix warnings with latest nightly 2024-02-19 08:47:32 +04:00
Mads Marquart
31f8b816bd Make EventLoopProxy Sync
Co-authored-by: daxpedda <daxpedda@gmail.com>
Closes: #3448
2024-02-19 08:47:32 +04:00
Kirill Chibisov
e61a7320a2 On X11, use events modifiers to detect state
While there's a separate event to deliver modifiers for keyboard,
unfortunately, it's not even remotely reflects the modifiers state.

Thus use events along side regular modifier updates to correctly
detect the state. Also, apply the modifiers from the regular
key event by converting their state to xkb modifiers state.

Links: https://github.com/alacritty/alacritty/issues/7549
Closes: #3388
2024-02-18 01:39:42 +04:00
Mads Marquart
db41938deb Remove Testers and Contributors table (#3497) 2024-02-17 09:54:29 +01:00
Mads Marquart
2bc1943188 Pin ahash in CI (#3498)
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2024-02-16 17:51:48 +01:00
Kirill Chibisov
385c4b3c88 On Wayland, update title from AboutToWait
Fixes #3472.
2024-02-14 00:48:52 +04:00
Kirill Chibisov
ea70f773d3 On X11, don't require XIM to be present
In general, we may want to use xinput v2 for keyboard input in such
cases, so we have compose going, but for now just don't crash if
there's no XIM.
2024-02-13 07:49:58 +04:00
AryaveerSR
83012f4c1c Fix Documentation for ScaleFactorChanged in WindowEvent. (#3489) 2024-02-12 18:20:03 +01:00
Jeremy Soller
6825fed073 On Orbital, implement various Window methods
Implement the following methods on the `Window`:
  - `Window::set_cursor_grab`.
  - `Window::set_cursor_visible`.
  - `Window::drag_window`.
  - `Window::drag_resize_window`.
  - `Window::set_transparent`.
  - `Window::set_visible`.
  - `Window::is_visible`.
  - `Window::set_resizable`.
  - `Window::is_resizable`.
  - `Window::set_maximized`.
  - `Window::is_maximized`.
  - `Window::set_decorations`.
  - `Window::is_decorated`.
  - `Window::set_window_level`.

To make locked pointer useful, the `DeviceEvent::MouseMotion`
event was also implemented.
2024-02-11 04:40:06 +04:00
Kirill Chibisov
273984a385 On X11, extract event handlers
Make code more clear wrt explicit returns during event handling,
which may lead to skipped IME event handling.
2024-02-11 03:31:47 +04:00
Kirill Chibisov
dbe0f852da On X11, store window target on EventProcessor
Remove the redundant `Rc` to access the window target.
2024-02-11 03:31:47 +04:00
Kirill Chibisov
d1902aa15a On X11, don't require XSETTINGS
We could fail to setup property watcher and fail to start, thus
don't require XSETTINGS to work.

Fixes: df8805c0 (On X11, reload DPI on _XSETTINGS_SETTINGS)
2024-02-10 00:24:03 +04:00
Mads Marquart
4112fccc12 Update meeting time (#3475) 2024-02-09 17:20:46 +01:00
Jeremy Soller
bd6ce32860 On Orbital, map keys to NamedKey when possible 2024-02-09 11:28:30 +04:00
Kirill Chibisov
8936fe1acd Fix nightly CI dead_code warnings 2024-02-08 13:28:26 +04:00
Jeremy Soller
fedb86ea5a On Orbital, implement KeyEventExtModifiersSupplement
This also fixes `logical_key` and `text` not reported in `KeyEvent`.
2024-02-08 12:55:11 +04:00
Kirill Chibisov
20687fef1c Fix compatibility with platforms without AtomicU64
Fixes #3456.
2024-02-08 00:58:43 +04:00
Kirill Chibisov
56035e1f13 Account for WAYLAND_SOCKET when detecting Wayland
Fixes #3459.
2024-02-07 06:41:23 +04:00
Amr Bashir
08fc4099e8 On Windows, apply ScaleFactorChanged new size if different than OS (#3408)
This fixes an issue when setting the position of the window on a new monitor and immediately maximizing it

```rs
window.set_outer_position::<PhysicalPosition<u32>>((2000, 200).into());
window.set_maximized(true);
```

Due to the nature of the event loop, the requested position and maximization state will apply correctly but due to the fact that the new position is a different monitor, a `ScaleFactorChanged` is emitted afterwards to the evenloop and a new size is set while the window is still maximized which results in a window that has `WS_MAXIMZE` window style but doesn't cover the whole monitor.
2024-02-06 20:46:30 +01:00
Kirill Chibisov
4d4d6e5052 On Wayland, fix min/max inner size setting
The size is only applied on the next `wl_surface::commit` thus we
must trigger the redraw.
2024-02-01 00:11:31 +04:00
Kirill Chibisov
cf5f4de19e Specify that alpha channel is not premultiplied 2024-01-30 21:31:17 +04:00
Kirill Chibisov
21df84b7f4 On Wayland, pre-multiply alpha for custom cursor
Fixes: #3360
2024-01-30 21:31:17 +04:00
Kirill Chibisov
dd13ccda4c On Wayland, send Focused(false) once seats left
Given that we merge all the seats, we should consider that window
is not focused once all seats wl_keyboards are no longer present.

We use seats instead of keyboards to track focus to protect against
wl_keyboard::leave not being delivered when removing the seat
(usually it's not the case though).

Fixes: #3376
2024-01-30 18:28:13 +04:00
John Nunley
df8805c0d2 On X11, reload DPI on _XSETTINGS_SETTINGS
This also fixes the deadlock when such reload may happen.

Fixes: #3383
Signed-off-by: John Nunley <dev@notgull.net>
Signed-off-by: Kirill Chibisov <contact@kchibisov.com>
2024-01-30 16:52:29 +04:00
Kirill Chibisov
db1ca45a17 Move ::builder changes to the correct release
They were added to 0.29.2 release.

Fixes: 8862ce01 (Add EventLoop::builder)
Fixes: 569c44a6 (Add Window::builder)
2024-01-30 14:28:42 +04:00
Kirill Chibisov
ff731197dc On Wayland, disable Occluded handling
Change in state requires a redraw, however drawing when getting
`Occluded` with vsync will block indefinitely, thus the event in
it's current state is rather useless.

To solve this issue winit needs a way to determine whether the user
paused/continued their render loop, so it can commit on their behalf.

This commit also forces redraw when getting configure.

Links: https://github.com/rust-windowing/winit/issues/3442
2024-01-30 13:00:10 +04:00
Mads Marquart
f526a47152 Remove EventLoopError::AlreadyRunning
This is already prevented by the type-system, and as such it doesn't
make sense to have an error case for this.
2024-01-29 22:06:03 +04:00
Mads Marquart
f204467838 Add a note about winit team meetings 2024-01-29 21:41:05 +04:00
Bruce Mitchener
6641dfa412 Bump cfg_aliases to 0.2.0 2024-01-29 21:27:23 +04:00
John Nunley
c1168b4f58 Remove drm/kms features from softbuffer (#3439)
We use softbuffer as a dev-dependency for rendering into our windows in
examples. However, we do not support a DRM/KMS backend yet, while
softbuffer comes with a DRM/KMS backend by default. This commit removes
the DRM/KMS feature from softbuffer to save some build time during
testing
2024-01-28 22:40:01 +01:00
Ulrik de Muelenaere
f8b7c4b78f bugfix: Fix swapped instance and general class names on X11
This let statement swapped the two names, resulting in incorrect
behavior since commit d7ec899d. That commit did not actually introduce
the swap, but the previous code swapped it again before setting the
WM_CLASS property, so no issue was ever observed.

It also brings the documentation in line with the implementation since the
parent commit, and with the ICCCM standard, which states the following
about the WM_CLASS property [1]:

  The two strings, respectively, are:
  * A string that names the particular instance of the application [...]
  * A string that names the general class of applications [...]

[1] https://www.x.org/releases/current/doc/xorg-docs/icccm/icccm.html#WM_CLASS_Property
2024-01-27 18:40:28 -08:00
John Nunley
3830b492c4 Update new builders for latest master
Signed-off-by: John Nunley <dev@notgull.net>
2024-01-27 10:16:52 -08:00
Mads Marquart
8862ce0163 Add EventLoop::builder, which replaces EventLoopBuilder::new
Similarly for EventLoop::with_user_event and EventLoopBuilder::with_user_event
2024-01-27 10:16:52 -08:00
Mads Marquart
569c44a632 Add Window::builder, which replaces WindowBuilder::new 2024-01-27 10:16:52 -08:00
Mads Marquart
ef2ec904ce Fix iOS gesture deltas (#3426) 2024-01-25 23:46:48 +01:00
Dubzer
98d3391f2d Add DWMWA_SYSTEMBACKDROP_TYPE support on Windows (#3257) 2024-01-25 18:59:10 +01:00
Diggory Hardy
d0a1917603 Improve error when X11/Wayland is not present 2024-01-25 14:49:36 +04:00
Mads Marquart
b36d8d1e52 Refactor user event handling on macOS/iOS (#3422)
Move user event handling to inside main event loop
2024-01-25 05:26:50 +01:00
Nick
a5b08fc48c Send the event before waking up the message pump. (#3418) 2024-01-24 21:38:20 +01:00
Mads Marquart
0482d9cfce Fix Android examples link in README (#3420) 2024-01-24 20:41:33 +01:00
Amr Bashir
10a785019c Add option to enable/disable WS_CLIPCHILDREN window style (#3212) 2024-01-22 18:55:37 +01:00
Amr Bashir
0cc19716f3 On Windows, Remove WS_CAPTION, WS_BORDER and WS_EX_WINDOWEDGE styles for child windows (#3410) 2024-01-20 13:07:03 +01:00
白山風露
572d7ee77c On Windows, set fullscreen/maximized creating window 2024-01-19 21:43:08 +04:00
sidit77
b0c59c8416 On Windows, expose DWM attributes (#3409) 2024-01-19 12:43:39 +01:00
daxpedda
d7c7ba1d6c Move PlatformSpecificWindowBuilderAttributes (#3318) 2024-01-17 23:37:28 +01:00
daxpedda
aec608f93c Document Window Drop behavior (#3315) 2024-01-17 23:17:36 +01:00
daxpedda
d1717b6a01 X11: cache custom cursors (#3366) 2024-01-17 18:17:49 +01:00
François
6b29253797 Support pinch, double tap and rotation gestures on iOS (#3130)
This is off by default on iOS. Note that pinch delta may be NaN.

Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-01-16 21:31:18 +01:00
Kirill Chibisov
30775f4982 Update platform and core maintainers 2024-01-16 21:45:29 +04:00
Mads Marquart
41070d7c67 Make DeviceId simpler on iOS (#3402)
This previously contained a UIScreen for some weird reason; perhaps
because `DeviceId` was confused for `UIDevice`?
2024-01-15 21:51:01 +01:00
John Nunley
6df972d108 feat: Add an owned display handle type
This was supposed to be rolled out with the rwh v0.6 update, but it
was left behind for some reason. I've added this type back.

Signed-off-by: John Nunley <dev@notgull.net>
2024-01-15 11:58:11 -08:00
Kirill Chibisov
73910d20cc Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-01-15 13:10:46 +04:00
daxpedda
16d860736b Platform is called Web not Wasm (#3393) 2024-01-14 18:54:52 +01:00
daxpedda
2ee44246ae Bump web-time to v1 (#3392) 2024-01-14 18:37:32 +01:00
Mads Marquart
14b418a3a7 macOS: Merge window and delegate state (#3391)
Previously we had a sort of artificial split between these, but both were accessing each other's state, since it's really the same state!
It was especially difficult to follow what happens to the fullscreen state.
So instead, we basically merge the window and the delegate files.

This does unfortunately screw a bit with the git history, apologies to whoever reads this in the future!
2024-01-14 05:19:23 +01:00
Mads Marquart
c86b0daf7f macOS: Remove unnecessary Mutex in window state (#3390) 2024-01-14 04:44:10 +01:00
Mads Marquart
40b61d2d92 macOS: Remove global HANDLER and AppState (#3389) 2024-01-14 03:37:53 +01:00
Mads Marquart
22311802b5 Remove generic parameter T from EventLoopWindowTarget (#3298) 2024-01-13 21:36:53 +01:00
daxpedda
169cd39f93 Web: improve custom cursor handling and add animated cursors (#3384) 2024-01-12 11:51:19 +01:00
Alexander Medvedev
bdeb2574dc Update Redox (#3368) 2024-01-10 20:05:52 +01:00
daxpedda
4fe38d8067 Web: increase cursor position accuracy (#3380) 2024-01-10 13:38:32 +01:00
daxpedda
816798bfd1 Web: support Firefox privacy.resistFingerprinting (#3371) 2024-01-06 23:05:51 +01:00
daxpedda
f99c810bec ci: Fix dead code error on nightly
See https://github.com/rust-lang/rust/pull/118297
2024-01-06 07:54:29 -08:00
daxpedda
d39528aa69 Web: account for canvas being focused already (#3369) 2024-01-06 16:14:27 +01:00
daxpedda
787b2d7362 Windows: cache custom cursors (#3293) 2024-01-05 17:02:08 +01:00
daxpedda
37b6243289 Deploy master docs to GitHub Pages (#3359) 2024-01-05 15:05:12 +01:00
Kirill Chibisov
8ea1da7879 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-01-05 15:18:55 +04:00
Emil Ernerfeldt
b1209bc253 On macOS, reported shifted key with shift+Ctrl/Cmd
Fixes #3078.
2024-01-05 14:53:47 +04:00
Kirill Chibisov
021fd23c34 On X11, fix error propagation in EventLoop::new
Fixes #3350.
2024-01-05 08:33:23 +04:00
nerditation
dd127463c5 Windows: Make EventLoopWindowTarget independent of UserEvent type (#3061)
* make `EventLoopWindowTarget` independent of UserEvent type

the `EventLoopWindowTarget` is needed for window creation. conceptually,
only `EventLoop` and `EventLoopProxy` need to be parameterized, and all
other parts of the backend should be agnostic about the user event type,
parallel to how `Event<T>` is parameterized, but `WindowEvent` is not.

this change removes the dependency on the type of user events from the
`EventLoopWindowTarget` for the Windows backend, but keep a phantom data
to keep the API intact. to achieve this, I moved the `Receiver` end of
the mpsc channel from `ThreadMsgTargetData` into `EventLoop` itself, so
the `UserEvent` is only passed between `EventLoop` and `EventLoopProxy`,
all other part of the backend just use unit type as a placeholder for
user events.

it's similar to the macos backend where an erased `EventHandler` trait
object is used so all component except `EventLoop` and `EventLoopProxy`
need to be parameterized. however `EventLoop` of the Windows backend
already use an `Box<dyn FnMut>` to wrap the user provided event handler
callback, so no need for an dedicated trait object, I just modified the
wrapper to replace the placeholder user event with real value pulled
from the channel. I find this is the approach which need minimum change
to be made to existing code. but it does the job and could serve as a
starting point to future Windows backend re-works.

* fix CI clippy failure.

* make UserEventPlaceholder a new type instead of alias

* invariance is maintained by top-level EventLoopWindowTarget<T>

this field is transitional and her to keep API compatibility only.
the correct variance and such is already ensured by the top-level
`EventLoopWindowTarget`, just use `PhantomData<T>` here.
2024-01-04 16:47:07 +01:00
daxpedda
ac247cd081 Fix missing target in docs.rs test (#3358) 2024-01-04 14:40:06 +01:00
daxpedda
ea1bfd254d Add Wasm atomic target to CI (#3357) 2024-01-04 14:21:19 +01:00
daxpedda
178f5fda05 Test all docs.rs deployments (#3356) 2024-01-04 13:59:31 +01:00
Mads Marquart
42dbc4748e Display all platform-specific documentation on docs.rs (#3076) 2024-01-04 12:54:35 +01:00
Kirill Chibisov
8b3de7cedf Issue resize due to scale change on Wayland
This is a regression from 8f6de4ef.

Links: https://github.com/alacritty/alacritty/issues/7559
2024-01-03 21:49:11 +04:00
Kirill Chibisov
8b0ffb7e7d On X11 and Wayland, fix numpad up being ArrowLeft
Links: https://github.com/alacritty/alacritty/issues/7533
2024-01-02 23:55:51 +04:00
Kirill Chibisov
c55a2c779b Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-12-31 20:25:19 +04:00
Kirill Chibisov
c5a422eed6 On X11, fix IME input lagging behind
IME events and requests where drained on one-by-one basis, however
we should drain all of them at once and send to user.

Links: https://github.com/alacritty/alacritty/issues/7514
2023-12-31 07:43:02 +04:00
John Nunley
1893b0ec42 On X11, cache the XRandR extension version 2023-12-30 10:04:27 +04:00
Kirill Chibisov
5e106b4dbb On X11, fix ModifiersChanged from xdotool
xdotool will update modifiers before Xkb will actually send event
updating them, thus the modifiers will be updating even before the
actual update, which is unfortunate.

Links: https://github.com/alacritty/alacritty/issues/7502
2023-12-30 09:05:03 +04:00
Kirill Chibisov
5a1d3e4656 On X11, update keymap on XkbMapNotify
This is required to handle xmodmap.

Fixes #3338.
2023-12-30 01:10:38 +04:00
Kirill Chibisov
8f6de4ef4b On Wayland, fix Window::request_inner_size during resize
The user may change the size during the on-going resize, meaning that
the size will desync with winit's internal loop which breaks viewporter
setup with fractional scaling.

Links: https://github.com/alacritty/alacritty/issues/7474
2023-12-29 21:28:06 +04:00
John Nunley
ad1843aea6 On X11, query for higher Xrandr version
This appears to be the solution for the elusive #3335 issue. Previously,
in the Xlib backend, we used the "XRRQueryVersion" function to query for
the Xrandr version, and used that to determine whether we should use the
"GetScreenResources" call or the "GetScreenResourcesCurrent" call.

However, we passed the version "0, 0" into "XRRQueryVersion".
Previously with Xlib this wasn't a problem, as Xlib ignores the version
you pass in and substitutes it with the version of RandR it expects.

https://gitlab.freedesktop.org/xorg/lib/libxrandr/-/blob/master/src/Xrandr.c?ref_type=heads#L386-387

The way that "XRRQueryVersion" is implemented on the server end, it
compares the version passed into the request with the version supported
by the server. If the server's version is greater than the client
version, it just returns the client version. If the client's version is
greater, it passes the server's version. Since we were passing in "0, 0"
this means that the server returned "0, 0".

https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/randr/rrdispatch.c?ref_type=heads#L50-59

To determine whether we use "GetScreenResources" or
"GetScreenResourcesCurrent", we compare the version returned by the
server against "1, 3". Since we got "0, 0"- a version of XRandR so old
it doesn't even exist- we use "GetScreenResources".

The problem manifests in that "GetScreenResources" can take several
seconds to query the screen state based on the current hardware
configuration. On the other hand, "GetScreenResourcesCurrent" is fast;
it uses the server's hardware cache if it is available.

This problem is visible in XTrace. On the latest `master`:

```
000:<:00c2: 12: RANDR-Request(140,0): QueryVersion major-version=0 minor-version=0
000:>:00c2:32: Reply to QueryVersion: major-version=0 minor-version=0
000:<:00c3:  8: RANDR-Request(140,8): GetScreenResources window=0x0000076e
000:>:00c3:1600: Reply to GetScreenResources:
```

On the `v0.28.0` tag:

```
000:<:0019: 12: RANDR-Request(140,0): QueryVersion major-version=1 minor-version=6
000:>:0019:32: Reply to QueryVersion: major-version=1 minor-version=6
...later
000:<:002d:  8: RANDR-Request(140,25): GetScreenResourcesCurrent window=0x0000076e
000:>:002d:1600: Reply to GetScreenResourcesCurrent
```

This commit fixes this issue by requesting "1, 3" instead. This returns
the version we expect, where we can now use "GetScreenResourcesCurrent"
properly.

Fixes #3335

Signed-off-by: John Nunley <dev@notgull.net>
2023-12-29 20:13:06 +04:00
Kirill Chibisov
ca1674519a Bump version on master (#3332)
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-12-27 10:30:59 +04:00
John Nunley
f78edc7ef1 bugfix: Change value sent to X server during minimize
Closes #3327

Signed-off-by: John Nunley <dev@notgull.net>
2023-12-26 21:25:27 -08:00
daxpedda
4f295e0c94 Add deprecated Window::set_cursor_icon() (#3329) 2023-12-26 22:40:43 +01:00
daxpedda
658f49b014 Rename VideoMode to VideoModeHandle (#3328) 2023-12-26 22:12:33 +01:00
daxpedda
34e42ff94d Remove unsound SendSyncWrapper (#3303) 2023-12-26 20:13:02 +01:00
daxpedda
ba654bb61e Add WindowBuilder::with_cursor() (#3319) 2023-12-26 19:50:58 +01:00
daxpedda
f5c691467b MacOS: check if cursor changed before applying (#3324) 2023-12-26 19:26:50 +01:00
John Nunley
a87cfb62c3 bugfix: Reload Xft database on DPI change
Closes #1228
2023-12-25 21:25:55 -08:00
daxpedda
25d6a1d46d Web: improve custom cursor loading (#3321) 2023-12-26 03:49:20 +01:00
daxpedda
e0fea25b06 Make canvas in WindowBuilder safe (#3320) 2023-12-26 01:22:10 +01:00
daxpedda
843d7904d6 On Web, add Window::(set_)prevent_default() (#3307) 2023-12-25 09:37:35 +01:00
daxpedda
28a811bbba Remove extern crate statements (#3310) 2023-12-25 09:25:09 +01:00
daxpedda
61a873d79a Remove wrong documentation on EventLoop::run() (#3314) 2023-12-25 08:27:34 +01:00
daxpedda
be4a660011 Merge Window::set_cursor_icon() and Window::set_custom_cursor() (#3308) 2023-12-25 07:20:52 +01:00
daxpedda
34dd2cdba9 Doc fixes (#3312) 2023-12-25 00:54:01 +01:00
Kirill Chibisov
775c8ece70 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-12-25 00:30:59 +04:00
Uli Schlachter
c12c7b82e8 On X11, simplify available_monitors() impl
This code confused me. I tried to understand it. I tried to simplify it
while keeping the functional style. But in the end, this just seems too
complicated for its own good. Just doing the exact same thing with a
match statement and the question mark operator makes it sooo much more
obvious what is happening.

Signed-off-by: Uli Schlachter <psychon@znc.in>
2023-12-24 22:27:02 +04:00
Kirill Chibisov
8cc5cb9d9b Fix run_on_demand exiting on consequent call
Fixes #3284.
2023-12-24 22:21:45 +04:00
Kirill Chibisov
9a28bb4b49 On Wayland, fix WindowEvent::Destroyed delivery 2023-12-24 22:21:45 +04:00
Mads Marquart
4f6fd44c6c macOS: Clean up coordinate system calculations (#3302)
* Clean up macOS and iOS monitor code a bit

* Clean up window size methods

Use `setContentSize`, `setContentMinSize`, `setContentMaxSize` and `contentRectForFrameRect` to let the windowing system figure out the required scaling, instead of us doing it manually.

* Use a flipped NSView coordinate system

* Clean up window position methods
2023-12-24 10:12:09 +01:00
Alex Butler
5a43ea8cd6 bugfix(rwh): Bump rwh_05 min version to 0.5.2
Correct min version to support "std" feature
2023-12-23 22:37:35 -08:00
Mads Marquart
e9a25a4c91 Replace remaining AppKit bindings with icrate's (#3296)
* Use icrate's window structs and enums

* Properly implement protocols

* Use icrate's NSWindow

We were previously using undocumented methods on `NSWindowTabGroup`

* Use icrate's NSApplication

And clean up some doc comments regarding NSApplication
2023-12-23 23:07:55 +01:00
Mads Marquart
674657efb6 Partially replace custom AppKit bindings with icrate's autogenerated bindings (#2982)
* Refactor winit-specific cursor logic out of appkit module

* Add relevant AppKit features that we depend on

* Use icrate's NSImageRep and NSBitmapImageRep

* Use icrate's NSImage

* Use icrate's NSCursor

* Use icrate's NSAppearance

* Use icrate's NSScreen

* Use icrate's NSButton

* Use icrate's NSAppKitVersionNumber

* Use icrate's NSTextInputContext

* Use icrate's NSColor

* Use icrate's NSEvent

* Use icrate's NSMenu and NSMenuItem

* Use icrate's NSPasteboard

* Use icrate's NSResponder

* Use icrate's NSTextInputClient

* Use icrate's NSView
2023-12-23 20:58:38 +01:00
Mads Marquart
7d5bee767c Update objc2 and icrate versions (#3256) 2023-12-23 18:04:24 +01:00
Markus Siglreithmaier
745cfaab2c On Windows, remove internal WindowWrapper (#3294)
HWND in windows-sys doesn't require a newtype wrapper for Send/Sync.
2023-12-23 17:06:43 +01:00
daxpedda
a8f49dc8ef MacOS: cache custom cursors (#3291) 2023-12-23 16:34:32 +01:00
daxpedda
e5310ade08 Custom cursor improvements (#3292) 2023-12-23 16:12:29 +01:00
daxpedda
37946e0a3a Use std::cell::OnceCell (#3290) 2023-12-22 23:49:25 +01:00
daxpedda
86b737f5e7 Fix changelog (#3289) 2023-12-22 23:36:58 +01:00
daxpedda
e37585e5bc Bump MSRV to 1.70 (#3287) 2023-12-22 23:27:36 +01:00
Mads Marquart
4aeeb24745 Window handle: Return an error when not on main thread on macOS and iOS (#3288) 2023-12-22 23:18:35 +01:00
daxpedda
8cd3aaa8a2 On Web, use the new WebCanvasWindowHandle (#3270) 2023-12-22 22:33:50 +01:00
daxpedda
2c15de7cf9 Allow custom cursor caching (#3276) 2023-12-22 22:20:41 +01:00
daxpedda
0a7ea61834 Fix some doc nits (#3274) 2023-12-22 21:46:00 +01:00
Markus Siglreithmaier
4ee11018c2 On Windows, refactor dynamic function definitions and raw input keyboard handling (#3286) 2023-12-22 18:42:17 +01:00
daxpedda
4f669ebbd2 On Web, fix context menu not being disabled (#3282) 2023-12-22 00:11:36 +01:00
Kirill Chibisov
7761b2b16c Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-12-22 00:55:52 +04:00
daxpedda
ae41e3265f On Web, correctly mark breaking changes in the changelog 2023-12-22 00:39:06 +04:00
wjian23
8702a09333 On Windows, fix IME area not working 2023-12-21 23:44:30 +04:00
Kirill Chibisov
8b5c84f404 On Wayland, ensure initial resize delivery
While we correctly configure the sizes, we also need to actually resize
the frame on initial configure and send geometry.

Fixes #3277.
2023-12-21 22:29:36 +04:00
Kirill Chibisov
a676d0018b On windows, remove empty file 2023-12-20 19:12:44 +04:00
Kirill Chibisov
04ca85a909 On Wayland, fix resize being sent on focus change
Fixes #3263.
2023-12-20 18:48:50 +04:00
daxpedda
cc33212479 On Web, fix setting cursor icon overriding cursor visibility (#3269) 2023-12-17 18:49:45 +01:00
daxpedda
f2c5127f27 Web: remove queuing fullscreen request (#3242) 2023-12-17 13:31:48 +01:00
Eero Lehtinen
af93167237 feat(all): Custom cursor images for all desktop platforms
There seems to be many PRs relating to this issue, but they don't include all
platforms and for some reason lost steam. This PR again tries to make this
feature happen, and does it for all desktop platforms (x11, wayland, macos,
windows, web).

I think the best user of this feature and the reason I'm doing this is Bevy and
game engines in general. There non laggy hardware cursors with custom images are
very important. Game devs also like their PNGs so supporting platform native
cursor files is not that important, but I guess could be added too.

Co-authored-by: daxpedda <daxpedda@gmail.com>
Co-authored-by: Mads Marquart <mads@marquart.dk>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-12-16 12:02:17 -08:00
Amr Bashir
7f6b16a6af On Windows, fix set_fullscreen early return for Fullscreen::Borderless(None) 2023-12-16 20:34:18 +04:00
Friz64
bf5806a9b2 Update sctk-adwaita to 0.8 2023-12-16 20:16:39 +04:00
Héctor Ramón
2a9c593e01 Fix typo in get_xft_dpi 2023-12-15 16:12:50 +04:00
Marijn Suijten
becdd0dbd2 On Wayland, make wl_subcompositor protocol optional
This protocol is only used for (optional) Client Side Decorations
(where) the compositor still takes the burden of compositing various
window parts together, via subsurfaces that all belong to a single
window.

If this core protocol is not available, as is the case on gamescope,
disable CSD.
2023-12-14 21:04:15 +04:00
Uli Schlachter
3eea505440 m: Update to x11rb 0.13.0
The only breaking change is that x11rb no longer reports an error when
querying the WmSizeHints of a window that does not have this property
set. For this reason, the return type of WmSizeHintsCookie::Reply()
changed from Result<WmSizeHints, SomeError> to
Result<Option<WmSizeHints>, SomeError>.

In update_normal_hints(), previously a cryptic error would be reported
to the caller. Instead, this now uses unwrap_or_default() to get a
WmSizeHints. All fields of WmSizeHints are Options, so this produces an
empty object.

resize_increments() queries a value from the window and returns an
Option. Previously, the error for "missing property" was turned into
None via .ok(). This commit adds a call to flatten() to also turn
"property not set" into None.

Finally, request_user_attention() queries a window's WmHints property
and updates one field of it. The code already uses unwrap_or_default()
to deal with missing properties, so just a call to flatten() is needed
to merge "missing property" and "error while querying" into one.

Other changes in x11rb do not seem to affect this crate.

x11rb's MSRV increased from 1.56 to 1.63, which is still below the MSRV
of this crate, which is 1.65.

Signed-off-by: Uli Schlachter <psychon@znc.in>
2023-12-09 07:02:30 -08:00
Fredrik Fornwall
b863283c38 On Windows, avoid panic in video_modes() 2023-12-06 20:47:33 +04:00
Leon
73718c9f2f Changes and improvements to the documentation (#3253)
* FEATURES.md improvements

* docs improvements

* typo fix: 'mean' -> 'main'

Co-authored-by: daxpedda <daxpedda@gmail.com>

---------

Co-authored-by: daxpedda <daxpedda@gmail.com>
2023-12-03 18:39:08 +01:00
Xiaopeng Li
f735f028a1 fix refresh_rate_millihertz on macOS (#3254)
* fix refresh_rate_millihertz on macOS

* round after conversion to mHz

* add changelog entry
2023-12-01 15:52:16 +01:00
John Nunley
da947992ac bugfix(x11): Use the right atom type in focus_window()
Closes #3248 by removing an Xlibism I forgot about

Signed-off-by: John Nunley <dev@notgull.net>
2023-11-28 16:20:36 -08:00
John Nunley
e9784127df bugfix(x11): Properly interpret float data in drag ops
Closes #3245

notgull forgot to properly interpret float data from the X server,
making him tonight's biggest loser.

Signed-off-by: John Nunley <dev@notgull.net>
2023-11-28 15:08:14 -08:00
Mads Marquart
0be2bb0a8c Remove unused .gitmodules 2023-11-29 00:56:11 +04:00
OG
075996b1fa feat: macos services menu added (#3231) 2023-11-28 21:39:12 +01:00
Emil Ernerfeldt
a7241b3db3 On macOS, remove spurious error logging when handling Fn
Fixes #3246.
2023-11-28 23:19:16 +04:00
Kirill Chibisov
17296e9878 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-11-24 18:32:53 +04:00
John Nunley
b3c87caa7c On X11, reload DPI on PropertyChange
Signed-off-by: John Nunley <dev@notgull.net>
Fixes #1228.
2023-11-24 12:14:06 +04:00
Kirill Chibisov
81a1d9c396 Fix infinite recursion in BadIcon reporting (#3237) 2023-11-23 19:15:17 +01:00
Mads Marquart
5612626944 Make Android docs build on docs.rs (#3236) 2023-11-23 08:18:05 +01:00
Arend van Beelen jr
d3ca685b77 Fix crash when running iPad build on macOS 2023-11-22 16:14:51 +04:00
Kirill Chibisov
7bed5eecfd On macOS, fix assertion when pressing Fn key 2023-11-17 15:56:03 +04:00
Kirill Chibisov
14140607d1 On Wayland, fix wl_surface being dropped first
The surface was automatically dropped due to new RAII type in SCTK
when dropping the Window, which was not the case at some point with
SCTK.

Thus destroying objects associated with it where causing issues
with some window managers.

Links: https://github.com/neovide/neovide/issues/2109
2023-11-11 20:35:30 +04:00
daxpedda
eab982c402 Web: forbid additional functions in favor of caching them (#3219) 2023-11-10 22:46:51 +01:00
Olivier Goffart
21701a33de On Windows, fix set_control_flow from `AboutToWait
In case the AboutToWait event sets the control flow to another value
it's not being used on this iteration.

Fixes #3215.
2023-11-08 19:21:33 +04:00
Nathan Lilienthal
c89e6df758 On macOS, send a Resized event after ScaleFactorChanged
Fixes #3213.
2023-11-07 02:26:02 +04:00
Kirill Chibisov
e9210555c1 On X11, try alternative cursor icon names as well
This should cover more icons.
2023-11-04 15:19:15 +04:00
Marijn Suijten
0994b5ceb8 Disable default-features for the ndk crate
We decided to add `rwh_06` to the `default` list of features in the
`ndk` to [nudge users to upgrade], but this forces `winit` to always
(transitively) include `raw-window-handle 0.6` even if the user has
set a different `rwh_xx` feature on the `winit` crate.  `winit` already
forwards the respective `rwh_xx` feaure to the `ndk` crate anyway, so
this default should just be turned off.

At the time of writing this is the only `default` feature of the `ndk`.

Links: https://github.com/rust-mobile/ndk/pull/434#issuecomment-1752089087
2023-11-04 15:18:55 +04:00
DevJac
bcce5134e1 Fix typo in pre_present_notify docs
Fix typo and other small grammar corrections.
2023-11-02 01:07:35 +04:00
Linda_pp
d333dd8664 Fix crash when minimizing example on Windows 2023-10-31 19:21:36 +04:00
Jasper Bekkers
52af1b4a77 On Windows, fix MT safety when starting drag 2023-10-31 19:20:34 +04:00
Kirill Chibisov
3c9f9da19e Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-10-28 21:09:28 +04:00
Kirill Chibisov
075dfcea19 Clarify scale_factor docs
Wayland scales each window individually, thus make it clear. Also
recommend against using the `MonitorHandle::scale_factor`.

Fixes #3183.
2023-10-28 14:23:20 +04:00
Kirill Chibisov
92b7dcccc1 On macOS, add support for Window::set_blur 2023-10-28 14:22:10 +04:00
Kirill Chibisov
5a3be586f4 On Windows, add support for Window::set_transparent 2023-10-28 02:22:45 +04:00
Kirill Chibisov
12dbbf8012 On Wayland, improve initial user size handling
Keep the user provided size in the original values and convert only
when we're getting a `configure` event. On some compositors will
have a scale available, so it'll work, however with some we'll
still have old 'pick 1` as default.

Also configure_bounds when compositor tells the user to pick the size,
that will ensure that initial `with_inner_size` won't grow beyond the
working area.

Fixes #3187.
2023-10-27 00:56:23 +04:00
Kirill Chibisov
53ca5af48f On Wayland, fix RedrawRequsted loop
The `dirty` is never cleared when decorations are hidden without
`sctk-adwaita`.

Fixes #3177.
2023-10-25 20:59:39 +04:00
Marijn Suijten
c235bd154a On wasm, provide intradoc-link for spawn() function in EventLoop docs (#3178) 2023-10-25 17:42:51 +02:00
J-P Nurmi
f4e71a1d9c On macOS, fix deadlock during nested event loops (e.g. rfd) 2023-10-25 19:13:44 +04:00
Kirill Chibisov
62ed51a138 On Winows, Fix deedlock with WM_MOUSEMOVE
The lock was still present in `None` path.

Fixes: d37d1a03b(On Windows, fix deadlock during `Cursor{Enter,Leave}`)
2023-10-25 18:32:16 +04:00
Kirill Chibisov
b2a2ec91ae Fix unused import warnings on nightly 2023-10-25 15:58:31 +04:00
Kirill Chibisov
d37d1a03b2 On Windows, fix deadlock during Cursor{Enter,Leave}
The lock was not released when calling back to the user.

Fixes #3171.
2023-10-22 19:38:54 +04:00
Kirill Chibisov
772b21ce09 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-10-21 11:58:17 +04:00
Kirill Chibisov
2edcd09704 On X11, fix cursor_hittest not reloaded on Resize
The cursor hittest was not reloaded on window size changes, only
when `Window::request_inner_size` was called leading to regions
of the window being not clickable.

Also, don't try to apply hittest logic when user never requested a
hittest.

Links: https://github.com/alacritty/alacritty/pull/7220
2023-10-21 11:09:53 +04:00
Diggory Hardy
d35c3bea42 Fix rwhd_05 doc links 2023-10-21 08:13:58 +04:00
Valaphee The Meerkat
89a184ed84 feat(windows): Fix inconsistency in mouse button device events, add hwheel device event on Windows
While working with device events, I noticed that there was an inconsistency in the mouse button device events between Windows/X11 and for example web, because web uses the same ids/order as the MouseButton enum, and Windows/X11 are using the X11 ids, and hwheel device event was ignored on Windows.

Mouse button device events are now using the same order as the MouseButton enum, and I also added hwheel device events for Windows.
2023-10-20 10:03:05 -07:00
Kirill Chibisov
36d4907da8 On Windows, fix IME APIs MT-safety
Execute the calls to the IME from the main thread.

Fixes #3123.
2023-10-20 15:46:57 +04:00
Kirill Chibisov
98b3508aca On Windows, fix RedrawRequested delivery
When calling `Window::request_redraw` from the `RedrawRequested`
handler the `RedrawWindow` won't result in `WM_PAINT` being delivered
due since user callback is run before `DefWindowProcW` is called.

Track whether the user called `Window::request_redraw` and ask for
`RedrawWindow` after running the said function during `WM_PAINT`
handling.

Fixes #3150.
2023-10-20 14:52:01 +04:00
Diggory Hardy
c0db53a516 Implement Ord/PartialOrd for ModifiersState 2023-10-20 14:51:42 +04:00
Xiaopeng Li
52b7205b75 On Windows, fix invalid hmonitor panic
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-20 14:51:04 +04:00
Arend van Beelen jr
41dbbc27a0 On iOS, add configuration for status bar style
Co-authored-by: Mads Marquart <mads@marquart.dk>
2023-10-20 14:26:10 +04:00
Kirill Chibisov
c346fb7e61 On macOS, fix tabGroup misuse
The property is marked as `Weak`, however we used strong `Id`.

Links: https://github.com/alacritty/alacritty/issues/7249
2023-10-20 14:05:57 +04:00
Kirill Chibisov
6a041f84ba Remove garbage from README
The docs are in the src/lib.rs anyway and are present on docs.rs.
2023-10-19 19:25:30 +04:00
Diggory Hardy
acfeff5327 Revise Key and KeyCode enums
Split `Key` into clear categories, like `Named`, `Dead`, Character`, `Unidentified`
removing the `#[non_exhaustive]` from the `Key` itself.

Similar action was done for the `KeyCode`.

Fixes: #2995
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-19 18:27:49 +04:00
Kirill Chibisov
b9e1e96eaa Ensure that DISPLAY vars are non-empty before using
It's common to disable Wayland by `WAYLAND_DISPLAY= <application>`.
2023-10-19 17:03:15 +04:00
Kirill Chibisov
880238a24f Fix examples not render on Wayland
The `rwh_05` feature was not enabled.

Fixes: e41fac825c (Update to new raw-window-handle strategy)
2023-10-17 07:53:38 +04:00
YouKnow
f5b4d6938f On Windows, fix CursorEntered/CursorLeft not sent during mouse grab
Fixes #3153.
2023-10-17 07:23:22 +04:00
Kirill Chibisov
c65e2247a1 On macOS, fix globe key triggering assertion
Sometimes FlagsChanged events don't carry any KeyCode information, thus
we can't create a synthetic presses events for them.

However in such cases, modifiers information is still accurate, thus
propagate it.

Fixes #2872.
2023-10-17 05:59:48 +04:00
Kirill Chibisov
801fddbfcf Make WindowBuilder Send + Sync
Window builder is always accessed by winit on the thread event loop
is on, thus it's safe to mark the data it gets as `Send + Sync`.
Each unsafe object is marked individually as `Send + Sync` instead
of just implementing `Send` and `Sync` for the whole builder.
2023-10-17 04:54:12 +04:00
Kirill Chibisov
3ad64fb811 Remove resolved deny.toml entries 2023-10-17 04:35:14 +04:00
Marijn Suijten
9bf4493a21 Upgrade to ndk 0.8, ndk-sys 0.5 + android-activity 0.5 releases
Fixes #2905.
Co-authored-by: Robert Bragg <robert@sixbynine.org>
2023-10-17 04:08:33 +04:00
daxpedda
48f6582eb4 Web Async Rework (#3082) 2023-10-16 15:50:22 +02:00
Kirill Chibisov
ef34692148 Add a note on Window::request_redraw on Windows
Fixing this could require a massive rework to how redraw is handled
on windows to the point of removing `WM_PAINT`, since it's not reliable
by any means for our use case.

For now at least document that the API is broken. It was broken like
that for a long while.
2023-10-15 23:48:37 +04:00
Kirill Chibisov
c48116a8fd Implement AsFd/AsRawFd for EventLoop<T>
This should help other crates to integrate winit's event loop into
their bigger event loop without adding an extra thread.
2023-10-15 20:31:29 +04:00
238 changed files with 19033 additions and 16721 deletions

16
.github/CODEOWNERS vendored
View File

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

View File

@@ -24,7 +24,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
toolchain: [stable, nightly, '1.65.0'] toolchain: [stable, nightly, '1.70.0']
platform: platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml! # Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, } - { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
@@ -35,20 +35,28 @@ jobs:
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } - { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' } - { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' } - { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, } - { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, } - { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, } - { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, } - { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, } - { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
include: exclude:
# Android is tested on stable-3 # Android is tested on stable-3
- toolchain: '1.69.0' - toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity' } platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- toolchain: 'stable' include:
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity' } - 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' - toolchain: 'nightly'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity' } platform: {
name: 'web Atomic',
target: wasm32-unknown-unknown,
os: ubuntu-latest,
options: '-Zbuild-std=panic_abort,std',
rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
components: rust-src,
}
env: env:
# Set more verbose terminal output # Set more verbose terminal output
@@ -56,10 +64,10 @@ jobs:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
# Faster compilation and error on warnings # Faster compilation and error on warnings
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings' RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
RUSTDOCFLAGS: '--deny=warnings'
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }} OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
CMD: ${{ matrix.platform.cmd }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -81,48 +89,49 @@ jobs:
- name: Generate lockfile - name: Generate lockfile
# Also updates the crates.io index # Also updates the crates.io index
run: cargo generate-lockfile run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0
- name: Install GCC Multilib - name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib run: sudo apt-get update && sudo apt-get install gcc-multilib
- name: Install xbuild - name: Cache cargo-apk
uses: taiki-e/install-action@v2 if: contains(matrix.platform.target, 'android')
if: contains(matrix.platform.target, 'android') || contains(matrix.platform.target, 'ios') id: cargo-apk-cache
uses: actions/cache@v3
with: with:
tool: xbuild@0.2.0 path: ~/.cargo/bin/cargo-apk
# Change this key if we update the required cargo-apk version
key: cargo-apk-v0-9-7
- uses: dtolnay/rust-toolchain@master
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
with:
toolchain: stable
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
run: cargo install cargo-apk --version=^0.9.7 --locked
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }} toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }} targets: ${{ matrix.platform.target }}
components: clippy components: clippy, ${{ matrix.platform.components }}
- name: Install LLVM tools for Android
if: contains(matrix.platform.target, 'android')
run: sudo apt-get update && sudo apt-get install lld llvm
- name: Check documentation - name: Check documentation
run: cargo doc --no-deps $OPTIONS --document-private-items run: cargo doc --no-deps $OPTIONS --document-private-items
env:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}'
- name: Build crate - name: Build crate
run: cargo build $OPTIONS run: cargo $CMD build $OPTIONS
- name: Build and package crate for Android
if: contains(matrix.platform.target, 'android')
run: x build -p android-xbuild-target --platform android --arch arm64
- name: Build and package crate for iOS
if: contains(matrix.platform.target, 'ios')
run: x build -p ios-xbuild-target --platform ios --arch arm64
- name: Build tests - name: Build tests
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
!contains(matrix.platform.target, 'android') && matrix.toolchain != '1.70.0'
matrix.toolchain != '1.65.0' run: cargo $CMD test --no-run $OPTIONS
run: cargo test --no-run $OPTIONS
- name: Run tests - name: Run tests
if: > if: >
@@ -130,8 +139,8 @@ jobs:
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0' matrix.toolchain != '1.70.0'
run: cargo test $OPTIONS run: cargo $CMD test $OPTIONS
- name: Lint with clippy - name: Lint with clippy
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features') if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
@@ -140,9 +149,8 @@ jobs:
- name: Build tests with serde enabled - name: Build tests with serde enabled
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
!contains(matrix.platform.target, 'android') && matrix.toolchain != '1.70.0'
matrix.toolchain != '1.65.0' run: cargo $CMD test --no-run $OPTIONS --features serde
run: cargo test --no-run $OPTIONS --features serde
- name: Run tests with serde enabled - name: Run tests with serde enabled
if: > if: >
@@ -150,8 +158,14 @@ jobs:
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0' matrix.toolchain != '1.70.0'
run: cargo test $OPTIONS --features serde run: cargo $CMD test $OPTIONS --features serde
- name: Check docs.rs documentation
if: matrix.toolchain == 'nightly'
run: cargo doc --no-deps $OPTIONS --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
env:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs'
# See restore step above # See restore step above
- name: Save cache of cargo folder - name: Save cache of cargo folder

50
.github/workflows/docs.yml vendored Normal file
View File

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

3
.gitmodules vendored
View File

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

View File

@@ -2,49 +2,222 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
Please keep one empty line before and after all headers. (This is required for `git` to produce a conflict when a release is made while a PR is open and the PR's changelog entry would go into the wrong section). Please keep one empty line before and after all headers. (This is required for
`git` to produce a conflict when a release is made while a PR is open and the
PR's changelog entry would go into the wrong section).
And please only add new entries to the top of this list, right below the `# Unreleased` header. And please only add new entries to the top of this list, right below the `#
Unreleased` header.
# Unreleased # Unreleased
- Renamed `EventLoopExtRunOnDemand` / `run_ondemand` to `EventLoopExtRunOnDemand` / `run_on_demand`. - Move `dpi` types to its own crate, and re-export it from the root crate.
- Make iOS `MonitorHandle` and `VideoMode` usable from other threads. - Implement `Sync` for `EventLoopProxy<T: Send>`.
- On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback. - **Breaking:** Move `Window::new` to `ActiveEventLoop::create_window` and `EventLoop::create_window` (with the latter being deprecated).
- On Web, never return a `MonitorHandle`. - **Breaking:** Rename `EventLoopWindowTarget` to `ActiveEventLoop`.
- **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`. - **Breaking:** Remove `Deref` implementation for `EventLoop` that gave `EventLoopWindowTarget`.
- On macOS, fix crash in `window.set_minimized(false)`. - **Breaking**: Remove `WindowBuilder` in favor of `WindowAttributes`.
- On Web, enable event propagation and let `DeviceEvent`s appear after `WindowEvent`s. - **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`.
- On Web, take all transient activations on the canvas and window into account to queue a fullscreen request. - On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
- On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected. - **Breaking:** Remove `Window::set_cursor_icon`
- On Wayland, fix `TouchPhase::Canceled` being sent for moved events. - Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`
- Mark `startup_notify` unsafe functions as safe. - Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Fix a bug where Wayland would be chosen on Linux even if the user specified `with_x11`. (#3058) - Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
- **Breaking:** Moved `ControlFlow` to `EventLoopWindowTarget::set_control_flow()` and `EventLoopWindowTarget::control_flow()`. - Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s.
- **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely. - Add `{Active,}EventLoop::create_custom_cursor` to load custom cursor image sources.
- On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback. - On macOS, add services menu.
- Implement `PartialOrd` and `Ord` for `MouseButton`. - **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
- On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`. - On Web, fix setting cursor icon overriding cursor visibility.
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
- On Web, add the ability to toggle calling `Event.preventDefault()` on `Window`.
- **Breaking:** Remove `WindowAttributes::fullscreen()` and expose as field directly.
- **Breaking:** Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold static data.
- **Breaking:** No longer export `platform::x11::XNotSupported`.
- **Breaking:** Renamed `platform::x11::XWindowType` to `platform::x11::WindowType`.
- Add the `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases.
- **Breaking:** Rename `TouchpadMagnify` to `PinchGesture`, `SmartMagnify` to `DoubleTapGesture` and `TouchpadRotate` to `RotationGesture` to represent the action rather than the intent.
- on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`.
- on Windows: add `with_system_backdrop`, `with_border_color`, `with_title_background_color`, `with_title_text_color` and `with_corner_preference`
- On Windows, Remove `WS_CAPTION`, `WS_BORDER` and `WS_EX_WINDOWEDGE` styles for child windows without decorations.
- **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`.
# 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
- On macOS, remove spurious error logging when handling `Fn`.
- On X11, fix an issue where floating point data from the server is
misinterpreted during a drag and drop operation.
- On X11, fix a bug where focusing the window would panic.
- On macOS, fix `refresh_rate_millihertz`.
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
- On X11, fix `Xft.dpi` detection from Xresources.
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
- On Wayland, fix resize being sent on focus change.
- On Windows, fix `set_ime_cursor_area`.
# 0.29.4
- Fix crash when running iOS app on macOS.
- On X11, check common alternative cursor names when loading cursor.
- On X11, reload the DPI after a property change event.
- On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread.
- On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account.
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
- On Wayland, fix `wl_surface` being destroyed before associated objects.
- On macOS, fix assertion when pressing `Fn` key.
- On Windows, add `WindowBuilderExtWindows::with_clip_children` to control `WS_CLIPCHILDREN` style.
# 0.29.3
- On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible.
- On Wayland, fix `RedrawRequested` being always sent without decorations and `sctk-adwaita` feature.
- On Wayland, ignore resize requests when the window is fully tiled.
- On Wayland, use `configure_bounds` to constrain `with_inner_size` when compositor wants users to pick size.
- On Windows, fix deadlock when accessing the state during `Cursor{Enter,Leave}`.
- On Windows, add support for `Window::set_transparent`.
- On macOS, fix deadlock when entering a nested event loop from an event handler.
- On macOS, add support for `Window::set_blur`.
# 0.29.2
- **Breaking:** Bump MSRV from `1.60` to `1.65`.
- **Breaking:** Add `Event::MemoryWarning`; implemented on iOS/Android.
- **Breaking:** Bump `ndk` version to `0.8.0`, ndk-sys to `0.5.0`, `android-activity` to `0.5.0`.
- **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`. - **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`.
- **Breaking:** remove `DeviceEvent::Text`. - **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`.
- On Android, fix `DeviceId` to contain device id's. - **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely.
- Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now. - **Breaking:** Moved `ControlFlow` to `EventLoopWindowTarget::set_control_flow()` and `EventLoopWindowTarget::control_flow()`.
- On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time.
- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now.
- On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground.
- **Breaking** add `Event::MemoryWarning`; implemented on iOS/Android.
- On Wayland, support `Occluded` event with xdg-shell v6
# 0.29.1-beta
- **Breaking:** Bump `ndk` version to `0.8.0-beta.0`, ndk-sys to `v0.5.0-beta.0`, `android-activity` to `0.5.0-beta.1`.
- **Breaking:** Bump MSRV from `1.64` to `1.65`.
- Make iOS windows usable from other threads.
- Reexport `raw-window-handle` in `window` module.
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
- **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>` - **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>`
- On X11, set `visual_id` in returned `raw-window-handle`. - **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
- **Breaking:** on Wayland, dispatching user created wayland queue won't wake up the loop unless winit has event to send back. - **Breaking:** on Wayland, dispatching user created Wayland queue won't wake up the loop unless winit has event to send back.
- **Breaking:** remove `DeviceEvent::Text`.
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
- **Breaking** `run() -> !` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_on_demand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** Remove all deprecated `modifiers` fields.
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
- **Breaking** Add `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events on Wayland, X11, Windows, macOS and Web.
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to the canvas size will be reported through `WindowEvent::Resized`.
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
- **Breaking:** `CursorIcon::Arrow` was removed.
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
- **Breaking:** Overhaul keyboard input handling.
- Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`.
- Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`.
- Change `Event::Key` to contain a `RawKeyEvent`.
- Remove `Event::ReceivedCharacter`. In its place, you should use
`KeyEvent.text` in combination with `WindowEvent::Ime`.
- Replace `VirtualKeyCode` with the `Key` enum.
- Replace `ScanCode` with the `KeyCode` enum.
- Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`.
- Add `PhysicalKey` wrapping `KeyCode` and `NativeKeyCode`.
- Add `KeyCode` to refer to keys (roughly) by their physical location.
- Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't
understand.
- Add `Key` to represent the keys after they've been interpreted by the
active (software) keyboard layout.
- Add `NamedKey` to represent the categorized keys.
- Add `NativeKey` to represent raw `Key`s which Winit doesn't understand.
- Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing,
but can appear simultaneously in different spots on the same keyboard
layout.
- Add `Window::reset_dead_keys` to enable application-controlled cancellation
of dead key sequences.
- Add `KeyEventExtModifierSupplement` to expose additional (and less
portable) interpretations of a given key-press.
- Add `PhysicalKeyExtScancode`, which lets you convert between scancodes and
`PhysicalKey`.
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
- Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead: - Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead:
- `platform::windows::HINSTANCE`. - `platform::windows::HINSTANCE`.
- `WindowExtWindows::hinstance`. - `WindowExtWindows::hinstance`.
@@ -61,126 +234,89 @@ And please only add new entries to the top of this list, right below the `# Unre
- `WindowExtX11::xlib_display`. - `WindowExtX11::xlib_display`.
- `WindowExtX11::xlib_screen_id`. - `WindowExtX11::xlib_screen_id`.
- `WindowExtX11::xcb_connection`. - `WindowExtX11::xcb_connection`.
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events. - Reexport `raw-window-handle` in `window` module.
- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor.
- Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
- On Wayland, make double clicking and moving the CSD frame more reliable.
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
- Implement `PartialOrd` and `Ord` for `Key`, `KeyCode`, `NativeKey`, and `NativeKeyCode`.
- Add `ElementState::is_pressed`. - Add `ElementState::is_pressed`.
- On Web, implement `WindowEvent::Occluded`. - Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
- On Web, fix touch location to be as accurate as mouse position. - Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now.
- On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position. - Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now.
- On Web, add Fullscreen API compatibility for Safari. - Implement `AsFd`/`AsRawFd` for `EventLoop<T>` on X11 and Wayland.
- On Web, implement `Window::set_(min|max)_inner_size()`. - Implement `PartialOrd` and `Ord` for `MouseButton`.
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
- On Web, fix some `WindowBuilder` methods doing nothing.
- On Web, implement `Window::focus_window()`.
- On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`.
- On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
- On Windows, add `drag_resize_window` method support.
- **Breaking** `run() ->!` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_ondemand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- Added `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
- On iOS, add force data to touch events when using the Apple Pencil.
- On Android, add force data to touch events.
# 0.29.0-beta.0
- On Web, allow event loops to be recreated with `spawn`.
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
- On Android, changed default behavior of Android to ignore volume keys letting the operating system handle them.
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
- On X11, fix `EventLoopWindowTarget::listen_device_events` effect being reversed.
- **Breaking:** Remove all deprecated `modifiers` fields.
- **Breaking:** Overhaul keyboard input handling.
- Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`.
- Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`.
- Change `Event::Key` to contain a `RawKeyEvent`.
- Remove `Event::ReceivedCharacter`. In its place, you should use
`KeyEvent.text` in combination with `WindowEvent::Ime`.
- Replace `VirtualKeyCode` with the `Key` enum.
- Replace `ScanCode` with the `KeyCode` enum.
- Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`.
- Add `KeyCode` to refer to keys (roughly) by their physical location.
- Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't
understand.
- Add `Key` to represent the keys after they've been interpreted by the
active (software) keyboard layout.
- Add `NativeKey` to represent raw `Key`s which Winit doesn't understand.
- Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing,
but can appear simultaneously in different spots on the same keyboard
layout.
- Add `Window::reset_dead_keys` to enable application-controlled cancellation
of dead key sequences.
- Add `KeyEventExtModifierSupplement` to expose additional (and less
portable) interpretations of a given key-press.
- Add `KeyCodeExtScancode`, which lets you convert between raw keycodes and
`KeyCode`.
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
- On Orbital, fix `ModifiersChanged` not being sent.
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
- **Breaking:** `CursorIcon::Arrow` was removed.
- On Wayland, fix maximized startup not taking full size on GNOME.
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
- On Wayland, fix window not checking that it actually got initial configure event.
- On Wayland, fix maximized window creation and window geometry handling.
- On Wayland, fix forward compatibility issues.
- On Wayland, add `Window::drag_resize_window` method.
- On Wayland, drop `WINIT_WAYLAND_CSD_THEME` variable.
- Implement `PartialOrd` and `Ord` on types in the `dpi` module. - Implement `PartialOrd` and `Ord` on types in the `dpi` module.
- **Breaking:** Bump MSRV from `1.60` to `1.64`. - Make `WindowBuilder` `Send + Sync`.
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted. - Make iOS `MonitorHandle` and `VideoMode` usable from other threads.
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop. - Make iOS windows usable from other threads.
- On Web: fix position of touch events to be relative to the canvas. - On Android, add force data to touch events.
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during - On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
a transient activation. - On Android, fix `DeviceId` to contain device id's.
- On Web, fix pointer button events not being processed when a buttons is already pressed. - On Orbital, fix `ModifiersChanged` not being sent.
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types. - On Wayland, `Window::outer_size` now accounts for **client side** decorations.
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs. - On Wayland, add `Window::drag_resize_window` method.
- **Breaking:** On Web, `instant` is now replaced by `web_time`. - On Wayland, remove `WINIT_WAYLAND_CSD_THEME` variable.
- On Windows, port to `windows-sys` version 0.48.0. - On Wayland, fix `TouchPhase::Canceled` being sent for moved events.
- On Web, fix pen treated as mouse input. - On Wayland, fix forward compatibility issues.
- On Web, send mouse position on button release as well. - On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
- On Web, fix touch input not gaining or loosing focus. - On Wayland, fix maximized startup not taking full size on GNOME.
- **Breaking:** On Web, dropped support for Safari versions below 13.1. - On Wayland, fix maximized window creation and window geometry handling.
- On Web, prevent clicks on the canvas to select text. - On Wayland, fix window not checking that it actually got initial configure event.
- On Wayland, make double clicking and moving the CSD frame more reliable.
- On Wayland, support `Occluded` event with xdg-shell v6
- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor.
- On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback.
- On Web, `EventLoopProxy` now implements `Send`. - On Web, `EventLoopProxy` now implements `Send`.
- On Web, `Window` now implements `Send` and `Sync`. - On Web, `Window` now implements `Send` and `Sync`.
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`. - On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position.
- On Web, use the correct canvas size when calculating the new size during scale factor change, - On Web, add Fullscreen API compatibility for Safari.
instead of using the output bitmap size. - On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and `DeviceEvent::Key` support.
- On Web, scale factor and dark mode detection are now more robust. - On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback.
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events. - On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
- On Web, fix scale factor resize suggestion always overwriting the canvas size. - On Web, allow event loops to be recreated with `spawn`.
- On macOS, fix crash when dropping `Window`. - On Web, enable event propagation.
- On Web, use `Window.requestIdleCallback()` for `ControlFlow::Poll` when available. - On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time.
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to
the canvas size will be reported through `WindowEvent::Resized`.
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
- On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window. - On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window.
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and - On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during transient activation.
`DeviceEvent::Key` support. - On Web, fix pen treated as mouse input.
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events - On Web, fix pointer button events not being processed when a buttons is already pressed.
on Wayland, X11, Windows, macOS and Web. - On Web, fix scale factor resize suggestion always overwriting the canvas size.
- On Web, fix some `WindowBuilder` methods doing nothing.
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
- On Web, fix touch input not gaining or loosing focus.
- On Web, fix touch location to be as accurate as mouse position.
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
- On Web, implement `Window::focus_window()`.
- On Web, implement `Window::set_(min|max)_inner_size()`.
- On Web, implement `WindowEvent::Occluded`.
- On Web, never return a `MonitorHandle`.
- On Web, prevent clicks on the canvas to select text.
- On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected.
- On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`.
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
- On Web, scale factor and dark mode detection are now more robust.
- On Web, send mouse position on button release as well.
- On Web, take all transient activations on the canvas and window into account to queue a fullscreen request.
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events.
- On Web, use the correct canvas size when calculating the new size during scale factor change, instead of using the output bitmap size.
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
- On Web: fix position of touch events to be relative to the canvas.
- On Windows, add `drag_resize_window` method support.
- On Windows, add horizontal MouseWheel `DeviceEvent`.
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
- On Windows, fix IME APIs not working when from non event loop thread.
- On Windows, fix `CursorEnter/Left` not being sent when grabbing the mouse.
- On Windows, fix `RedrawRequested` not being delivered when calling `Window::request_redraw` from `RedrawRequested`.
- On Windows, port to `windows-sys` version 0.48.0.
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
- On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`.
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
- On X11, set `visual_id` in returned `raw-window-handle`.
- On iOS, add ability to change the status bar style.
- On iOS, add force data to touch events when using the Apple Pencil.
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
- On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground.
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
- On macOS, fix assertion when pressing `Globe` key.
- On macOS, fix crash in `window.set_minimized(false)`.
- On macOS, fix crash when dropping `Window`.
# 0.28.7 # 0.28.7
@@ -259,7 +395,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- **Breaking:**: Removed deprecated method `platform::unix::WindowExtUnix::is_ready`. - **Breaking:**: Removed deprecated method `platform::unix::WindowExtUnix::is_ready`.
- Removed `parking_lot` dependency. - Removed `parking_lot` dependency.
- **Breaking:** On macOS, add support for two-finger touchpad magnification and rotation gestures with new events `WindowEvent::TouchpadMagnify` and `WindowEvent::TouchpadRotate`. Also add support for touchpad smart-magnification gesture with a new event `WindowEvent::SmartMagnify`. - **Breaking:** On macOS, add support for two-finger touchpad magnification and rotation gestures with new events `WindowEvent::TouchpadMagnify` and `WindowEvent::TouchpadRotate`. Also add support for touchpad smart-magnification gesture with a new event `WindowEvent::SmartMagnify`.
- **Breaking:** On web, the `WindowBuilderExtWebSys::with_prevent_default` setting (enabled by default), now additionally prevents scrolling of the webpage in mobile browsers, previously it only disabled scrolling on desktop. - **Breaking:** On Web, the `WindowBuilderExtWebSys::with_prevent_default` setting (enabled by default), now additionally prevents scrolling of the webpage in mobile browsers, previously it only disabled scrolling on desktop.
- On Wayland, `wayland-csd-adwaita` now uses `ab_glyph` instead of `crossfont` to render the title for decorations. - On Wayland, `wayland-csd-adwaita` now uses `ab_glyph` instead of `crossfont` to render the title for decorations.
- On Wayland, a new `wayland-csd-adwaita-crossfont` feature was added to use `crossfont` instead of `ab_glyph` for decorations. - On Wayland, a new `wayland-csd-adwaita-crossfont` feature was added to use `crossfont` instead of `ab_glyph` for decorations.
- On Wayland, if not otherwise specified use upstream automatic CSD theme selection. - On Wayland, if not otherwise specified use upstream automatic CSD theme selection.
@@ -279,7 +415,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- Added `Window::set_transparent` to provide a hint about transparency of the window on Wayland and macOS. - Added `Window::set_transparent` to provide a hint about transparency of the window on Wayland and macOS.
- On macOS, fix the mouse buttons other than left/right/middle being reported as middle. - On macOS, fix the mouse buttons other than left/right/middle being reported as middle.
- On Wayland, support fractional scaling via the wp-fractional-scale protocol. - On Wayland, support fractional scaling via the wp-fractional-scale protocol.
- On web, fix removal of mouse event listeners from the global object upon window distruction. - On Web, fix removal of mouse event listeners from the global object upon window destruction.
- Add WindowAttributes getter to WindowBuilder to allow introspection of default values. - Add WindowAttributes getter to WindowBuilder to allow introspection of default values.
- Added `Window::set_ime_purpose` for setting the IME purpose, currently implemented on Wayland only. - Added `Window::set_ime_purpose` for setting the IME purpose, currently implemented on Wayland only.
@@ -385,7 +521,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated. - On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated.
- On Windows, fix focus events being sent to inactive windows. - On Windows, fix focus events being sent to inactive windows.
- **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`. - **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`.
- On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors comming from Xlib. - On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors coming from Xlib.
- On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`. - On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`.
- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability. - All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability.
- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down. - **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down.
@@ -684,7 +820,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`. - On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`.
- On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode. - On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode.
- On iOS, fix panic upon closing the app. - On iOS, fix panic upon closing the app.
- On X11, allow setting mulitple `XWindowType`s. - On X11, allow setting multiple `XWindowType`s.
- On iOS, fix null window on initial `HiDpiFactorChanged` event. - On iOS, fix null window on initial `HiDpiFactorChanged` event.
- On Windows, fix fullscreen window shrinking upon getting restored to a normal window. - On Windows, fix fullscreen window shrinking upon getting restored to a normal window.
- On macOS, fix events not being emitted during modal loops, such as when windows are being resized - On macOS, fix events not being emitted during modal loops, such as when windows are being resized
@@ -837,7 +973,7 @@ and `WindowEvent::HoveredFile`.
- On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on. - On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on.
- On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area. - On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area.
- On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled. - On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled.
- On Windows, ignore the AltGr key when populating the `ModifersState` type. - On Windows, ignore the AltGr key when populating the `ModifiersState` type.
# Version 0.18.1 (2018-12-30) # Version 0.18.1 (2018-12-30)
@@ -1117,7 +1253,7 @@ _Yanked_
# Version 0.8.2 (2017-09-28) # Version 0.8.2 (2017-09-28)
- Uniformize keyboard scancode values accross Wayland and X11 (#297). - Uniformize keyboard scancode values across Wayland and X11 (#297).
- Internal rework of the wayland event loop - Internal rework of the wayland event loop
- Added method `os::linux::WindowExt::is_ready` - Added method `os::linux::WindowExt::is_ready`
@@ -1131,7 +1267,7 @@ _Yanked_
- Added `Window::set_maximized`, `WindowAttributes::maximized` and `WindowBuilder::with_maximized`. - Added `Window::set_maximized`, `WindowAttributes::maximized` and `WindowBuilder::with_maximized`.
- Added `Window::set_fullscreen`. - Added `Window::set_fullscreen`.
- Changed `with_fullscreen` to take a `Option<MonitorId>` instead of a `MonitorId`. - Changed `with_fullscreen` to take a `Option<MonitorId>` instead of a `MonitorId`.
- Removed `MonitorId::get_native_identifer()` in favor of platform-specific traits in the `os` - Removed `MonitorId::get_native_identifier()` in favor of platform-specific traits in the `os`
module. module.
- Changed `get_available_monitors()` and `get_primary_monitor()` to be methods of `EventsLoop` - Changed `get_available_monitors()` and `get_primary_monitor()` to be methods of `EventsLoop`
instead of stand-alone methods. instead of stand-alone methods.

View File

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

View File

@@ -1,20 +1,27 @@
[package] [package]
name = "winit" name = "winit"
version = "0.29.1-beta" version = "0.29.11"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"] authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library." description = "Cross-platform window creation library."
edition = "2021"
keywords = ["windowing"] keywords = ["windowing"]
license = "Apache-2.0"
readme = "README.md" readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit" documentation = "https://docs.rs/winit"
categories = ["gui"] categories = ["gui"]
rust-version = "1.65.0" rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["rwh_04", "rwh_05", "rwh_06", "serde"] features = [
default-target = "x86_64-unknown-linux-gnu" "rwh_04",
"rwh_05",
"rwh_06",
"serde",
"mint",
# Enabled to get docs to compile
"android-native-activity",
]
# These are all tested in CI # These are all tested in CI
targets = [ targets = [
# Windows # Windows
@@ -29,11 +36,12 @@ targets = [
"x86_64-apple-ios", "x86_64-apple-ios",
# Android # Android
"aarch64-linux-android", "aarch64-linux-android",
# WebAssembly # Web
"wasm32-unknown-unknown", "wasm32-unknown-unknown",
] ]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
# Features are documented in either `lib.rs` or under `winit::platform`.
[features] [features]
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"] x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
@@ -44,45 +52,48 @@ wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"] wayland-csd-adwaita-notitle = ["sctk-adwaita"]
android-native-activity = ["android-activity/native-activity"] android-native-activity = ["android-activity/native-activity"]
android-game-activity = ["android-activity/game-activity"] android-game-activity = ["android-activity/game-activity"]
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"] serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde"]
mint = ["dpi/mint"]
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
[build-dependencies] [build-dependencies]
cfg_aliases = "0.1.1" cfg_aliases = "0.2.0"
[dependencies] [dependencies]
bitflags = "2" bitflags = "2"
cursor-icon = "1.0.0" cursor-icon = "1.1.0"
log = "0.4" 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_04 = { package = "raw-window-handle", version = "0.4", optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5", features = ["std"], optional = true } rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true } rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
serde = { version = "1", optional = true, features = ["serde_derive"] } serde = { workspace = true, optional = true }
smol_str = "0.2.0" smol_str = "0.2.0"
dpi = { path = "dpi" }
[dev-dependencies] [dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] } image = { version = "0.24.0", default-features = false, features = ["png"] }
simple_logger = { version = "2.1.0", default_features = false } 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] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
softbuffer = "0.3.0" softbuffer = { version = "0.3.0", default-features = false, features = ["x11", "x11-dlopen", "wayland", "wayland-dlopen"] }
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
# Coordinate the next winit release android-activity 0.5 release android-activity = "0.5.0"
android-activity = "=0.5.0-beta.1" ndk = { version = "0.8.0", default-features = false }
ndk = "=0.8.0-beta.0" ndk-sys = "0.5.0"
ndk-sys = "=0.5.0-beta.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3" core-foundation = "0.9.3"
objc2 = "0.4.1" objc2 = "0.5.0"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1" core-graphics = "0.23.1"
[target.'cfg(target_os = "macos")'.dependencies.icrate] [target.'cfg(target_os = "macos")'.dependencies.icrate]
version = "0.0.4" version = "0.1.0"
features = [ features = [
"dispatch", "dispatch",
"Foundation", "Foundation",
@@ -95,10 +106,31 @@ features = [
"Foundation_NSProcessInfo", "Foundation_NSProcessInfo",
"Foundation_NSThread", "Foundation_NSThread",
"Foundation_NSNumber", "Foundation_NSNumber",
"AppKit",
"AppKit_NSAppearance",
"AppKit_NSApplication",
"AppKit_NSBitmapImageRep",
"AppKit_NSButton",
"AppKit_NSColor",
"AppKit_NSControl",
"AppKit_NSCursor",
"AppKit_NSEvent",
"AppKit_NSGraphicsContext",
"AppKit_NSImage",
"AppKit_NSImageRep",
"AppKit_NSMenu",
"AppKit_NSMenuItem",
"AppKit_NSPasteboard",
"AppKit_NSResponder",
"AppKit_NSScreen",
"AppKit_NSTextInputContext",
"AppKit_NSView",
"AppKit_NSWindow",
"AppKit_NSWindowTabGroup",
] ]
[target.'cfg(target_os = "ios")'.dependencies.icrate] [target.'cfg(target_os = "ios")'.dependencies.icrate]
version = "0.0.4" version = "0.1.0"
features = [ features = [
"dispatch", "dispatch",
"Foundation", "Foundation",
@@ -142,7 +174,7 @@ features = [
] ]
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies] [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
ahash = { version = "0.8.3", features = ["no-rng"], optional = true } ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true } bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.12.3" calloop = "0.12.3"
libc = "0.2.64" libc = "0.2.64"
@@ -150,18 +182,18 @@ memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true } percent-encoding = { version = "2.0", optional = true }
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] } rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true } sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.7.0", default_features = false, optional = true } sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true } wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
wayland-client = { version = "0.31.1", optional = true } wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true } wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true } wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
x11-dl = { version = "2.18.5", optional = true } x11-dl = { version = "2.18.5", optional = true }
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true } x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
xkbcommon-dl = "0.4.0" xkbcommon-dl = "0.4.2"
[target.'cfg(target_os = "redox")'.dependencies] [target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.42", default-features = false } orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.3" redox_syscall = "0.4.1"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys] [target.'cfg(target_family = "wasm")'.dependencies.web_sys]
package = "web-sys" package = "web-sys"
@@ -169,9 +201,11 @@ version = "0.3.64"
features = [ features = [
'AbortController', 'AbortController',
'AbortSignal', 'AbortSignal',
'Blob',
'console', 'console',
'CssStyleDeclaration', 'CssStyleDeclaration',
'Document', 'Document',
'DomException',
'DomRect', 'DomRect',
'DomRectReadOnly', 'DomRectReadOnly',
'Element', 'Element',
@@ -180,6 +214,11 @@ features = [
'FocusEvent', 'FocusEvent',
'HtmlCanvasElement', 'HtmlCanvasElement',
'HtmlElement', 'HtmlElement',
'HtmlImageElement',
'ImageBitmap',
'ImageBitmapOptions',
'ImageBitmapRenderingContext',
'ImageData',
'IntersectionObserver', 'IntersectionObserver',
'IntersectionObserverEntry', 'IntersectionObserverEntry',
'KeyboardEvent', 'KeyboardEvent',
@@ -189,6 +228,7 @@ features = [
'Node', 'Node',
'PageTransitionEvent', 'PageTransitionEvent',
'PointerEvent', 'PointerEvent',
'PremultiplyAlpha',
'ResizeObserver', 'ResizeObserver',
'ResizeObserverBoxOptions', 'ResizeObserverBoxOptions',
'ResizeObserverEntry', 'ResizeObserverEntry',
@@ -196,23 +236,42 @@ features = [
'ResizeObserverSize', 'ResizeObserverSize',
'VisibilityState', 'VisibilityState',
'Window', 'Window',
'WheelEvent' 'WheelEvent',
'Url',
] ]
[target.'cfg(target_family = "wasm")'.dependencies] [target.'cfg(target_family = "wasm")'.dependencies]
atomic-waker = "1"
js-sys = "0.3.64" js-sys = "0.3.64"
pin-project = "1"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-time = "0.2" web-time = "1"
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
atomic-waker = "1"
concurrent-queue = { version = "2", default-features = false }
[target.'cfg(target_family = "wasm")'.dev-dependencies] [target.'cfg(target_family = "wasm")'.dev-dependencies]
console_log = "1" console_log = "1"
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] } web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
[[example]]
doc-scrape-examples = true
name = "full"
[workspace] [workspace]
resolver = "2"
members = [ members = [
"xbuild/android-target", "dpi",
"xbuild/ios-target",
"run-wasm", "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

@@ -1,10 +1,10 @@
# Winit Scope # Winit Scope
Winit aims to expose an interface that abstracts over window creation and input handling, and can Winit aims to expose an interface that abstracts over window creation and input handling and can
be used to create both games and applications. It supports the following main graphical platforms: be used to create both games and applications. It supports the following main graphical platforms:
- Desktop - Desktop
- Windows 7+ (10+ is tested regularly) - Windows
- macOS 10.7+ (10.14+ is tested regularly) - macOS
- Unix - Unix
- via X11 - via X11
- via Wayland - via Wayland
@@ -13,9 +13,6 @@ be used to create both games and applications. It supports the following main gr
- iOS - iOS
- Android - Android
- Web - Web
- Chrome
- Firefox
- Safari 13.1+
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
aim to support every single feature of every platform, but rather to abstract over the common features aim to support every single feature of every platform, but rather to abstract over the common features
@@ -45,10 +42,10 @@ be released and the library will enter maintenance mode. For the most part, new
be added past this point. New platform features may be accepted and exposed through point releases. be added past this point. New platform features may be accepted and exposed through point releases.
### Tier upgrades ### Tier upgrades
Some platform features could in theory be exposed across multiple platforms, but have not gone Some platform features could, in theory, be exposed across multiple platforms, but have not gone
through the implementation work necessary to function on all platforms. When one of these features through the implementation work necessary to function on all platforms. When one of these features
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature. gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions gets deprecated and become permanently If that gets accepted, the platform-specific functions get deprecated and become permanently
exposed through the core, cross-platform API. exposed through the core, cross-platform API.
# Features # Features
@@ -88,7 +85,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after - **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation. creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor - **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows, and if applicable, captures the monitor for exclusive for fullscreen windows and, if applicable, captures the monitor for exclusive
use by this application. use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content. - **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent - **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
@@ -105,7 +102,8 @@ If your PR makes notable changes to Winit's features, please update this section
- **Mouse set location**: Forcibly changing the location of the pointer. - **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor locking**: Locking the cursor inside the window so it cannot move. - **Cursor locking**: Locking the cursor inside the window so it cannot move.
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them. - **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor. - **Cursor icon**: Changing the cursor icon or hiding the cursor.
- **Cursor image**: Changing the cursor to your own image.
- **Cursor hittest**: Handle or ignore mouse events for a window. - **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events. - **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied. - **Touch pressure**: Touch events contain information about the amount of force being applied.
@@ -125,6 +123,11 @@ If your PR makes notable changes to Winit's features, please update this section
* Setting a menu bar * Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support * `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme * Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
* Changing a system-drawn backdrop
* Setting the window border color
* Setting the title bar background color
* Setting the title color
* Setting the corner rounding preference
### macOS ### macOS
* Window activation policy * Window activation policy
@@ -145,28 +148,24 @@ If your PR makes notable changes to Winit's features, please update this section
* Setting the X11 parent window * Setting the X11 parent window
### iOS ### iOS
* `winit` has a minimum OS requirement of iOS 8
* Get the `UIScreen` object pointer * Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor * Setting the `UIView` hidpi factor
* Valid orientations * Valid orientations
* Home indicator visibility * Home indicator visibility
* Status bar visibility * Status bar visibility and style
* Deferrring system gestures * Deferring system gestures
* Getting the device idiom * Getting the device idiom
* Getting the preferred video mode * Getting the preferred video mode
### Web ### Web
* Get if systems preferred color scheme is "dark" * Get if the systems preferred color scheme is "dark"
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
## Compatibility Matrix ## Compatibility Matrix
Legend: Legend:
- ✔️: Works as intended - ✔️: Works as intended
- ▢: Mostly works but some bugs are known - ▢: Mostly works, but some bugs are known
- ❌: Missing feature or large bugs making it unusable - ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform - **N/A**: Not applicable for this platform
- ❓: Unknown status - ❓: Unknown status
@@ -206,6 +205,7 @@ Legend:
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ | |Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ | |Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** | |Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ | |Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** | |Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** | |Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |

View File

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

195
README.md
View File

@@ -2,11 +2,13 @@
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit) [![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit) [![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs
)](https://rust-windowing.github.io/winit/winit/index.html)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions) [![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
```toml ```toml
[dependencies] [dependencies]
winit = "0.29.1-beta" winit = "0.29.11"
``` ```
## [Documentation](https://docs.rs/winit) ## [Documentation](https://docs.rs/winit)
@@ -17,58 +19,23 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
## Contact Us ## Contact Us
Join us in any of these: 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).
[![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
[![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit)
## Usage ## Usage
Winit is a window creation and management library. It can create windows and lets you handle Winit is a window creation and management library. It can create windows and lets you handle
events (for example: the window being resized, a key being pressed, a mouse movement, etc.) events (for example: the window being resized, a key being pressed, a mouse movement, etc.)
produced by window. produced by the window.
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to
show something on the window you need to use the platform-specific getters provided by winit, or show something on the window you need to use the platform-specific getters provided by winit, or
another library. another library.
```rust
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
fn main() {
let event_loop = EventLoop::new();
event_loop.set_control_flow(ControlFlow::Wait);
let window = WindowBuilder::new().build(&event_loop).unwrap();
event_loop.run(move |event, elwt| {
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => elwt.exit(),
_ => (),
}
});
}
```
Winit is only officially supported on the latest stable version of the Rust compiler.
### Cargo Features
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
* `x11` (enabled by default): On Unix platform, compiles with the X11 backend
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
* `mint`: Enables mint (math interoperability standard types) conversions.
## MSRV Policy ## MSRV Policy
The Minimum Supported Rust Version (MSRV) of this crate is **1.65**. Changes to This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
the MSRV will be accompanied by a minor version bump. the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following As a **tentative** policy, the upper bound of the MSRV is given by the following
@@ -79,12 +46,11 @@ min(sid, stable - 3)
``` ```
Where `sid` is the current version of `rustc` provided by [Debian Sid], and Where `sid` is the current version of `rustc` provided by [Debian Sid], and
`stable` is the latest stable version of Rust. This bound may be broken in the `stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability.
event of a major ecosystem shift or a security vulnerability.
[Debian Sid]: https://packages.debian.org/sid/rustc [Debian Sid]: https://packages.debian.org/sid/rustc
The exception to this is for the Android platform, where a higher Rust version The exception is for the Android platform, where a higher Rust version
must be used for certain Android features. In this case, the MSRV will be must be used for certain Android features. In this case, the MSRV will be
capped at the latest stable version of Rust minus three. This inconsistency is capped at the latest stable version of Rust minus three. This inconsistency is
not reflected in Cargo metadata, as it is not powerful enough to expose this not reflected in Cargo metadata, as it is not powerful enough to expose this
@@ -97,145 +63,4 @@ same MSRV policy.
### Platform-specific usage ### Platform-specific usage
#### Wayland Check out the [`winit::platform`](https://rust-windowing.github.io/winit/winit/platform/index.html) module for platform-specific usage.
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 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 entrypoint. If Cargo resolves multiple versions they will
clash.
`winit` glue compatibility table:
| winit | ndk-glue |
| :---: | :--------------------------: |
| 0.29.1-beta | `android-activity = "0.5.0-beta.1"` |
| 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`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
[`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 example, add this to Cargo.toml:
```toml
winit = { version = "0.29.1-beta", features = [ "android-native-activity" ] }
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.11.0"
```
And, for example, define an entry point for your library like this:
```rust
#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;
#[cfg(target_os = "android")]
#[no_mangle]
fn android_main(app: AndroidApp) {
use winit::platform::android::EventLoopBuilderExtAndroid;
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Trace));
let event_loop = EventLoopBuilder::with_user_event()
.with_android_app(app)
.build();
_main(event_loop);
}
```
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity`
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.1-beta", 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::NewEvents(StartCause::Init)`.
#### iOS
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
by all UI related code, see issue [#1705]. You should consider creating your windows
inside `Event::NewEvents(StartCause::Init)`.
[#2238]: https://github.com/rust-windowing/winit/issues/2238
[#2051]: https://github.com/rust-windowing/winit/issues/2051
[#2087]: https://github.com/rust-windowing/winit/issues/2087
[#1705]: https://github.com/rust-windowing/winit/issues/1705
#### Redox OS
Redox OS has some functionality not present yet, that will be implemented when
its orbital display server provides it.

View File

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

View File

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

View File

@@ -34,13 +34,6 @@ skip = [
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this { name = "raw-window-handle" }, # we intentionally have multiple versions of this
{ name = "bitflags" }, # the ecosystem is in the process of migrating. { name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "libloading" }, # x11rb uses a different version until the next update { name = "libloading" }, # x11rb uses a different version until the next update
{ name = "syn" }, # https://github.com/rust-mobile/ndk/issues/392
{ name = "num_enum"}, # See above ^, waiting for release
{ name = "num_enum_derive"}, # See above ^, waiting for release
{ name = "miniz_oxide"}, # https://github.com/rust-lang/flate2-rs/issues/340
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
{ name = "foreign-types-shared" }, # https://github.com/servo/core-foundation-rs/issues/634
{ name = "foreign-types" }, # See above ^, waiting for release
] ]
skip-tree = [] skip-tree = []

View File

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

39
dpi/Cargo.toml Normal file
View File

@@ -0,0 +1,39 @@
[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"]

1
dpi/LICENSE Symbolic link
View File

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

View File

@@ -1,16 +1,16 @@
//! UI scaling is important, so read the docs for this module if you don't want to be confused. //! # DPI
//! //!
//! ## Why should I care about UI scaling? //! ## Why should I care about UI scaling?
//! //!
//! Modern computer screens don't have a consistent relationship between resolution and size. //! Modern computer screens don't have a consistent relationship between resolution and size.
//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens //! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
//! normally being less than a quarter the size of their desktop counterparts. What's more, neither //! typically being less than a quarter the size of their desktop counterparts. Moreover, neither
//! desktop nor mobile screens are consistent resolutions within their own size classes - common //! desktop nor mobile screens have consistent resolutions within their own size classes - common
//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K //! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
//! and beyond. //! and beyond.
//! //!
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with //! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen, //! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen and
//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up //! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially //! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
//! problematic with text rendering, where quarter-sized text becomes a significant legibility //! problematic with text rendering, where quarter-sized text becomes a significant legibility
@@ -25,83 +25,50 @@
//! //!
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is //! The solution to this problem is to account for the device's *scale factor*. The scale factor is
//! the factor UI elements should be scaled by to be consistent with the rest of the user's system - //! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device //! for example, a button that's usually 50 pixels across would be 100 pixels across on a device
//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`. //! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
//! //!
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's //! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's //! usually a mistake since there's no consistent mapping between the scale factor and the screen's
//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather //! actual DPI. Unless printing to a physical medium, you should work in scaled pixels rather
//! than any DPI-dependent units. //! than any DPI-dependent units.
//! //!
//! ### Position and Size types //! ### Position and Size types
//! //!
//! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the //! The [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the
//! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels //! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels
//! divided by the scale factor. //! divided by the scale factor.
//! All of Winit's functions return physical types, but can take either logical or physical
//! coordinates as input, allowing you to use the most convenient coordinate system for your
//! particular application.
//! //!
//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the //! The position and size types are generic over their exact pixel type, `P`, to allow the
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and //! API to have integer precision where appropriate (e.g. most window manipulation functions) and
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch //! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so //! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
//! will truncate the fractional part of the float, rather than properly round to the nearest //! will truncate the fractional part of the float rather than properly round to the nearest
//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the //! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the
//! rounding properly. Note that precision loss will still occur when rounding from a float to an //! rounding properly. Note that precision loss will still occur when rounding from a float to an
//! int, although rounding lessens the problem. //! int, although rounding lessens the problem.
//! //!
//! ### Events //! ## Cargo Features
//! //!
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed. //! This crate provides the following Cargo features:
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your
//! application's UI elements and adjust how the platform changes the window's size to reflect the new
//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor
//! can be found by calling [`window.scale_factor()`].
//! //!
//! ## How is the scale factor calculated? //! * `serde`: Enables serialization/deserialization of certain types with
//! //! [Serde](https://crates.io/crates/serde).
//! Scale factor is calculated differently on different platforms: //! * `mint`: Enables mint (math interoperability standard types) conversions.
//!
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
//! display settings. While users are free to select any option they want, they're only given a
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
//! global and changing it requires logging out. See [this article][windows_1] for technical
//! details.
//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
//! the specific value varies across devices.
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
//! currently uses a three-pronged approach:
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
//! + If not present, use the value set in `Xft.dpi` in Xresources.
//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
//!
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
//! - **Wayland:** Scale factor is suggested by the the compositor.
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
//! information.
//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the
//! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information.
//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels.
//! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by
//! both the screen scaling and the browser zoom level and can go below `1.0`.
//! //!
//! //!
//! [points]: https://en.wikipedia.org/wiki/Point_(typography) //! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged
//! [`window.scale_factor()`]: crate::window::Window::scale_factor #![cfg_attr(
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows docsrs,
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html feature(doc_auto_cfg, doc_cfg_hide),
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ doc(cfg_hide(doc, docsrs))
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities )]
//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio #![forbid(unsafe_code)]
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub trait Pixel: Copy + Into<f64> { pub trait Pixel: Copy + Into<f64> {
fn from_f64(f: f64) -> Self; fn from_f64(f: f64) -> Self;
@@ -164,7 +131,7 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
/// A position represented in logical pixels. /// A position represented in logical pixels.
/// ///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the /// The position is stored as floats, so please be careful. Casting floats to integers truncates the
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>` /// fractional part, which can cause noticeable issues. To help with that, an `Into<(i32, i32)>`
/// implementation is provided which does the rounding for you. /// implementation is provided which does the rounding for you.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -583,15 +550,13 @@ impl<P: Pixel> From<LogicalPosition<P>> for Position {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::dpi; use super::*;
use std::collections::HashSet; use std::collections::HashSet;
macro_rules! test_pixel_int_impl { macro_rules! test_pixel_int_impl {
($($name:ident => $ty:ty),*) => {$( ($($name:ident => $ty:ty),*) => {$(
#[test] #[test]
fn $name() { fn $name() {
use dpi::Pixel;
assert_eq!( assert_eq!(
<$ty as Pixel>::from_f64(37.0), <$ty as Pixel>::from_f64(37.0),
37, 37,
@@ -660,8 +625,6 @@ mod tests {
($($name:ident => $ty:ty),*) => {$( ($($name:ident => $ty:ty),*) => {$(
#[test] #[test]
fn $name() { fn $name() {
use dpi::Pixel;
assert_approx_eq!( assert_approx_eq!(
<$ty as Pixel>::from_f64(37.0), <$ty as Pixel>::from_f64(37.0),
37.0, 37.0,
@@ -754,46 +717,40 @@ mod tests {
#[test] #[test]
fn test_validate_scale_factor() { fn test_validate_scale_factor() {
assert!(dpi::validate_scale_factor(1.0)); assert!(validate_scale_factor(1.0));
assert!(dpi::validate_scale_factor(2.0)); assert!(validate_scale_factor(2.0));
assert!(dpi::validate_scale_factor(3.0)); assert!(validate_scale_factor(3.0));
assert!(dpi::validate_scale_factor(1.5)); assert!(validate_scale_factor(1.5));
assert!(dpi::validate_scale_factor(0.5)); assert!(validate_scale_factor(0.5));
assert!(!dpi::validate_scale_factor(0.0)); assert!(!validate_scale_factor(0.0));
assert!(!dpi::validate_scale_factor(-1.0)); assert!(!validate_scale_factor(-1.0));
assert!(!dpi::validate_scale_factor(f64::INFINITY)); assert!(!validate_scale_factor(f64::INFINITY));
assert!(!dpi::validate_scale_factor(f64::NAN)); assert!(!validate_scale_factor(f64::NAN));
assert!(!dpi::validate_scale_factor(f64::NEG_INFINITY)); assert!(!validate_scale_factor(f64::NEG_INFINITY));
} }
#[test] #[test]
fn test_logical_position() { fn test_logical_position() {
let log_pos = dpi::LogicalPosition::new(1.0, 2.0); let log_pos = LogicalPosition::new(1.0, 2.0);
assert_eq!( assert_eq!(log_pos.to_physical::<u32>(1.0), PhysicalPosition::new(1, 2));
log_pos.to_physical::<u32>(1.0), assert_eq!(log_pos.to_physical::<u32>(2.0), PhysicalPosition::new(2, 4));
dpi::PhysicalPosition::new(1, 2) assert_eq!(log_pos.cast::<u32>(), LogicalPosition::new(1, 2));
);
assert_eq!(
log_pos.to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(2, 4)
);
assert_eq!(log_pos.cast::<u32>(), dpi::LogicalPosition::new(1, 2));
assert_eq!( assert_eq!(
log_pos, log_pos,
dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(1.0, 2.0), 1.0) LogicalPosition::from_physical(PhysicalPosition::new(1.0, 2.0), 1.0)
); );
assert_eq!( assert_eq!(
log_pos, log_pos,
dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(2.0, 4.0), 2.0) LogicalPosition::from_physical(PhysicalPosition::new(2.0, 4.0), 2.0)
); );
assert_eq!( assert_eq!(
dpi::LogicalPosition::from((2.0, 2.0)), LogicalPosition::from((2.0, 2.0)),
dpi::LogicalPosition::new(2.0, 2.0) LogicalPosition::new(2.0, 2.0)
); );
assert_eq!( assert_eq!(
dpi::LogicalPosition::from([2.0, 3.0]), LogicalPosition::from([2.0, 3.0]),
dpi::LogicalPosition::new(2.0, 3.0) LogicalPosition::new(2.0, 3.0)
); );
let x: (f64, f64) = log_pos.into(); let x: (f64, f64) = log_pos.into();
@@ -805,56 +762,44 @@ mod tests {
#[test] #[test]
fn test_physical_position() { fn test_physical_position() {
assert_eq!( assert_eq!(
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(1.0, 2.0), 1.0), PhysicalPosition::from_logical(LogicalPosition::new(1.0, 2.0), 1.0),
dpi::PhysicalPosition::new(1, 2) PhysicalPosition::new(1, 2)
); );
assert_eq!( assert_eq!(
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(2.0, 4.0), 0.5), PhysicalPosition::from_logical(LogicalPosition::new(2.0, 4.0), 0.5),
dpi::PhysicalPosition::new(1, 2) PhysicalPosition::new(1, 2)
); );
assert_eq!( assert_eq!(
dpi::PhysicalPosition::from((2.0, 2.0)), PhysicalPosition::from((2.0, 2.0)),
dpi::PhysicalPosition::new(2.0, 2.0) PhysicalPosition::new(2.0, 2.0)
); );
assert_eq!( assert_eq!(
dpi::PhysicalPosition::from([2.0, 3.0]), PhysicalPosition::from([2.0, 3.0]),
dpi::PhysicalPosition::new(2.0, 3.0) PhysicalPosition::new(2.0, 3.0)
); );
let x: (f64, f64) = dpi::PhysicalPosition::new(1, 2).into(); let x: (f64, f64) = PhysicalPosition::new(1, 2).into();
assert_eq!(x, (1.0, 2.0)); assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = dpi::PhysicalPosition::new(1, 2).into(); let x: [f64; 2] = PhysicalPosition::new(1, 2).into();
assert_eq!(x, [1.0, 2.0]); assert_eq!(x, [1.0, 2.0]);
} }
#[test] #[test]
fn test_logical_size() { fn test_logical_size() {
let log_size = dpi::LogicalSize::new(1.0, 2.0); let log_size = LogicalSize::new(1.0, 2.0);
assert_eq!( assert_eq!(log_size.to_physical::<u32>(1.0), PhysicalSize::new(1, 2));
log_size.to_physical::<u32>(1.0), assert_eq!(log_size.to_physical::<u32>(2.0), PhysicalSize::new(2, 4));
dpi::PhysicalSize::new(1, 2) assert_eq!(log_size.cast::<u32>(), LogicalSize::new(1, 2));
);
assert_eq!(
log_size.to_physical::<u32>(2.0),
dpi::PhysicalSize::new(2, 4)
);
assert_eq!(log_size.cast::<u32>(), dpi::LogicalSize::new(1, 2));
assert_eq!( assert_eq!(
log_size, log_size,
dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(1.0, 2.0), 1.0) LogicalSize::from_physical(PhysicalSize::new(1.0, 2.0), 1.0)
); );
assert_eq!( assert_eq!(
log_size, log_size,
dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(2.0, 4.0), 2.0) LogicalSize::from_physical(PhysicalSize::new(2.0, 4.0), 2.0)
);
assert_eq!(
dpi::LogicalSize::from((2.0, 2.0)),
dpi::LogicalSize::new(2.0, 2.0)
);
assert_eq!(
dpi::LogicalSize::from([2.0, 3.0]),
dpi::LogicalSize::new(2.0, 3.0)
); );
assert_eq!(LogicalSize::from((2.0, 2.0)), LogicalSize::new(2.0, 2.0));
assert_eq!(LogicalSize::from([2.0, 3.0]), LogicalSize::new(2.0, 3.0));
let x: (f64, f64) = log_size.into(); let x: (f64, f64) = log_size.into();
assert_eq!(x, (1.0, 2.0)); assert_eq!(x, (1.0, 2.0));
@@ -865,136 +810,130 @@ mod tests {
#[test] #[test]
fn test_physical_size() { fn test_physical_size() {
assert_eq!( assert_eq!(
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(1.0, 2.0), 1.0), PhysicalSize::from_logical(LogicalSize::new(1.0, 2.0), 1.0),
dpi::PhysicalSize::new(1, 2) PhysicalSize::new(1, 2)
); );
assert_eq!( assert_eq!(
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(2.0, 4.0), 0.5), PhysicalSize::from_logical(LogicalSize::new(2.0, 4.0), 0.5),
dpi::PhysicalSize::new(1, 2) 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) = dpi::PhysicalSize::new(1, 2).into(); let x: (f64, f64) = PhysicalSize::new(1, 2).into();
assert_eq!(x, (1.0, 2.0)); assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = dpi::PhysicalSize::new(1, 2).into(); let x: [f64; 2] = PhysicalSize::new(1, 2).into();
assert_eq!(x, [1.0, 2.0]); assert_eq!(x, [1.0, 2.0]);
} }
#[test] #[test]
fn test_size() { fn test_size() {
assert_eq!( assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)), Size::new(PhysicalSize::new(1, 2)),
dpi::Size::Physical(dpi::PhysicalSize::new(1, 2)) Size::Physical(PhysicalSize::new(1, 2))
); );
assert_eq!( assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)), Size::new(LogicalSize::new(1.0, 2.0)),
dpi::Size::Logical(dpi::LogicalSize::new(1.0, 2.0)) Size::Logical(LogicalSize::new(1.0, 2.0))
); );
assert_eq!( assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(1.0), Size::new(PhysicalSize::new(1, 2)).to_logical::<f64>(1.0),
dpi::LogicalSize::new(1.0, 2.0) LogicalSize::new(1.0, 2.0)
); );
assert_eq!( assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(2.0), Size::new(PhysicalSize::new(1, 2)).to_logical::<f64>(2.0),
dpi::LogicalSize::new(0.5, 1.0) LogicalSize::new(0.5, 1.0)
); );
assert_eq!( assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0), Size::new(LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0),
dpi::LogicalSize::new(1.0, 2.0) LogicalSize::new(1.0, 2.0)
); );
assert_eq!( assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(1.0), Size::new(PhysicalSize::new(1, 2)).to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2) PhysicalSize::new(1, 2)
); );
assert_eq!( assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(2.0), Size::new(PhysicalSize::new(1, 2)).to_physical::<u32>(2.0),
dpi::PhysicalSize::new(1, 2) PhysicalSize::new(1, 2)
); );
assert_eq!( assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0), Size::new(LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2) PhysicalSize::new(1, 2)
); );
assert_eq!( assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0), Size::new(LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0),
dpi::PhysicalSize::new(2, 4) PhysicalSize::new(2, 4)
); );
let small = dpi::Size::Physical((1, 2).into()); let small = Size::Physical((1, 2).into());
let medium = dpi::Size::Logical((3, 4).into()); let medium = Size::Logical((3, 4).into());
let medium_physical = dpi::Size::new(medium.to_physical::<u32>(1.0)); let medium_physical = Size::new(medium.to_physical::<u32>(1.0));
let large = dpi::Size::Physical((5, 6).into()); let large = Size::Physical((5, 6).into());
assert_eq!(dpi::Size::clamp(medium, small, large, 1.0), medium_physical); assert_eq!(Size::clamp(medium, small, large, 1.0), medium_physical);
assert_eq!(dpi::Size::clamp(small, medium, large, 1.0), medium_physical); assert_eq!(Size::clamp(small, medium, large, 1.0), medium_physical);
assert_eq!(dpi::Size::clamp(large, small, medium, 1.0), medium_physical); assert_eq!(Size::clamp(large, small, medium, 1.0), medium_physical);
} }
#[test] #[test]
fn test_position() { fn test_position() {
assert_eq!( assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)), Position::new(PhysicalPosition::new(1, 2)),
dpi::Position::Physical(dpi::PhysicalPosition::new(1, 2)) Position::Physical(PhysicalPosition::new(1, 2))
); );
assert_eq!( assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)), Position::new(LogicalPosition::new(1.0, 2.0)),
dpi::Position::Logical(dpi::LogicalPosition::new(1.0, 2.0)) Position::Logical(LogicalPosition::new(1.0, 2.0))
); );
assert_eq!( assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0), Position::new(PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0),
dpi::LogicalPosition::new(1.0, 2.0) LogicalPosition::new(1.0, 2.0)
); );
assert_eq!( assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0), Position::new(PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0),
dpi::LogicalPosition::new(0.5, 1.0) LogicalPosition::new(0.5, 1.0)
); );
assert_eq!( assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0), Position::new(LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0),
dpi::LogicalPosition::new(1.0, 2.0) LogicalPosition::new(1.0, 2.0)
); );
assert_eq!( assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0), Position::new(PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2) PhysicalPosition::new(1, 2)
); );
assert_eq!( assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0), Position::new(PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(1, 2) PhysicalPosition::new(1, 2)
); );
assert_eq!( assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0), Position::new(LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2) PhysicalPosition::new(1, 2)
); );
assert_eq!( assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0), Position::new(LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(2, 4) PhysicalPosition::new(2, 4)
); );
} }
// Eat coverage for the Debug impls et al // Eat coverage for the Debug impls et al
#[test] #[test]
fn ensure_attrs_do_not_panic() { fn ensure_attrs_do_not_panic() {
let _ = format!("{:?}", dpi::LogicalPosition::<u32>::default().clone()); let _ = format!("{:?}", LogicalPosition::<u32>::default().clone());
HashSet::new().insert(dpi::LogicalPosition::<u32>::default()); HashSet::new().insert(LogicalPosition::<u32>::default());
let _ = format!("{:?}", dpi::PhysicalPosition::<u32>::default().clone()); let _ = format!("{:?}", PhysicalPosition::<u32>::default().clone());
HashSet::new().insert(dpi::PhysicalPosition::<u32>::default()); HashSet::new().insert(PhysicalPosition::<u32>::default());
let _ = format!("{:?}", dpi::LogicalSize::<u32>::default().clone()); let _ = format!("{:?}", LogicalSize::<u32>::default().clone());
HashSet::new().insert(dpi::LogicalSize::<u32>::default()); HashSet::new().insert(LogicalSize::<u32>::default());
let _ = format!("{:?}", dpi::PhysicalSize::<u32>::default().clone()); let _ = format!("{:?}", PhysicalSize::<u32>::default().clone());
HashSet::new().insert(dpi::PhysicalSize::<u32>::default()); HashSet::new().insert(PhysicalSize::<u32>::default());
let _ = format!("{:?}", dpi::Size::Physical((1, 2).into()).clone()); let _ = format!("{:?}", Size::Physical((1, 2).into()).clone());
let _ = format!("{:?}", dpi::Position::Physical((1, 2).into()).clone()); let _ = format!("{:?}", Position::Physical((1, 2).into()).clone());
} }
} }

View File

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

View File

@@ -1,17 +1,17 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
use std::thread; use std::thread;
#[cfg(not(wasm_platform))] #[cfg(not(web_platform))]
use std::time; use std::time;
#[cfg(wasm_platform)] #[cfg(web_platform)]
use web_time as time; use web_time as time;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
keyboard::Key, keyboard::{Key, NamedKey},
window::WindowBuilder, window::Window,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -37,17 +37,14 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Press 'Esc' to close the window."); println!("Press 'Esc' to close the window.");
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = 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 mode = Mode::Wait;
let mut request_redraw = false; let mut request_redraw = false;
let mut wait_cancelled = false; let mut wait_cancelled = false;
let mut close_requested = false; let mut close_requested = false;
event_loop.run(move |event, elwt| { let mut window = None;
event_loop.run(move |event, event_loop| {
use winit::event::StartCause; use winit::event::StartCause;
println!("{event:?}"); println!("{event:?}");
match event { match event {
@@ -57,6 +54,12 @@ fn main() -> Result<(), impl std::error::Error> {
_ => false, _ => false,
} }
} }
Event::Resumed => {
let window_attributes = Window::default_attributes().with_title(
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
);
window = Some(event_loop.create_window(window_attributes).unwrap());
}
Event::WindowEvent { event, .. } => match event { Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
close_requested = true; close_requested = true;
@@ -70,7 +73,7 @@ fn main() -> Result<(), impl std::error::Error> {
}, },
.. ..
} => match key.as_ref() { } => match key.as_ref() {
// WARNING: Consider using `key_without_modifers()` if available on your platform. // WARNING: Consider using `key_without_modifiers()` if available on your platform.
// See the `key_binding` example // See the `key_binding` example
Key::Character("1") => { Key::Character("1") => {
mode = Mode::Wait; mode = Mode::Wait;
@@ -88,38 +91,40 @@ fn main() -> Result<(), impl std::error::Error> {
request_redraw = !request_redraw; request_redraw = !request_redraw;
println!("\nrequest_redraw: {request_redraw}\n"); println!("\nrequest_redraw: {request_redraw}\n");
} }
Key::Escape => { Key::Named(NamedKey::Escape) => {
close_requested = true; close_requested = true;
} }
_ => (), _ => (),
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
fill::fill_window(&window); let window = window.as_ref().unwrap();
window.pre_present_notify();
fill::fill_window(window);
} }
_ => (), _ => (),
}, },
Event::AboutToWait => { Event::AboutToWait => {
if request_redraw && !wait_cancelled && !close_requested { if request_redraw && !wait_cancelled && !close_requested {
window.request_redraw(); window.as_ref().unwrap().request_redraw();
} }
match mode { match mode {
Mode::Wait => elwt.set_control_flow(ControlFlow::Wait), Mode::Wait => event_loop.set_control_flow(ControlFlow::Wait),
Mode::WaitUntil => { Mode::WaitUntil => {
if !wait_cancelled { if !wait_cancelled {
elwt.set_control_flow(ControlFlow::WaitUntil( event_loop.set_control_flow(ControlFlow::WaitUntil(
time::Instant::now() + WAIT_TIME, time::Instant::now() + WAIT_TIME,
)); ));
} }
} }
Mode::Poll => { Mode::Poll => {
thread::sleep(POLL_SLEEP_TIME); thread::sleep(POLL_SLEEP_TIME);
elwt.set_control_flow(ControlFlow::Poll); event_loop.set_control_flow(ControlFlow::Poll);
} }
}; };
if close_requested { if close_requested {
elwt.exit(); event_loop.exit();
} }
} }
_ => (), _ => (),

View File

@@ -1,88 +0,0 @@
#![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,
];

View File

@@ -1,73 +0,0 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState},
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::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

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

BIN
examples/data/cross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

BIN
examples/data/cross2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

BIN
examples/data/gradient.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

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

847
examples/full.rs Normal file
View File

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

View File

@@ -1,163 +0,0 @@
#![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;
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::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

@@ -1,86 +0,0 @@
#![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

@@ -1,94 +0,0 @@
#![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::{Key, KeyCode},
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.physical_key == KeyCode::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 == Key::F3 {
ime_purpose = match ime_purpose {
ImePurpose::Normal => ImePurpose::Password,
ImePurpose::Password => ImePurpose::Terminal,
_ => ImePurpose::Normal,
};
window.set_ime_purpose(ime_purpose);
println!("\nIME purpose: {ime_purpose:?}\n");
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
}
})
}

View File

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

View File

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

62
examples/monitors.rs Normal file
View File

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

View File

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

View File

@@ -1,209 +0,0 @@
#![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},
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 Key::{ArrowLeft, ArrowRight};
window.set_title(&format!("{key:?}"));
let state = !modifiers.shift_key();
match key {
// Cycle through video modes
Key::ArrowRight | Key::ArrowLeft => {
video_mode_id = match key {
ArrowLeft => video_mode_id.saturating_sub(1),
ArrowRight => (video_modes.len() - 1).min(video_mode_id + 1),
_ => unreachable!(),
};
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::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");
}

View File

@@ -1,65 +0,0 @@
#![allow(clippy::single_match)]
use std::collections::HashMap;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let 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:
KeyEvent {
state: ElementState::Pressed,
logical_key: Key::Character(c),
..
},
is_synthetic: false,
..
} if matches!(c.as_ref(), "n" | "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

@@ -16,7 +16,7 @@ fn main() -> std::process::ExitCode {
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
window::WindowBuilder, window::Window,
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -25,38 +25,40 @@ fn main() -> std::process::ExitCode {
let mut event_loop = EventLoop::new().unwrap(); let mut event_loop = EventLoop::new().unwrap();
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
'main: loop { let mut window = None;
loop {
let timeout = Some(Duration::ZERO); let timeout = Some(Duration::ZERO);
let status = event_loop.pump_events(timeout, |event, elwt| { let status = event_loop.pump_events(timeout, |event, event_loop| {
if let Event::WindowEvent { event, .. } = &event { if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise // Print only Window events to reduce noise
println!("{event:?}"); println!("{event:?}");
} }
match event { match event {
Event::WindowEvent { Event::Resumed => {
event: WindowEvent::CloseRequested, let window_attributes =
window_id, Window::default_attributes().with_title("A fantastic window!");
} if window_id == window.id() => elwt.exit(), window = Some(event_loop.create_window(window_attributes).unwrap());
Event::AboutToWait => {
window.request_redraw();
} }
Event::WindowEvent { Event::WindowEvent { event, .. } => {
event: WindowEvent::RedrawRequested, let window = window.as_ref().unwrap();
.. match event {
} => { WindowEvent::CloseRequested => event_loop.exit(),
fill::fill_window(&window); WindowEvent::RedrawRequested => fill::fill_window(window),
_ => (),
}
}
Event::AboutToWait => {
window.as_ref().unwrap().request_redraw();
} }
_ => (), _ => (),
} }
}); });
if let PumpStatus::Exit(exit_code) = status { if let PumpStatus::Exit(exit_code) = status {
break 'main ExitCode::from(exit_code as u8); break ExitCode::from(exit_code as u8);
} }
// Sleep for 1/60 second to simulate application work // Sleep for 1/60 second to simulate application work
@@ -68,7 +70,7 @@ fn main() -> std::process::ExitCode {
} }
} }
#[cfg(any(ios_platform, wasm_platform, orbital_platform))] #[cfg(any(ios_platform, web_platform, orbital_platform))]
fn main() { fn main() {
println!("This platform doesn't support pump_events."); println!("This platform doesn't support pump_events.");
} }

View File

@@ -1,42 +0,0 @@
#![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

@@ -1,59 +0,0 @@
#![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
}

View File

@@ -1,54 +0,0 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::KeyCode,
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: KeyCode::Space,
state: ElementState::Released,
..
},
..
} => {
resizable = !resizable;
println!("Resizable: {resizable}");
window.set_resizable(resizable);
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
};
})
}

View File

@@ -12,7 +12,7 @@ fn main() -> Result<(), impl std::error::Error> {
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
platform::run_on_demand::EventLoopExtRunOnDemand, platform::run_on_demand::EventLoopExtRunOnDemand,
window::{Window, WindowBuilder, WindowId}, window::{Window, WindowId},
}; };
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -30,7 +30,7 @@ fn main() -> Result<(), impl std::error::Error> {
fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> { fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> {
let mut app = App::default(); let mut app = App::default();
event_loop.run_on_demand(move |event, elwt| { event_loop.run_on_demand(move |event, event_loop| {
println!("Run {idx}: {:?}", event); println!("Run {idx}: {:?}", event);
if let Some(window) = &app.window { if let Some(window) = &app.window {
@@ -40,6 +40,7 @@ fn main() -> Result<(), impl std::error::Error> {
window_id, window_id,
} if window.id() == window_id => { } if window.id() == window_id => {
println!("--------------------------------------------------------- Window {idx} CloseRequested"); println!("--------------------------------------------------------- Window {idx} CloseRequested");
fill::cleanup_window(window);
app.window = None; app.window = None;
} }
Event::AboutToWait => window.request_redraw(), Event::AboutToWait => window.request_redraw(),
@@ -59,16 +60,15 @@ fn main() -> Result<(), impl std::error::Error> {
} if id == window_id => { } if id == window_id => {
println!("--------------------------------------------------------- Window {idx} Destroyed"); println!("--------------------------------------------------------- Window {idx} Destroyed");
app.window_id = None; app.window_id = None;
elwt.exit(); event_loop.exit();
} }
_ => (), _ => (),
} }
} else if let Event::Resumed = event { } else if let Event::Resumed = event {
let window = WindowBuilder::new() let window_attributes = Window::default_attributes()
.with_title("Fantastic window number one!") .with_title("Fantastic window number one!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0));
.build(elwt) let window = event_loop.create_window(window_attributes).unwrap();
.unwrap();
app.window_id = Some(window.id()); app.window_id = Some(window.id());
app.window = Some(window); app.window = Some(window);
} }

View File

@@ -1,117 +0,0 @@
//! 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::keyboard::Key;
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 == Key::Character("n".into()) {
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.");
}

View File

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

View File

@@ -1,54 +0,0 @@
#![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

@@ -1,50 +0,0 @@
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);
}
_ => (),
}
}
})
}

View File

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

View File

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

View File

@@ -1,147 +0,0 @@
#![allow(clippy::disallowed_methods, clippy::single_match)]
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::KeyCode,
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 {
physical_key: KeyCode::KeyF,
state: ElementState::Released,
..
},
..
},
} if window_id == window.id() => {
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

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

View File

@@ -1,43 +0,0 @@
#![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("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop)
.unwrap();
event_loop.run(move |event, elwt| {
println!("{event:?}");
match event {
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::RedrawRequested => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
_ => (),
}
})
}

View File

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

View File

@@ -1,137 +0,0 @@
#![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},
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 {
KeyCode::KeyM => {
if minimized {
minimized = !minimized;
window.set_minimized(minimized);
window.focus_window();
}
}
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

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

View File

@@ -1,60 +0,0 @@
#![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

@@ -1,72 +0,0 @@
#![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

@@ -1,57 +0,0 @@
use log::debug;
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
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("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:
KeyEvent {
logical_key: Key::Space,
state: ElementState::Released,
..
},
..
} => {
has_increments = !has_increments;
let new_increments = match window.resize_increments() {
Some(_) => None,
None => Some(LogicalSize::new(25.0, 25.0)),
};
debug!("Had increments: {}", new_increments.is_none());
window.set_resize_increments(new_increments);
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => window.request_redraw(),
_ => (),
})
}

View File

@@ -1,107 +0,0 @@
#![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,
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::ArrowRight => {
windows.get(&window_id).unwrap().select_next_tab();
}
Key::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,70 +1,62 @@
//! A demonstration of embedding a winit window in an existing X11 application. //! A demonstration of embedding a winit window in an existing X11 application.
use std::error::Error;
#[cfg(x11_platform)] #[cfg(x11_platform)]
#[path = "util/fill.rs"] fn main() -> Result<(), Box<dyn Error>> {
mod fill;
#[cfg(x11_platform)]
mod imple {
use super::fill;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
platform::x11::WindowBuilderExtX11, platform::x11::WindowAttributesExtX11,
window::WindowBuilder, window::Window,
}; };
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> { #[path = "util/fill.rs"]
// First argument should be a 32-bit X11 window ID. mod fill;
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(); // First argument should be a 32-bit X11 window ID.
let event_loop = EventLoop::new()?; let parent_window_id = std::env::args()
.nth(1)
.ok_or("Expected a 32-bit X11 window ID as the first argument.")?
.parse::<u32>()?;
let window = WindowBuilder::new() SimpleLogger::new().init().unwrap();
.with_title("An embedded window!") let event_loop = EventLoop::new()?;
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(parent_window_id) let mut window = None;
.build(&event_loop) event_loop.run(move |event, event_loop| match event {
.unwrap(); 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();
event_loop.run(move |event, elwt| {
match event { match event {
Event::WindowEvent { WindowEvent::CloseRequested => event_loop.exit(),
event: WindowEvent::CloseRequested, WindowEvent::RedrawRequested => {
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(); 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))] #[cfg(not(x11_platform))]
mod imple { fn main() -> Result<(), Box<dyn Error>> {
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> { println!("This example is only supported on X11 platforms.");
println!("This example is only supported on X11 platforms."); Ok(())
Ok(())
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
imple::entry()
} }

View File

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

280
src/cursor.rs Normal file
View File

@@ -0,0 +1,280 @@
use core::fmt;
use std::hash::Hasher;
use std::sync::Arc;
use std::{error::Error, hash::Hash};
use cursor_icon::CursorIcon;
use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource};
/// 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.
///
/// # Example
///
/// ```no_run
/// # use winit::event_loop::ActiveEventLoop;
/// # use winit::window::Window;
/// # fn scope(event_loop: &ActiveEventLoop, window: &Window) {
/// use winit::window::CustomCursor;
///
/// 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();
///
/// #[cfg(target_family = "wasm")]
/// let source = {
/// 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);
///
/// window.set_cursor(custom_cursor.clone());
/// # }
/// ```
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CustomCursor {
/// Platforms should make sure this is cheap to clone.
pub(crate) inner: PlatformCustomCursor,
}
impl CustomCursor {
/// Creates a new cursor from an rgba buffer.
///
/// The alpha channel is assumed to be **not** premultiplied.
pub fn from_rgba(
rgba: impl Into<Vec<u8>>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<CustomCursorSource, BadImage> {
Ok(CustomCursorSource {
inner: PlatformCustomCursorSource::from_rgba(
rgba.into(),
width,
height,
hotspot_x,
hotspot_y,
)?,
})
}
}
/// Source for [`CustomCursor`].
///
/// See [`CustomCursor`] for more details.
#[derive(Debug)]
pub struct CustomCursorSource {
pub(crate) inner: PlatformCustomCursorSource,
}
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
#[derive(Debug, Clone)]
pub enum BadImage {
/// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't
/// guarantee that the cursor will work, but should avoid many platform and device specific
/// limits.
TooLarge { width: u16, height: u16 },
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
/// safely interpreted as 32bpp RGBA pixels.
ByteCountNotDivisibleBy4 { byte_count: usize },
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
/// At least one of your arguments is incorrect.
DimensionsVsPixelCount {
width: u16,
height: u16,
width_x_height: u64,
pixel_count: u64,
},
/// Produced when the hotspot is outside the image bounds
HotspotOutOfBounds {
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
},
}
impl fmt::Display for BadImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BadImage::TooLarge { width, height } => write!(f,
"The specified dimensions ({width:?}x{height:?}) are too large. The maximum is {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
),
BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
),
BadImage::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
} => write!(f,
"The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.",
),
BadImage::HotspotOutOfBounds {
width,
height,
hotspot_x,
hotspot_y,
} => write!(f,
"The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds ({width:?}x{height:?}).",
),
}
}
}
impl Error for BadImage {}
/// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with
/// images.
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
#[allow(dead_code)]
impl OnlyCursorImageSource {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
}
}
/// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching.
#[derive(Debug, Clone)]
pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
impl Hash for OnlyCursorImage {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}
impl PartialEq for OnlyCursorImage {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for OnlyCursorImage {}
#[derive(Debug)]
#[allow(dead_code)]
pub(crate) struct CursorImage {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u16,
pub(crate) height: u16,
pub(crate) hotspot_x: u16,
pub(crate) hotspot_y: u16,
}
impl CursorImage {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
return Err(BadImage::TooLarge { width, height });
}
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadImage::ByteCountNotDivisibleBy4 {
byte_count: rgba.len(),
});
}
let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
let width_x_height = width as u64 * height as u64;
if pixel_count != width_x_height {
return Err(BadImage::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
});
}
if hotspot_x >= width || hotspot_y >= height {
return Err(BadImage::HotspotOutOfBounds {
width,
height,
hotspot_x,
hotspot_y,
});
}
Ok(CursorImage {
rgba,
width,
height,
hotspot_x,
hotspot_y,
})
}
}
// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct NoCustomCursor;
#[allow(dead_code)]
impl NoCustomCursor {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
Ok(Self)
}
}

View File

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

View File

@@ -34,11 +34,13 @@
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil //! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Mutex, Weak}; use std::sync::{Mutex, Weak};
#[cfg(not(wasm_platform))] #[cfg(not(web_platform))]
use std::time::Instant; use std::time::Instant;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use smol_str::SmolStr; use smol_str::SmolStr;
#[cfg(wasm_platform)] #[cfg(web_platform)]
use web_time::Instant; use web_time::Instant;
use crate::error::ExternalError; use crate::error::ExternalError;
@@ -307,7 +309,7 @@ pub enum WindowEvent {
/// The activation token was delivered back and now could be used. /// The activation token was delivered back and now could be used.
/// ///
#[cfg_attr( #[cfg_attr(
not(any(x11_platform, wayland_platfrom)), not(any(x11_platform, wayland_platform)),
allow(rustdoc::broken_intra_doc_links) allow(rustdoc::broken_intra_doc_links)
)] )]
/// Delivered in response to [`request_activation_token`]. /// Delivered in response to [`request_activation_token`].
@@ -445,21 +447,23 @@ pub enum WindowEvent {
button: MouseButton, button: MouseButton,
}, },
/// Touchpad magnification event with two-finger pinch gesture. /// Two-finger pinch gesture, often used for magnification.
///
/// Positive delta values indicate magnification (zooming in) and
/// negative delta values indicate shrinking (zooming out).
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - Only available on **macOS**. /// - Only available on **macOS** and **iOS**.
TouchpadMagnify { /// - On iOS, not recognized by default. It must be enabled when needed.
PinchGesture {
device_id: DeviceId, device_id: DeviceId,
/// Positive values indicate magnification (zooming in) and negative
/// values indicate shrinking (zooming out).
///
/// This value may be NaN.
delta: f64, delta: f64,
phase: TouchPhase, phase: TouchPhase,
}, },
/// Smart magnification event. /// Double tap gesture.
/// ///
/// On a Mac, smart magnification is triggered by a double tap with two fingers /// On a Mac, smart magnification is triggered by a double tap with two fingers
/// on the trackpad and is commonly used to zoom on a certain object /// on the trackpad and is commonly used to zoom on a certain object
@@ -469,24 +473,26 @@ pub enum WindowEvent {
/// The event is general enough that its generating gesture is allowed to vary /// The event is general enough that its generating gesture is allowed to vary
/// across platforms. It could also be generated by another device. /// across platforms. It could also be generated by another device.
/// ///
/// Unfortunatly, neither [Windows](https://support.microsoft.com/en-us/windows/touch-gestures-for-windows-a9d28305-4818-a5df-4e2b-e5590f850741) /// Unfortunately, neither [Windows](https://support.microsoft.com/en-us/windows/touch-gestures-for-windows-a9d28305-4818-a5df-4e2b-e5590f850741)
/// nor [Wayland](https://wayland.freedesktop.org/libinput/doc/latest/gestures.html) /// nor [Wayland](https://wayland.freedesktop.org/libinput/doc/latest/gestures.html)
/// support this gesture or any other gesture with the same effect. /// support this gesture or any other gesture with the same effect.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - Only available on **macOS 10.8** and later. /// - Only available on **macOS 10.8** and later, and **iOS**.
SmartMagnify { device_id: DeviceId }, /// - On iOS, not recognized by default. It must be enabled when needed.
DoubleTapGesture { device_id: DeviceId },
/// Touchpad rotation event with two-finger rotation gesture. /// Two-finger rotation gesture.
/// ///
/// Positive delta values indicate rotation counterclockwise and /// Positive delta values indicate rotation counterclockwise and
/// negative delta values indicate rotation clockwise. /// negative delta values indicate rotation clockwise.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - Only available on **macOS**. /// - Only available on **macOS** and **iOS**.
TouchpadRotate { /// - On iOS, not recognized by default. It must be enabled when needed.
RotationGesture {
device_id: DeviceId, device_id: DeviceId,
delta: f32, delta: f32,
phase: TouchPhase, phase: TouchPhase,
@@ -530,11 +536,10 @@ pub enum WindowEvent {
/// * Changing the display's scale factor (e.g. in Control Panel on Windows). /// * Changing the display's scale factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different scale factor. /// * Moving the window to a display with a different scale factor.
/// ///
/// After this event callback has been processed, the window will be resized to whatever value /// To update the window size, use the provided [`InnerSizeWriter`] handle. By default, the window is
/// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested /// resized to the value suggested by the OS, but it can be changed to any value.
/// by the OS, but it can be changed to any value.
/// ///
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module. /// For more information about DPI in general, see the [`dpi`] crate.
ScaleFactorChanged { ScaleFactorChanged {
scale_factor: f64, scale_factor: f64,
/// Handle to update inner size during scale changes. /// Handle to update inner size during scale changes.
@@ -574,7 +579,7 @@ pub enum WindowEvent {
/// ### Others /// ### Others
/// ///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **Android / Windows / Orbital:** Unsupported. /// - **Android / Wayland / Windows / Orbital:** Unsupported.
/// ///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
@@ -671,7 +676,7 @@ pub enum DeviceEvent {
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RawKeyEvent { pub struct RawKeyEvent {
pub physical_key: keyboard::KeyCode, pub physical_key: keyboard::PhysicalKey,
pub state: ElementState, pub state: ElementState,
} }
@@ -703,7 +708,7 @@ pub struct KeyEvent {
/// `Fn` and `FnLock` key events are *exceedingly unlikely* to be emitted by Winit. These keys /// `Fn` and `FnLock` key events are *exceedingly unlikely* to be emitted by Winit. These keys
/// are usually handled at the hardware or OS level, and aren't surfaced to applications. If /// are usually handled at the hardware or OS level, and aren't surfaced to applications. If
/// you somehow see this in the wild, we'd like to know :) /// you somehow see this in the wild, we'd like to know :)
pub physical_key: keyboard::KeyCode, pub physical_key: keyboard::PhysicalKey,
// Allowing `broken_intra_doc_links` for `logical_key`, because // Allowing `broken_intra_doc_links` for `logical_key`, because
// `key_without_modifiers` is not available on all platforms // `key_without_modifiers` is not available on all platforms
@@ -743,7 +748,7 @@ pub struct KeyEvent {
/// An additional difference from `logical_key` is that /// An additional difference from `logical_key` is that
/// this field stores the text representation of any key /// this field stores the text representation of any key
/// that has such a representation. For example when /// that has such a representation. For example when
/// `logical_key` is `Key::Enter`, this field is `Some("\r")`. /// `logical_key` is `Key::Named(NamedKey::Enter)`, this field is `Some("\r")`.
/// ///
/// This is `None` if the current keypress cannot /// This is `None` if the current keypress cannot
/// be interpreted as text. /// be interpreted as text.
@@ -1107,7 +1112,7 @@ pub enum MouseScrollDelta {
PixelDelta(PhysicalPosition<f64>), PixelDelta(PhysicalPosition<f64>),
} }
/// Handle to synchroniously change the size of the window from the /// Handle to synchronously change the size of the window from the
/// [`WindowEvent`]. /// [`WindowEvent`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct InnerSizeWriter { pub struct InnerSizeWriter {
@@ -1120,7 +1125,7 @@ impl InnerSizeWriter {
Self { new_inner_size } Self { new_inner_size }
} }
/// Try to request inner size which will be set synchroniously on the window. /// Try to request inner size which will be set synchronously on the window.
pub fn request_inner_size( pub fn request_inner_size(
&mut self, &mut self,
new_inner_size: PhysicalSize<u32>, new_inner_size: PhysicalSize<u32>,
@@ -1199,13 +1204,13 @@ mod tests {
state: event::ElementState::Pressed, state: event::ElementState::Pressed,
button: event::MouseButton::Other(0), button: event::MouseButton::Other(0),
}); });
with_window_event(TouchpadMagnify { with_window_event(PinchGesture {
device_id: did, device_id: did,
delta: 0.0, delta: 0.0,
phase: event::TouchPhase::Started, phase: event::TouchPhase::Started,
}); });
with_window_event(SmartMagnify { device_id: did }); with_window_event(DoubleTapGesture { device_id: did });
with_window_event(TouchpadRotate { with_window_event(RotationGesture {
device_id: did, device_id: did,
delta: 0.0, delta: 0.0,
phase: event::TouchPhase::Started, phase: event::TouchPhase::Started,

View File

@@ -8,16 +8,18 @@
//! See the root-level documentation for information on how to create and use an event loop to //! See the root-level documentation for information on how to create and use an event loop to
//! handle events. //! handle events.
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::Deref; #[cfg(any(x11_platform, wayland_platform))]
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::{error, fmt}; use std::{error, fmt};
#[cfg(not(wasm_platform))] #[cfg(not(web_platform))]
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
#[cfg(wasm_platform)] #[cfg(web_platform)]
use web_time::{Duration, Instant}; use web_time::{Duration, Instant};
use crate::error::EventLoopError; use crate::error::{EventLoopError, OsError};
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
use crate::{event::Event, monitor::MonitorHandle, platform_impl}; use crate::{event::Event, monitor::MonitorHandle, platform_impl};
/// Provides a way to retrieve events from the system and from the windows that were registered to /// Provides a way to retrieve events from the system and from the windows that were registered to
@@ -43,11 +45,9 @@ pub struct EventLoop<T: 'static> {
/// Target that associates windows with an [`EventLoop`]. /// Target that associates windows with an [`EventLoop`].
/// ///
/// This type exists to allow you to create new windows while Winit executes /// This type exists to allow you to create new windows while Winit executes
/// your callback. [`EventLoop`] will coerce into this type (`impl<T> Deref for /// your callback.
/// EventLoop<T>`), so functions that take this as a parameter can also take pub struct ActiveEventLoop {
/// `&EventLoop`. pub(crate) p: platform_impl::ActiveEventLoop,
pub struct EventLoopWindowTarget<T: 'static> {
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
} }
@@ -55,33 +55,26 @@ pub struct EventLoopWindowTarget<T: 'static> {
/// ///
/// This is used to make specifying options that affect the whole application /// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported. /// easier. But note that constructing multiple event loops is not supported.
///
/// This can be created using [`EventLoop::new`] or [`EventLoop::with_user_event`].
#[derive(Default)] #[derive(Default)]
pub struct EventLoopBuilder<T: 'static> { pub struct EventLoopBuilder<T: 'static> {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes, pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
_p: PhantomData<T>, _p: PhantomData<T>,
} }
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl EventLoopBuilder<()> { impl EventLoopBuilder<()> {
/// Start building a new event loop. /// Start building a new event loop.
#[inline] #[inline]
#[deprecated = "use `EventLoop::builder` instead"]
pub fn new() -> Self { pub fn new() -> Self {
Self::with_user_event() EventLoop::builder()
} }
} }
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl<T> EventLoopBuilder<T> { impl<T> EventLoopBuilder<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
#[inline]
pub fn with_user_event() -> Self {
Self {
platform_specific: Default::default(),
_p: PhantomData,
}
}
/// Builds a new event loop. /// Builds a new event loop.
/// ///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread, /// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
@@ -128,7 +121,7 @@ impl<T> EventLoopBuilder<T> {
}) })
} }
#[cfg(wasm_platform)] #[cfg(web_platform)]
pub(crate) fn allow_event_loop_recreation() { pub(crate) fn allow_event_loop_recreation() {
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed); EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
} }
@@ -140,13 +133,13 @@ impl<T> fmt::Debug for EventLoop<T> {
} }
} }
impl<T> fmt::Debug for EventLoopWindowTarget<T> { impl fmt::Debug for ActiveEventLoop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoopWindowTarget { .. }") f.pad("ActiveEventLoop { .. }")
} }
} }
/// Set through [`EventLoopWindowTarget::set_control_flow()`]. /// Set through [`ActiveEventLoop::set_control_flow()`].
/// ///
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted. /// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
/// ///
@@ -191,27 +184,38 @@ impl ControlFlow {
} }
impl EventLoop<()> { impl EventLoop<()> {
/// Alias for [`EventLoopBuilder::new().build()`]. /// Create the event loop.
/// ///
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build /// This is an alias of `EventLoop::builder().build()`.
#[inline] #[inline]
pub fn new() -> Result<EventLoop<()>, EventLoopError> { pub fn new() -> Result<EventLoop<()>, EventLoopError> {
EventLoopBuilder::new().build() 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()
} }
} }
impl<T> EventLoop<T> { impl<T> EventLoop<T> {
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."] /// Start building a new event loop, with the given type as the user event
pub fn with_user_event() -> Result<EventLoop<T>, EventLoopError> { /// type.
EventLoopBuilder::<T>::with_user_event().build() pub fn with_user_event() -> EventLoopBuilder<T> {
EventLoopBuilder {
platform_specific: Default::default(),
_p: PhantomData,
}
} }
/// Runs the event loop in the calling thread and calls the given `event_handler` closure /// Runs the event loop in the calling thread and calls the given `event_handler` closure
/// to dispatch any pending events. /// to dispatch any pending events.
/// ///
/// Since the closure is `'static`, it must be a `move` closure if it needs to
/// access any data from the calling context.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
/// ///
/// ## Platform-specific /// ## Platform-specific
@@ -222,54 +226,152 @@ impl<T> EventLoop<T> {
/// (that Rust doesn't see) that will also mean that the rest of the function is never executed /// (that Rust doesn't see) that will also mean that the rest of the function is never executed
/// and any values not passed to this function will *not* be dropped. /// and any values not passed to this function will *not* be dropped.
/// ///
/// Web applications are recommended to use `spawn()` instead of `run()` to avoid the need /// Web applications are recommended to use
#[cfg_attr(
web_platform,
doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]"
)]
#[cfg_attr(not(web_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 /// for the Javascript exception trick, and to make it clearer that the event loop runs
/// asynchronously (via the browser's own, internal, event loop) and doesn't block the /// asynchronously (via the browser's own, internal, event loop) and doesn't block the
/// current thread of execution like it does on other platforms. /// current thread of execution like it does on other platforms.
/// ///
/// This function won't be available with `target_feature = "exception-handling"`. /// This function won't be available with `target_feature = "exception-handling"`.
/// ///
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
/// [`run()`]: Self::run()
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on Web.
#[inline] #[inline]
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))] #[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError> pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(Event<T>, &EventLoopWindowTarget<T>), F: FnMut(Event<T>, &ActiveEventLoop),
{ {
self.event_loop.run(event_handler) self.event_loop.run(event_handler)
} }
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events to the main event loop. /// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy { EventLoopProxy {
event_loop_proxy: self.event_loop.create_proxy(), event_loop_proxy: self.event_loop.create_proxy(),
} }
} }
/// Gets a persistent reference to the underlying platform display.
///
/// See the [`OwnedDisplayHandle`] type for more information.
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle {
platform: self.event_loop.window_target().p.owned_display_handle(),
}
}
/// Change if or when [`DeviceEvent`]s are captured.
///
/// See [`ActiveEventLoop::listen_device_events`] for details.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn listen_device_events(&self, allowed: DeviceEvents) {
self.event_loop
.window_target()
.p
.listen_device_events(allowed);
}
/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.event_loop
.window_target()
.p
.set_control_flow(control_flow)
}
/// Create a window.
///
/// Creating window without event loop running often leads to improper window creation;
/// use [`ActiveEventLoop::create_window`] instead.
#[deprecated = "use `ActiveEventLoop::create_window` instead"]
#[inline]
pub fn create_window(&self, window_attributes: WindowAttributes) -> Result<Window, OsError> {
let window =
platform_impl::Window::new(&self.event_loop.window_target().p, window_attributes)?;
Ok(Window { window })
}
/// Create custom cursor.
pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor {
self.event_loop
.window_target()
.p
.create_custom_cursor(custom_cursor)
}
} }
#[cfg(feature = "rwh_06")] #[cfg(feature = "rwh_06")]
impl<T> rwh_06::HasDisplayHandle for EventLoop<T> { impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> { fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
rwh_06::HasDisplayHandle::display_handle(&**self) rwh_06::HasDisplayHandle::display_handle(self.event_loop.window_target())
} }
} }
#[cfg(feature = "rwh_05")] #[cfg(feature = "rwh_05")]
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> { unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop. /// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
rwh_05::HasRawDisplayHandle::raw_display_handle(&**self) rwh_05::HasRawDisplayHandle::raw_display_handle(self.event_loop.window_target())
} }
} }
impl<T> Deref for EventLoop<T> { #[cfg(any(x11_platform, wayland_platform))]
type Target = EventLoopWindowTarget<T>; impl<T> AsFd for EventLoop<T> {
fn deref(&self) -> &EventLoopWindowTarget<T> { /// Get the underlying [EventLoop]'s `fd` which you can register
self.event_loop.window_target() /// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_events`] API.
///
/// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio
/// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
} }
} }
impl<T> EventLoopWindowTarget<T> { #[cfg(any(x11_platform, wayland_platform))]
impl<T> AsRawFd for EventLoop<T> {
/// Get the underlying [EventLoop]'s raw `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_events`] API.
///
/// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio
/// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
}
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 window = platform_impl::Window::new(&self.p, window_attributes)?;
Ok(Window { window })
}
/// Create custom cursor.
pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor {
self.p.create_custom_cursor(custom_cursor)
}
/// Returns the list of all the monitors available on the system. /// Returns the list of all the monitors available on the system.
#[inline] #[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> { pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
@@ -332,10 +434,19 @@ impl<T> EventLoopWindowTarget<T> {
pub fn exiting(&self) -> bool { pub fn exiting(&self) -> bool {
self.p.exiting() self.p.exiting()
} }
/// Gets a persistent reference to the underlying platform display.
///
/// See the [`OwnedDisplayHandle`] type for more information.
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle {
platform: self.p.owned_display_handle(),
}
}
} }
#[cfg(feature = "rwh_06")] #[cfg(feature = "rwh_06")]
impl<T> rwh_06::HasDisplayHandle for EventLoopWindowTarget<T> { impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> { fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.p.raw_display_handle_rwh_06()?; let raw = self.p.raw_display_handle_rwh_06()?;
// SAFETY: The display will never be deallocated while the event loop is alive. // SAFETY: The display will never be deallocated while the event loop is alive.
@@ -344,13 +455,59 @@ impl<T> rwh_06::HasDisplayHandle for EventLoopWindowTarget<T> {
} }
#[cfg(feature = "rwh_05")] #[cfg(feature = "rwh_05")]
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoopWindowTarget<T> { unsafe impl rwh_05::HasRawDisplayHandle for ActiveEventLoop {
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop. /// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
self.p.raw_display_handle_rwh_05() self.p.raw_display_handle_rwh_05()
} }
} }
/// A proxy for the underlying display handle.
///
/// The purpose of this type is to provide a cheaply clonable handle to the underlying
/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`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`]. /// Used to send custom events to [`EventLoop`].
pub struct EventLoopProxy<T: 'static> { pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>, event_loop_proxy: platform_impl::EventLoopProxy<T>,
@@ -415,22 +572,22 @@ pub enum DeviceEvents {
/// This could be used to identify the async request once it's done /// This could be used to identify the async request once it's done
/// and a specific action must be taken. /// and a specific action must be taken.
/// ///
/// One of the handling scenarious could be to maintain a working list /// One of the handling scenarios could be to maintain a working list
/// containing [`AsyncRequestSerial`] and some closure associated with it. /// containing [`AsyncRequestSerial`] and some closure associated with it.
/// Then once event is arriving the working list is being traversed and a job /// Then once event is arriving the working list is being traversed and a job
/// executed and removed from the list. /// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AsyncRequestSerial { pub struct AsyncRequestSerial {
serial: u64, serial: usize,
} }
impl AsyncRequestSerial { impl AsyncRequestSerial {
// TODO(kchibisov) remove `cfg` when the clipboard will be added. // TODO(kchibisov): Remove `cfg` when the clipboard will be added.
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn get() -> Self { pub(crate) fn get() -> Self {
static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0); static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0);
// NOTE: we rely on wrap around here, while the user may just request // NOTE: We rely on wrap around here, while the user may just request
// in the loop u64::MAX times that's issue is considered on them. // in the loop usize::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed); let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial } Self { serial }
} }

View File

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

View File

@@ -20,7 +20,7 @@
// the W3C short notice apply to the `Key` and `KeyCode` enums and their variants and the // the W3C short notice apply to the `Key` and `KeyCode` enums and their variants and the
// documentation attached to their variants. // documentation attached to their variants.
// --------- BEGGINING OF W3C LICENSE -------------------------------------------------------------- // --------- BEGINNING OF W3C LICENSE --------------------------------------------------------------
// //
// License // License
// //
@@ -55,7 +55,7 @@
// //
// --------- END OF W3C LICENSE -------------------------------------------------------------------- // --------- END OF W3C LICENSE --------------------------------------------------------------------
// --------- BEGGINING OF W3C SHORT NOTICE --------------------------------------------------------- // --------- BEGINNING OF W3C SHORT NOTICE ---------------------------------------------------------
// //
// winit: https://github.com/rust-windowing/winit // winit: https://github.com/rust-windowing/winit
// //
@@ -69,6 +69,9 @@
// //
// --------- END OF W3C SHORT NOTICE --------------------------------------------------------------- // --------- END OF W3C SHORT NOTICE ---------------------------------------------------------------
use bitflags::bitflags;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub use smol_str::SmolStr; pub use smol_str::SmolStr;
/// Contains the platform-native physical key identifier /// Contains the platform-native physical key identifier
@@ -185,26 +188,112 @@ impl std::fmt::Debug for NativeKey {
} }
} }
impl From<NativeKeyCode> for NativeKey {
#[inline]
fn from(code: NativeKeyCode) -> Self {
match code {
NativeKeyCode::Unidentified => NativeKey::Unidentified,
NativeKeyCode::Android(x) => NativeKey::Android(x),
NativeKeyCode::MacOS(x) => NativeKey::MacOS(x),
NativeKeyCode::Windows(x) => NativeKey::Windows(x),
NativeKeyCode::Xkb(x) => NativeKey::Xkb(x),
}
}
}
impl PartialEq<NativeKey> for NativeKeyCode {
#[allow(clippy::cmp_owned)] // uses less code than direct match; target is stack allocated
#[inline]
fn eq(&self, rhs: &NativeKey) -> bool {
NativeKey::from(*self) == *rhs
}
}
impl PartialEq<NativeKeyCode> for NativeKey {
#[inline]
fn eq(&self, rhs: &NativeKeyCode) -> bool {
rhs == self
}
}
/// Represents the location of a physical key. /// Represents the location of a physical key.
/// ///
/// This type is a superset of [`KeyCode`], including an [`Unidentified`](Self::Unidentified)
/// variant.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PhysicalKey {
/// A known key code
Code(KeyCode),
/// This variant is used when the key cannot be translated to a [`KeyCode`]
///
/// The native keycode is provided (if available) so you're able to more reliably match
/// key-press and key-release events by hashing the [`PhysicalKey`]. It is also possible to use
/// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
Unidentified(NativeKeyCode),
}
impl From<KeyCode> for PhysicalKey {
#[inline]
fn from(code: KeyCode) -> Self {
PhysicalKey::Code(code)
}
}
impl From<NativeKeyCode> for PhysicalKey {
#[inline]
fn from(code: NativeKeyCode) -> Self {
PhysicalKey::Unidentified(code)
}
}
impl PartialEq<KeyCode> for PhysicalKey {
#[inline]
fn eq(&self, rhs: &KeyCode) -> bool {
match self {
PhysicalKey::Code(ref code) => code == rhs,
_ => false,
}
}
}
impl PartialEq<PhysicalKey> for KeyCode {
#[inline]
fn eq(&self, rhs: &PhysicalKey) -> bool {
rhs == self
}
}
impl PartialEq<NativeKeyCode> for PhysicalKey {
#[inline]
fn eq(&self, rhs: &NativeKeyCode) -> bool {
match self {
PhysicalKey::Unidentified(ref code) => code == rhs,
_ => false,
}
}
}
impl PartialEq<PhysicalKey> for NativeKeyCode {
#[inline]
fn eq(&self, rhs: &PhysicalKey) -> bool {
rhs == self
}
}
/// Code representing the location of a physical key
///
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few /// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few
/// exceptions: /// exceptions:
/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and /// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and
/// "SuperRight" here. /// "SuperRight" here.
/// - The key that the specification calls "Super" is reported as `Unidentified` here. /// - The key that the specification calls "Super" is reported as `Unidentified` here.
/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`.
/// ///
/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables /// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum KeyCode { pub enum KeyCode {
/// This variant is used when the key cannot be translated to any other variant.
///
/// The native keycode is provided (if available) so you're able to more reliably match
/// key-press and key-release events by hashing the [`KeyCode`]. It is also possible to use
/// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
Unidentified(NativeKeyCode),
/// <kbd>`</kbd> on a US keyboard. This is also called a backtick or grave. /// <kbd>`</kbd> on a US keyboard. This is also called a backtick or grave.
/// This is the <kbd>半角</kbd>/<kbd>全角</kbd>/<kbd>漢字</kbd> /// This is the <kbd>半角</kbd>/<kbd>全角</kbd>/<kbd>漢字</kbd>
/// (hankaku/zenkaku/kanji) key on Japanese keyboards /// (hankaku/zenkaku/kanji) key on Japanese keyboards
@@ -648,40 +737,20 @@ pub enum KeyCode {
F35, F35,
} }
/// Key represents the meaning of a keypress. /// A [`Key::Named`] value
/// ///
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few /// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few
/// exceptions: /// exceptions:
/// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's /// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's
/// another key which the specification calls `Super`. That does not exist here.) /// another key which the specification calls `Super`. That does not exist here.)
/// - The `Space` variant here, can be identified by the character it generates in the /// - The `Space` variant here, can be identified by the character it generates in the
/// specificaiton. /// specification.
/// - 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.
/// ///
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/ /// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Key<Str = SmolStr> { pub enum NamedKey {
/// A key string that corresponds to the character typed by the user, taking into account the
/// users current locale setting, and any system-level keyboard mapping overrides that are in
/// effect.
Character(Str),
/// This variant is used when the key cannot be translated to any other variant.
///
/// The native key is provided (if available) in order to allow the user to specify keybindings
/// for keys which are not defined by this API, mainly through some sort of UI.
Unidentified(NativeKey),
/// Contains the text representation of the dead-key when available.
///
/// ## Platform-specific
/// - **Web:** Always contains `None`
Dead(Option<char>),
/// The `Alt` (Alternative) key. /// The `Alt` (Alternative) key.
/// ///
/// This key enables the alternate modifier function for interpreting concurrent or subsequent /// This key enables the alternate modifier function for interpreting concurrent or subsequent
@@ -845,7 +914,7 @@ pub enum Key<Str = SmolStr> {
Standby, Standby,
/// The WakeUp key. (`KEYCODE_WAKEUP`) /// The WakeUp key. (`KEYCODE_WAKEUP`)
WakeUp, WakeUp,
/// Initate the multi-candidate mode. /// Initiate the multi-candidate mode.
AllCandidates, AllCandidates,
Alphanumeric, Alphanumeric,
/// Initiate the Code Input mode to allow characters to be entered by /// Initiate the Code Input mode to allow characters to be entered by
@@ -1385,83 +1454,131 @@ pub enum Key<Str = SmolStr> {
F35, F35,
} }
macro_rules! map_match { /// Key represents the meaning of a keypress.
( ///
$to_match:expr, /// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
// Custom match arms /// additions:
{ $( $from:pat => $to:expr ),* }, /// - All simple variants are wrapped under the `Named` variant
// The enum's name /// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`.
$prefix:path, /// - The `Dead` variant here, can specify the character which is inserted when pressing the
// Trivial match arms for unit variants /// dead-key twice.
{ $( $t:tt ),* }) => { ///
match $to_match { /// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
$( $from => $to, )* #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
$( Key::$t => Key::$t, )* #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Key<Str = SmolStr> {
/// A simple (unparameterised) action
Named(NamedKey),
/// A key string that corresponds to the character typed by the user, taking into account the
/// users current locale setting, and any system-level keyboard mapping overrides that are in
/// effect.
Character(Str),
/// This variant is used when the key cannot be translated to any other variant.
///
/// The native key is provided (if available) in order to allow the user to specify keybindings
/// for keys which are not defined by this API, mainly through some sort of UI.
Unidentified(NativeKey),
/// Contains the text representation of the dead-key when available.
///
/// ## Platform-specific
/// - **Web:** Always contains `None`
Dead(Option<char>),
}
impl From<NamedKey> for Key {
#[inline]
fn from(action: NamedKey) -> Self {
Key::Named(action)
}
}
impl From<NativeKey> for Key {
#[inline]
fn from(code: NativeKey) -> Self {
Key::Unidentified(code)
}
}
impl<Str> PartialEq<NamedKey> for Key<Str> {
#[inline]
fn eq(&self, rhs: &NamedKey) -> bool {
match self {
Key::Named(ref a) => a == rhs,
_ => false,
} }
}; }
}
impl<Str: PartialEq<str>> PartialEq<str> for Key<Str> {
#[inline]
fn eq(&self, rhs: &str) -> bool {
match self {
Key::Character(ref s) => s == rhs,
_ => false,
}
}
}
impl<Str: PartialEq<str>> PartialEq<&str> for Key<Str> {
#[inline]
fn eq(&self, rhs: &&str) -> bool {
self == *rhs
}
}
impl<Str> PartialEq<NativeKey> for Key<Str> {
#[inline]
fn eq(&self, rhs: &NativeKey) -> bool {
match self {
Key::Unidentified(ref code) => code == rhs,
_ => false,
}
}
}
impl<Str> PartialEq<Key<Str>> for NativeKey {
#[inline]
fn eq(&self, rhs: &Key<Str>) -> bool {
rhs == self
}
} }
impl Key<SmolStr> { impl Key<SmolStr> {
/// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on /// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on
/// `Key`. All other variants remain unchanged. /// `Key`. All other variants remain unchanged.
pub fn as_ref(&self) -> Key<&str> { pub fn as_ref(&self) -> Key<&str> {
map_match!( match self {
self, Key::Named(a) => Key::Named(*a),
{ Key::Character(ch) => Key::Character(ch.as_str()),
Key::Character(ch) => Key::Character(ch.as_str()), Key::Dead(d) => Key::Dead(*d),
Key::Dead(d) => Key::Dead(*d), Key::Unidentified(u) => Key::Unidentified(u.clone()),
Key::Unidentified(u) => Key::Unidentified(u.clone()) }
}, }
Key, }
{
Alt, AltGraph, CapsLock, Control, Fn, FnLock, NumLock, ScrollLock, Shift, Symbol, impl NamedKey {
SymbolLock, Meta, Hyper, Super, Enter, Tab, Space, ArrowDown, ArrowLeft, /// Convert an action to its approximate textual equivalent.
ArrowRight, ArrowUp, End, Home, PageDown, PageUp, Backspace, Clear, Copy, CrSel, ///
Cut, Delete, EraseEof, ExSel, Insert, Paste, Redo, Undo, Accept, Again, Attn, /// # Examples
Cancel, ContextMenu, Escape, Execute, Find, Help, Pause, Play, Props, Select, ///
ZoomIn, ZoomOut, BrightnessDown, BrightnessUp, Eject, LogOff, Power, PowerOff, /// ```
PrintScreen, Hibernate, Standby, WakeUp, AllCandidates, Alphanumeric, CodeInput, /// use winit::keyboard::NamedKey;
Compose, Convert, FinalMode, GroupFirst, GroupLast, GroupNext, GroupPrevious, ///
ModeChange, NextCandidate, NonConvert, PreviousCandidate, Process, SingleCandidate, /// assert_eq!(NamedKey::Enter.to_text(), Some("\r"));
HangulMode, HanjaMode, JunjaMode, Eisu, Hankaku, Hiragana, HiraganaKatakana, /// assert_eq!(NamedKey::F20.to_text(), None);
KanaMode, KanjiMode, Katakana, Romaji, Zenkaku, ZenkakuHankaku, Soft1, Soft2, /// ```
Soft3, Soft4, ChannelDown, ChannelUp, Close, MailForward, MailReply, MailSend, pub fn to_text(&self) -> Option<&str> {
MediaClose, MediaFastForward, MediaPause, MediaPlay, MediaPlayPause, MediaRecord, match self {
MediaRewind, MediaStop, MediaTrackNext, MediaTrackPrevious, New, Open, Print, Save, NamedKey::Enter => Some("\r"),
SpellCheck, Key11, Key12, AudioBalanceLeft, AudioBalanceRight, AudioBassBoostDown, NamedKey::Backspace => Some("\x08"),
AudioBassBoostToggle, AudioBassBoostUp, AudioFaderFront, AudioFaderRear, NamedKey::Tab => Some("\t"),
AudioSurroundModeNext, AudioTrebleDown, AudioTrebleUp, AudioVolumeDown, NamedKey::Space => Some(" "),
AudioVolumeUp, AudioVolumeMute, MicrophoneToggle, MicrophoneVolumeDown, NamedKey::Escape => Some("\x1b"),
MicrophoneVolumeUp, MicrophoneVolumeMute, SpeechCorrectionList, SpeechInputToggle, _ => None,
LaunchApplication1, LaunchApplication2, LaunchCalendar, LaunchContacts, LaunchMail, }
LaunchMediaPlayer, LaunchMusicPlayer, LaunchPhone, LaunchScreenSaver,
LaunchSpreadsheet, LaunchWebBrowser, LaunchWebCam, LaunchWordProcessor,
BrowserBack, BrowserFavorites, BrowserForward, BrowserHome, BrowserRefresh,
BrowserSearch, BrowserStop, AppSwitch, Call, Camera, CameraFocus, EndCall, GoBack,
GoHome, HeadsetHook, LastNumberRedial, Notification, MannerMode, VoiceDial, TV,
TV3DMode, TVAntennaCable, TVAudioDescription, TVAudioDescriptionMixDown,
TVAudioDescriptionMixUp, TVContentsMenu, TVDataService, TVInput, TVInputComponent1,
TVInputComponent2, TVInputComposite1, TVInputComposite2, TVInputHDMI1,
TVInputHDMI2, TVInputHDMI3, TVInputHDMI4, TVInputVGA1, TVMediaContext, TVNetwork,
TVNumberEntry, TVPower, TVRadioService, TVSatellite, TVSatelliteBS, TVSatelliteCS,
TVSatelliteToggle, TVTerrestrialAnalog, TVTerrestrialDigital, TVTimer, AVRInput,
AVRPower, ColorF0Red, ColorF1Green, ColorF2Yellow, ColorF3Blue, ColorF4Grey,
ColorF5Brown, ClosedCaptionToggle, Dimmer, DisplaySwap, DVR, Exit, FavoriteClear0,
FavoriteClear1, FavoriteClear2, FavoriteClear3, FavoriteRecall0, FavoriteRecall1,
FavoriteRecall2, FavoriteRecall3, FavoriteStore0, FavoriteStore1, FavoriteStore2,
FavoriteStore3, Guide, GuideNextDay, GuidePreviousDay, Info, InstantReplay, Link,
ListProgram, LiveContent, Lock, MediaApps, MediaAudioTrack, MediaLast,
MediaSkipBackward, MediaSkipForward, MediaStepBackward, MediaStepForward,
MediaTopMenu, NavigateIn, NavigateNext, NavigateOut, NavigatePrevious,
NextFavoriteChannel, NextUserProfile, OnDemand, Pairing, PinPDown, PinPMove,
PinPToggle, PinPUp, PlaySpeedDown, PlaySpeedReset, PlaySpeedUp, RandomToggle,
RcLowBattery, RecordSpeedNext, RfBypass, ScanChannelsToggle, ScreenModeNext,
Settings, SplitScreenToggle, STBInput, STBPower, Subtitle, Teletext, VideoModeNext,
Wink, ZoomToggle, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15,
F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31,
F32, F33, F34, F35
}
)
} }
} }
@@ -1471,20 +1588,16 @@ impl Key {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use winit::keyboard::Key; /// use winit::keyboard::{NamedKey, Key};
/// ///
/// assert_eq!(Key::Character("a".into()).to_text(), Some("a")); /// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
/// assert_eq!(Key::Enter.to_text(), Some("\r")); /// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
/// assert_eq!(Key::F20.to_text(), None); /// assert_eq!(Key::Named(NamedKey::F20).to_text(), None);
/// ``` /// ```
pub fn to_text(&self) -> Option<&str> { pub fn to_text(&self) -> Option<&str> {
match self { match self {
Key::Named(action) => action.to_text(),
Key::Character(ch) => Some(ch.as_str()), Key::Character(ch) => Some(ch.as_str()),
Key::Enter => Some("\r"),
Key::Backspace => Some("\x08"),
Key::Tab => Some("\t"),
Key::Space => Some(" "),
Key::Escape => Some("\x1b"),
_ => None, _ => None,
} }
} }
@@ -1572,7 +1685,7 @@ bitflags! {
/// Represents the current state of the keyboard modifiers /// Represents the current state of the keyboard modifiers
/// ///
/// Each flag represents a modifier and is set if this modifier is active. /// Each flag represents a modifier and is set if this modifier is active.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ModifiersState: u32 { pub struct ModifiersState: u32 {
/// The "shift" key. /// The "shift" key.
const SHIFT = 0b100; const SHIFT = 0b100;

View File

@@ -2,22 +2,15 @@
//! //!
//! # Building windows //! # Building windows
//! //!
//! Before you can build a [`Window`], you first need to build an [`EventLoop`]. This is done with the //! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with
//! [`EventLoop::new()`] function. //! the [`EventLoop::new()`] function.
//! //!
//! ```no_run //! ```no_run
//! use winit::event_loop::EventLoop; //! use winit::event_loop::EventLoop;
//! let event_loop = EventLoop::new().unwrap(); //! let event_loop = EventLoop::new().unwrap();
//! ``` //! ```
//! //!
//! Once this is done there are two ways to create a [`Window`]: //! Then you create a [`Window`] with [`create_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 //! # Event handling
//! //!
@@ -26,28 +19,47 @@
//! window or a key getting pressed while the window is focused. Devices can generate //! window or a key getting pressed while the window is focused. Devices can generate
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window. //! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a //! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired. //! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if desired.
//! //!
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will //! You can retrieve events by calling [`EventLoop::run()`]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`Event`]`::`[`LoopExiting`]. //! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
//! //!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop //! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on //! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
//! most other platforms. However, this model can be re-implemented to an extent with //! most other platforms. However, this model can be re-implemented to an extent with
//! [`EventLoopExtPumpEvents::pump_events`]. See that method's documentation for more reasons about why #![cfg_attr(
//! it's discouraged, beyond compatibility reasons. any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
),
doc = "[`EventLoopExtPumpEvents::pump_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_events()]"
)]
#![cfg_attr(
not(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
)),
doc = "`EventLoopExtPumpEvents::pump_events()`"
)]
//! [^1]. See that method's documentation for more reasons about why
//! it's discouraged beyond compatibility reasons.
//! //!
//! //!
//! ```no_run //! ```no_run
//! use winit::{ //! use winit::{
//! event::{Event, WindowEvent}, //! event::{Event, WindowEvent},
//! event_loop::{ControlFlow, EventLoop}, //! event_loop::{ControlFlow, EventLoop},
//! window::WindowBuilder, //! window::Window,
//! }; //! };
//! //!
//! let event_loop = EventLoop::new().unwrap(); //! 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 //! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications. //! // dispatched any events. This is ideal for games and similar applications.
@@ -58,24 +70,29 @@
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll. //! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait); //! event_loop.set_control_flow(ControlFlow::Wait);
//! //!
//! event_loop.run(move |event, elwt| { //! let mut window = None;
//!
//! event_loop.run(move |event, event_loop| {
//! match event { //! match event {
//! Event::Resumed => {
//! window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
//! }
//! Event::WindowEvent { //! Event::WindowEvent {
//! event: WindowEvent::CloseRequested, //! event: WindowEvent::CloseRequested,
//! .. //! ..
//! } => { //! } => {
//! println!("The close button was pressed; stopping"); //! println!("The close button was pressed; stopping");
//! elwt.exit(); //! event_loop.exit();
//! }, //! },
//! Event::AboutToWait => { //! Event::AboutToWait => {
//! // Application update code. //! // Application update code.
//! //!
//! // Queue a RedrawRequested event. //! // Queue a RedrawRequested event.
//! // //! //
//! // You only need to call this if you've determined that you need to redraw, in //! // You only need to call this if you've determined that you need to redraw in
//! // applications which do not always need to. Applications that redraw continuously //! // applications which do not always need to. Applications that redraw continuously
//! // can just render here instead. //! // can render here instead.
//! window.request_redraw(); //! window.as_ref().unwrap().request_redraw();
//! }, //! },
//! Event::WindowEvent { //! Event::WindowEvent {
//! event: WindowEvent::RedrawRequested, //! event: WindowEvent::RedrawRequested,
@@ -92,74 +109,107 @@
//! }); //! });
//! ``` //! ```
//! //!
//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be //! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`] //! compared to the value returned by [`Window::id()`] to determine which [`Window`]
//! dispatched the event. //! dispatched the event.
//! //!
//! # Drawing on the window //! # Drawing on the window
//! //!
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to //! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you to
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the //! retrieve the raw handle of the window and display (see the [`platform`] module and/or the
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows //! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. //! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//! //!
//! Note that many platforms will display garbage data in the window's client area if the //! Note that many platforms will display garbage data in the window's client area if the
//! application doesn't render anything to the window by the time the desktop compositor is ready to //! application doesn't render anything to the window by the time the desktop compositor is ready to
//! display the window to the user. If you notice this happening, you should create the window with //! display the window to the user. If you notice this happening, you should create the window with
//! [`visible` set to `false`](crate::window::WindowBuilder::with_visible) and explicitly make the //! [`visible` set to `false`](crate::window::WindowAttributes::with_visible) and explicitly make the
//! window visible only once you're ready to render into it. //! window visible only once you're ready to render into it.
//! //!
//! # UI scaling
//!
//! UI scaling is important, go read the docs for the [`dpi`] crate for an
//! introduction.
//!
//! All of Winit's functions return physical types, but can take either logical or physical
//! coordinates as input, allowing you to use the most convenient coordinate system for your
//! particular application.
//!
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
//! monitor or if the user changes their DPI settings. This allows you to rescale your application's
//! UI elements and adjust how the platform changes the window's size to reflect the new scale
//! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor
//! can be found by calling [`window.scale_factor()`].
//!
//! [`ScaleFactorChanged`]: event::WindowEvent::ScaleFactorChanged
//! [`window.scale_factor()`]: window::Window::scale_factor
//!
//! # Cargo Features
//!
//! Winit provides the following Cargo features:
//!
//! * `x11` (enabled by default): On Unix platforms, enables the X11 backend.
//! * `wayland` (enabled by default): On Unix platforms, enables the Wayland
//! backend.
//! * `rwh_04`: Implement `raw-window-handle v0.4` traits.
//! * `rwh_05`: Implement `raw-window-handle v0.5` traits.
//! * `rwh_06`: Implement `raw-window-handle v0.6` traits.
//! * `serde`: Enables serialization/deserialization of certain types with
//! [Serde](https://crates.io/crates/serde).
//! * `mint`: Enables mint (math interoperability standard types) conversions.
//!
//! See the [`platform`] module for documentation on platform-specific cargo
//! features.
//!
//! [`EventLoop`]: event_loop::EventLoop //! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoopExtPumpEvents::pump_events`]: ./platform/pump_events/trait.EventLoopExtPumpEvents.html#tymethod.pump_events
//! [`EventLoop::new()`]: event_loop::EventLoop::new //! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [event_loop_run]: event_loop::EventLoop::run //! [`EventLoop::run()`]: event_loop::EventLoop::run
//! [`exit()`]: event_loop::EventLoopWindowTarget::exit //! [`exit()`]: event_loop::ActiveEventLoop::exit
//! [`Window`]: window::Window //! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId //! [`WindowId`]: window::WindowId
//! [`WindowBuilder`]: window::WindowBuilder //! [`WindowAttributes`]: window::WindowAttributes
//! [window_new]: window::Window::new //! [window_new]: window::Window::new
//! [window_builder_new]: window::WindowBuilder::new //! [`create_window`]: event_loop::ActiveEventLoop::create_window
//! [window_builder_build]: window::WindowBuilder::build //! [`Window::id()`]: window::Window::id
//! [window_id_fn]: window::Window::id
//! [`Event`]: event::Event
//! [`WindowEvent`]: event::WindowEvent //! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent //! [`DeviceEvent`]: event::DeviceEvent
//! [`UserEvent`]: event::Event::UserEvent //! [`Event::UserEvent`]: event::Event::UserEvent
//! [`LoopExiting`]: event::Event::LoopExiting //! [`Event::LoopExiting`]: event::Event::LoopExiting
//! [`platform`]: platform
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle //! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [^1]: `EventLoopExtPumpEvents::pump_events()` is only available on Windows, macOS, Android, X11 and Wayland.
#![deny(rust_2018_idioms)] #![deny(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)] #![deny(clippy::all)]
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(feature = "cargo-clippy", deny(warnings))] #![cfg_attr(clippy, deny(warnings))]
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc // Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(
docsrs,
feature(doc_auto_cfg, doc_cfg_hide),
doc(cfg_hide(doc, docsrs))
)]
#![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_safety_doc)]
#[cfg(feature = "rwh_06")] #[cfg(feature = "rwh_06")]
pub use rwh_06 as raw_window_handle; pub use rwh_06 as raw_window_handle;
#[allow(unused_imports)] // Re-export DPI types so that users don't have to put it in Cargo.toml.
#[macro_use] #[doc(inline)]
extern crate log; pub use dpi;
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
#[macro_use]
extern crate bitflags;
pub mod dpi;
#[macro_use] #[macro_use]
pub mod error; pub mod error;
mod cursor;
pub mod event; pub mod event;
pub mod event_loop; pub mod event_loop;
mod icon; mod icon;
pub mod keyboard; pub mod keyboard;
pub mod monitor; pub mod monitor;
mod platform_impl; mod platform_impl;
mod utils;
pub mod window; pub mod window;
pub mod platform; pub mod platform;

View File

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

View File

@@ -1,17 +1,83 @@
//! # Android
//!
//! The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/) crate.
//!
//! Native Android applications need some form of "glue" crate that is responsible
//! for defining the main entry point for your Rust application as well as tracking
//! various life-cycle events and synchronizing with the main JVM thread.
//!
//! Winit uses the [android-activity](https://docs.rs/android-activity/) as a
//! glue crate (prior to `0.28` it used
//! [ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue)).
//!
//! The version of the glue crate that your application depends on _must_ match the
//! version that Winit depends on because the glue crate is responsible for your
//! application's main entry point. If Cargo resolves multiple versions, they will
//! clash.
//!
//! `winit` glue compatibility table:
//!
//! | winit | ndk-glue |
//! | :---: | :--------------------------: |
//! | 0.29 | `android-activity = "0.5"` |
//! | 0.28 | `android-activity = "0.4"` |
//! | 0.27 | `ndk-glue = "0.7"` |
//! | 0.26 | `ndk-glue = "0.5"` |
//! | 0.25 | `ndk-glue = "0.3"` |
//! | 0.24 | `ndk-glue = "0.2"` |
//!
//! The recommended way to avoid a conflict with the glue version is to avoid explicitly
//! depending on the `android-activity` crate, and instead consume the API that
//! is re-exported by Winit under `winit::platform::android::activity::*`
//!
//! Running on an Android device needs a dynamic system library. Add this to Cargo.toml:
//!
//! ```toml
//! [lib]
//! name = "main"
//! crate-type = ["cdylib"]
//! ```
//!
//! All Android applications are based on an `Activity` subclass, and the
//! `android-activity` crate is designed to support different choices for this base
//! class. Your application _must_ specify the base class it needs via a feature flag:
//!
//! | Base Class | Feature Flag | Notes |
//! | :--------------: | :---------------: | :-----: |
//! | `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
//! | [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
//!
//! [`GameActivity`]: https://developer.android.com/games/agdk/game-activity
//! [`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
//! [`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
//! [agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games
//! [agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
//! [Gradle]: https://developer.android.com/studio/build
//!
//! For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
//!
//! ## Converting from `ndk-glue` to `android-activity`
//!
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.11", features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
//! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
use crate::{ use crate::{
event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget}, event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder},
window::{Window, WindowBuilder}, window::{Window, WindowAttributes},
}; };
use android_activity::{AndroidApp, ConfigurationRef, Rect}; use self::activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android. /// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {} pub trait EventLoopExtAndroid {}
impl<T> EventLoopExtAndroid for EventLoop<T> {} impl<T> EventLoopExtAndroid for EventLoop<T> {}
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Android. /// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait EventLoopWindowTargetExtAndroid {} pub trait ActiveEventLoopExtAndroid {}
/// Additional methods on [`Window`] that are specific to Android. /// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid { pub trait WindowExtAndroid {
@@ -30,12 +96,12 @@ impl WindowExtAndroid for Window {
} }
} }
impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {} impl ActiveEventLoopExtAndroid for ActiveEventLoop {}
/// Additional methods on [`WindowBuilder`] that are specific to Android. /// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowBuilderExtAndroid {} pub trait WindowAttributesExtAndroid {}
impl WindowBuilderExtAndroid for WindowBuilder {} impl WindowAttributesExtAndroid for WindowAttributes {}
pub trait EventLoopBuilderExtAndroid { pub trait EventLoopBuilderExtAndroid {
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop /// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
@@ -84,5 +150,21 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
/// use winit::platform::android::activity::AndroidApp; /// use winit::platform::android::activity::AndroidApp;
/// ``` /// ```
pub mod activity { pub mod activity {
// We enable the `"native-activity"` feature just so that we can build the
// docs, but it'll be very confusing for users to see the docs with that
// feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page.
#[doc(no_inline)]
#[cfg(android_platform)]
pub use android_activity::*; pub use android_activity::*;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct Rect;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct ConfigurationRef;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct AndroidApp;
} }

View File

@@ -1,12 +1,75 @@
use std::os::raw::c_void; //! # 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 icrate::Foundation::MainThreadMarker; use std::os::raw::c_void;
use objc2::rc::Id;
use crate::{ use crate::{
event_loop::EventLoop, event_loop::EventLoop,
monitor::{MonitorHandle, VideoMode}, monitor::{MonitorHandle, VideoModeHandle},
window::{Window, WindowBuilder}, window::{Window, WindowAttributes},
}; };
/// Additional methods on [`EventLoop`] that are specific to iOS. /// Additional methods on [`EventLoop`] that are specific to iOS.
@@ -69,11 +132,40 @@ pub trait WindowExtIOS {
/// ///
/// The default is to prefer showing the status bar. /// The default is to prefer showing the status bar.
/// ///
/// This changes the value returned by /// This sets the value of the
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc), /// [`prefersStatusBarHidden`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc)
/// and then calls /// property.
/// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc). ///
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
/// is also called for you.
fn set_prefers_status_bar_hidden(&self, hidden: bool); fn set_prefers_status_bar_hidden(&self, hidden: bool);
/// Sets the preferred status bar style for the [`Window`].
///
/// The default is system-defined.
///
/// This sets the value of the
/// [`preferredStatusBarStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc)
/// property.
///
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
/// is also called for you.
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle);
/// Sets whether the [`Window`] should recognize pinch gestures.
///
/// The default is to not recognize gestures.
fn recognize_pinch_gesture(&self, should_recognize: bool);
/// Sets whether the [`Window`] should recognize double tap gestures.
///
/// The default is to not recognize gestures.
fn recognize_doubletap_gesture(&self, should_recognize: bool);
/// Sets whether the [`Window`] should recognize rotation gestures.
///
/// The default is to not recognize gestures.
fn recognize_rotation_gesture(&self, should_recognize: bool);
} }
impl WindowExtIOS for Window { impl WindowExtIOS for Window {
@@ -107,10 +199,34 @@ impl WindowExtIOS for Window {
self.window self.window
.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden)) .maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
} }
#[inline]
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
self.window
.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
}
#[inline]
fn recognize_pinch_gesture(&self, should_recognize: bool) {
self.window
.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
}
#[inline]
fn recognize_doubletap_gesture(&self, should_recognize: bool) {
self.window
.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
}
#[inline]
fn recognize_rotation_gesture(&self, should_recognize: bool) {
self.window
.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
}
} }
/// Additional methods on [`WindowBuilder`] that are specific to iOS. /// Additional methods on [`WindowAttributes`] that are specific to iOS.
pub trait WindowBuilderExtIOS { pub trait WindowAttributesExtIOS {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
/// ///
/// The default value is device dependent, and it's recommended GLES or Metal applications set /// The default value is device dependent, and it's recommended GLES or Metal applications set
@@ -154,9 +270,17 @@ pub trait WindowBuilderExtIOS {
/// This sets the initial value returned by /// This sets the initial value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc). /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self; fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self;
/// Sets the style of the [`Window`]'s status bar.
///
/// The default is system-defined.
///
/// This sets the initial value returned by
/// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc),
fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
} }
impl WindowBuilderExtIOS for WindowBuilder { impl WindowAttributesExtIOS for WindowAttributes {
#[inline] #[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> Self { fn with_scale_factor(mut self, scale_factor: f64) -> Self {
self.platform_specific.scale_factor = Some(scale_factor); self.platform_specific.scale_factor = Some(scale_factor);
@@ -187,6 +311,12 @@ impl WindowBuilderExtIOS for WindowBuilder {
self.platform_specific.prefers_status_bar_hidden = hidden; self.platform_specific.prefers_status_bar_hidden = hidden;
self self
} }
#[inline]
fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
self.platform_specific.preferred_status_bar_style = status_bar_style;
self
}
} }
/// Additional methods on [`MonitorHandle`] that are specific to iOS. /// Additional methods on [`MonitorHandle`] that are specific to iOS.
@@ -196,23 +326,23 @@ pub trait MonitorHandleExtIOS {
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc /// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
fn ui_screen(&self) -> *mut c_void; fn ui_screen(&self) -> *mut c_void;
/// Returns the preferred [`VideoMode`] for this monitor. /// Returns the preferred [`VideoModeHandle`] for this monitor.
/// ///
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc). /// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
fn preferred_video_mode(&self) -> VideoMode; fn preferred_video_mode(&self) -> VideoModeHandle;
} }
impl MonitorHandleExtIOS for MonitorHandle { impl MonitorHandleExtIOS for MonitorHandle {
#[inline] #[inline]
fn ui_screen(&self) -> *mut c_void { fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen // SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { MainThreadMarker::new_unchecked() }; let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() };
Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void objc2::rc::Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
} }
#[inline] #[inline]
fn preferred_video_mode(&self) -> VideoMode { fn preferred_video_mode(&self) -> VideoModeHandle {
VideoMode { VideoModeHandle {
video_mode: self.inner.preferred_video_mode(), video_mode: self.inner.preferred_video_mode(),
} }
} }
@@ -249,7 +379,7 @@ pub enum Idiom {
CarPlay, CarPlay,
} }
bitflags! { bitflags::bitflags! {
/// The [edges] of a screen. /// The [edges] of a screen.
/// ///
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc /// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
@@ -264,3 +394,11 @@ bitflags! {
| ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits(); | ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits();
} }
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum StatusBarStyle {
#[default]
Default,
LightContent,
DarkContent,
}

View File

@@ -1,11 +1,28 @@
//! # macOS / AppKit
//!
//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
//! itself), and is regularly tested on macOS 10.14.
//!
//! A lot of functionality expects the application to be ready before you
//! start doing anything; this includes creating windows, fetching monitors,
//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
//!
//! If you encounter problems, you should try doing your initialization inside
//! `Event::Resumed`.
//!
//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
use std::os::raw::c_void; use std::os::raw::c_void;
use objc2::rc::Id; #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget}, event_loop::{ActiveEventLoop, EventLoopBuilder},
monitor::MonitorHandle, monitor::MonitorHandle,
window::{Window, WindowBuilder}, window::{Window, WindowAttributes},
}; };
/// Additional methods on [`Window`] that are specific to MacOS. /// Additional methods on [`Window`] that are specific to MacOS.
@@ -173,15 +190,15 @@ pub enum ActivationPolicy {
Prohibited, Prohibited,
} }
/// Additional methods on [`WindowBuilder`] that are specific to MacOS. /// Additional methods on [`WindowAttributes`] that are specific to MacOS.
/// ///
/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method: /// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowAttributes::with_decorations`] method:
/// - `with_titlebar_transparent` /// - `with_titlebar_transparent`
/// - `with_title_hidden` /// - `with_title_hidden`
/// - `with_titlebar_hidden` /// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden` /// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view` /// - `with_fullsize_content_view`
pub trait WindowBuilderExtMacOS { pub trait WindowAttributesExtMacOS {
/// Enables click-and-drag behavior for the entire window, not just the titlebar. /// Enables click-and-drag behavior for the entire window, not just the titlebar.
fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self; fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self;
/// Makes the titlebar transparent and allows the content to appear behind it. /// Makes the titlebar transparent and allows the content to appear behind it.
@@ -208,7 +225,7 @@ pub trait WindowBuilderExtMacOS {
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self; fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
} }
impl WindowBuilderExtMacOS for WindowBuilder { impl WindowAttributesExtMacOS for WindowAttributes {
#[inline] #[inline]
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self { fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.platform_specific.movable_by_window_background = movable_by_window_background; self.platform_specific.movable_by_window_background = movable_by_window_background;
@@ -365,12 +382,16 @@ impl MonitorHandleExtMacOS for MonitorHandle {
} }
fn ns_screen(&self) -> Option<*mut c_void> { fn ns_screen(&self) -> Option<*mut c_void> {
self.inner.ns_screen().map(|s| Id::as_ptr(&s) as _) // 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 _)
} }
} }
/// Additional methods on [`EventLoopWindowTarget`] that are specific to macOS. /// Additional methods on [`ActiveEventLoop`] that are specific to macOS.
pub trait EventLoopWindowTargetExtMacOS { pub trait ActiveEventLoopExtMacOS {
/// Hide the entire application. In most applications this is typically triggered with Command-H. /// Hide the entire application. In most applications this is typically triggered with Command-H.
fn hide_application(&self); fn hide_application(&self);
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H. /// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
@@ -383,7 +404,7 @@ pub trait EventLoopWindowTargetExtMacOS {
fn allows_automatic_window_tabbing(&self) -> bool; fn allows_automatic_window_tabbing(&self) -> bool;
} }
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> { impl ActiveEventLoopExtMacOS for ActiveEventLoop {
fn hide_application(&self) { fn hide_application(&self) {
self.p.hide_application() self.p.hide_application()
} }

View File

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

View File

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

View File

@@ -1 +1,6 @@
//! # 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. // There are no Orbital specific traits yet.

View File

@@ -2,7 +2,7 @@ use std::time::Duration;
use crate::{ use crate::{
event::Event, event::Event,
event_loop::{EventLoop, EventLoopWindowTarget}, event_loop::{ActiveEventLoop, EventLoop},
}; };
/// The return status for `pump_events` /// The return status for `pump_events`
@@ -32,68 +32,6 @@ pub trait EventLoopExtPumpEvents {
/// Passing a `timeout` of `None` means that it may wait indefinitely for new /// Passing a `timeout` of `None` means that it may wait indefinitely for new
/// events before returning control back to the external loop. /// 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 /// **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! /// caveats and trade offs that should be considered before using this API!
/// ///
@@ -137,12 +75,14 @@ pub trait EventLoopExtPumpEvents {
/// other lifecycle events occur while the event is buffered. /// other lifecycle events occur while the event is buffered.
/// ///
/// ## Supported Platforms /// ## Supported Platforms
///
/// - Windows /// - Windows
/// - Linux /// - Linux
/// - MacOS /// - MacOS
/// - Android /// - Android
/// ///
/// ## Unsupported Platforms /// ## Unsupported Platforms
///
/// - **Web:** This API is fundamentally incompatible with the event-based way in which /// - **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 /// 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 /// loop that would block the browser and there is nothing that can be
@@ -152,29 +92,30 @@ pub trait EventLoopExtPumpEvents {
/// there's no way to support the same approach to polling as on MacOS. /// there's no way to support the same approach to polling as on MacOS.
/// ///
/// ## Platform-specific /// ## Platform-specific
///
/// - **Windows**: The implementation will use `PeekMessage` when checking for /// - **Windows**: The implementation will use `PeekMessage` when checking for
/// window messages to avoid blocking your external event loop. /// window messages to avoid blocking your external event loop.
/// ///
/// - **MacOS**: The implementation works in terms of stopping the global `NSApp` /// - **MacOS**: The implementation works in terms of stopping the global application
/// whenever the application `RunLoop` indicates that it is preparing to block /// whenever the application `RunLoop` indicates that it is preparing to block
/// and wait for new events. /// and wait for new events.
/// ///
/// This is very different to the polling APIs that are available on other /// This is very different to the polling APIs that are available on other
/// platforms (the lower level polling primitives on MacOS are private /// platforms (the lower level polling primitives on MacOS are private
/// implementation details for `NSApp` which aren't accessible to application /// implementation details for `NSApplication` which aren't accessible to
/// developers) /// application developers)
/// ///
/// It's likely this will be less efficient than polling on other OSs and /// It's likely this will be less efficient than polling on other OSs and
/// it also means the `NSApp` is stopped while outside of the Winit /// it also means the `NSApplication` is stopped while outside of the Winit
/// event loop - and that's observable (for example to crates like `rfd`) /// event loop - and that's observable (for example to crates like `rfd`)
/// because the `NSApp` is global state. /// because the `NSApplication` is global state.
/// ///
/// If you render outside of Winit you are likely to see window resizing artifacts /// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect` /// since MacOS expects applications to render synchronously during any `drawRect`
/// callback. /// callback.
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>); F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
} }
impl<T> EventLoopExtPumpEvents for EventLoop<T> { impl<T> EventLoopExtPumpEvents for EventLoop<T> {
@@ -182,7 +123,7 @@ impl<T> EventLoopExtPumpEvents for EventLoop<T> {
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>), F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{ {
self.event_loop.pump_events(timeout, event_handler) self.event_loop.pump_events(timeout, event_handler)
} }

View File

@@ -1,7 +1,7 @@
use crate::{ use crate::{
error::EventLoopError, error::EventLoopError,
event::Event, event::Event,
event_loop::{EventLoop, EventLoopWindowTarget}, event_loop::{ActiveEventLoop, EventLoop},
}; };
#[cfg(doc)] #[cfg(doc)]
@@ -35,9 +35,9 @@ pub trait EventLoopExtRunOnDemand {
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
/// ///
/// # Caveats /// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to /// - This extension isn't available on all platforms, since it's not always possible to return
/// return to the caller (specifically this is impossible on iOS and Web - though with /// to the caller (specifically this is impossible on iOS and Web - though with the Web
/// the Web backend it is possible to use `spawn()` more than once instead). /// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop. /// - No [`Window`] state can be carried between separate runs of the event loop.
/// ///
/// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you specifically need /// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you specifically need
@@ -55,13 +55,18 @@ pub trait EventLoopExtRunOnDemand {
/// loop that would block the browser and there is nothing that can be /// loop that would block the browser and there is nothing that can be
/// polled to ask for new events. Events are delivered via callbacks based /// polled to ask for new events. Events are delivered via callbacks based
/// on an event loop that is internal to the browser itself. /// on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS. /// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
/// ///
/// [`exit()`]: EventLoopWindowTarget::exit #[cfg_attr(
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow not(web_platform),
doc = "[^1]: `spawn()` is only available on `wasm` platforms."
)]
///
/// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError> fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>); F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
} }
impl<T> EventLoopExtRunOnDemand for EventLoop<T> { impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
@@ -69,8 +74,29 @@ impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError> fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>), F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{ {
self.event_loop.window_target().clear_exit();
self.event_loop.run_on_demand(event_handler) 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,30 +1,50 @@
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))] use crate::keyboard::{KeyCode, PhysicalKey};
use crate::keyboard::KeyCode;
// TODO: Describe what this value contains for each platform // TODO: Describe what this value contains for each platform
/// Additional methods for the [`KeyCode`] type that allow the user to access the platform-specific /// Additional methods for the [`PhysicalKey`] type that allow the user to access the platform-specific
/// scancode. /// scancode.
/// ///
/// [`KeyCode`]: crate::keyboard::KeyCode /// [`PhysicalKey`]: crate::keyboard::PhysicalKey
pub trait KeyCodeExtScancode { pub trait PhysicalKeyExtScancode {
/// The raw value of the platform-specific physical key identifier. /// The raw value of the platform-specific physical key identifier.
/// ///
/// Returns `Some(key_id)` if the conversion was succesful; returns `None` otherwise. /// Returns `Some(key_id)` if the conversion was successful; returns `None` otherwise.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// - **Windows:** A 16bit extended scancode /// - **Windows:** A 16bit extended scancode
/// - **Wayland/X11**: A 32-bit linux scancode, which is X11/Wayland keycode subtracted by 8. /// - **Wayland/X11**: A 32-bit linux scancode, which is X11/Wayland keycode subtracted by 8.
fn to_scancode(self) -> Option<u32>; fn to_scancode(self) -> Option<u32>;
/// Constructs a `KeyCode` from a platform-specific physical key identifier. /// Constructs a `PhysicalKey` from a platform-specific physical key identifier.
/// ///
/// Note that this conversion may be lossy, i.e. converting the returned `KeyCode` back /// Note that this conversion may be lossy, i.e. converting the returned `PhysicalKey` back
/// using `to_scancode` might not yield the original value. /// using `to_scancode` might not yield the original value.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// - **Wayland/X11**: A 32-bit linux scancode. When building from X11/Wayland keycode subtract /// - **Wayland/X11**: A 32-bit linux scancode. When building from X11/Wayland keycode subtract
/// `8` to get the value you wanted. /// `8` to get the value you wanted.
fn from_scancode(scancode: u32) -> KeyCode; 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))
}
#[inline]
fn from_scancode(scancode: u32) -> PhysicalKey {
<PhysicalKey as PhysicalKeyExtScancode>::from_scancode(scancode)
}
} }

View File

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

View File

@@ -1,20 +1,33 @@
//! # 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::{ use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget}, event_loop::{ActiveEventLoop, EventLoopBuilder},
monitor::MonitorHandle, monitor::MonitorHandle,
window::{Window, WindowBuilder}, window::{Window, WindowAttributes},
}; };
use crate::platform_impl::{ApplicationName, Backend};
pub use crate::window::Theme; pub use crate::window::Theme;
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Wayland. /// Additional methods on [`ActiveEventLoop`] that are specific to Wayland.
pub trait EventLoopWindowTargetExtWayland { pub trait ActiveEventLoopExtWayland {
/// True if the [`EventLoopWindowTarget`] uses Wayland. /// True if the [`ActiveEventLoop`] uses Wayland.
fn is_wayland(&self) -> bool; fn is_wayland(&self) -> bool;
} }
impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> { impl ActiveEventLoopExtWayland for ActiveEventLoop {
#[inline] #[inline]
fn is_wayland(&self) -> bool { fn is_wayland(&self) -> bool {
self.p.is_wayland() self.p.is_wayland()
@@ -36,7 +49,7 @@ pub trait EventLoopBuilderExtWayland {
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> { impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
#[inline] #[inline]
fn with_wayland(&mut self) -> &mut Self { fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::Wayland); self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland);
self self
} }
@@ -52,22 +65,25 @@ pub trait WindowExtWayland {}
impl WindowExtWayland for Window {} impl WindowExtWayland for Window {}
/// Additional methods on [`WindowBuilder`] that are specific to Wayland. /// Additional methods on [`WindowAttributes`] that are specific to Wayland.
pub trait WindowBuilderExtWayland { pub trait WindowAttributesExtWayland {
/// Build window with the given name. /// Build window with the given name.
/// ///
/// The `general` name sets an application ID, which should match the `.desktop` /// The `general` name sets an application ID, which should match the `.desktop`
/// file destributed with your program. The `instance` is a `no-op`. /// file distributed with your program. The `instance` is a `no-op`.
/// ///
/// For details about application ID conventions, see the /// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id) /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self; fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
} }
impl WindowBuilderExtWayland for WindowBuilder { impl WindowAttributesExtWayland for WindowAttributes {
#[inline] #[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self { fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into())); self.platform_specific.name = Some(crate::platform_impl::ApplicationName::new(
general.into(),
instance.into(),
));
self self
} }
} }

View File

@@ -1,12 +1,31 @@
//! The web target does not automatically insert the canvas element object into the web page, to //! # Web
//! 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 //! The officially supported browsers are Chrome, Firefox and Safari 13.1+,
//! to provide your own canvas. //! 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
//! //!
//! It is recommended **not** to apply certain CSS properties to the canvas: //! It is recommended **not** to apply certain CSS properties to the canvas:
//! - [`transform`] //! - [`transform`](https://developer.mozilla.org/en-US/docs/Web/CSS/transform)
//! - [`border`] //! - [`border`](https://developer.mozilla.org/en-US/docs/Web/CSS/border)
//! - [`padding`] //! - [`padding`](https://developer.mozilla.org/en-US/docs/Web/CSS/padding)
//! //!
//! The following APIs can't take them into account and will therefore provide inaccurate results: //! The following APIs can't take them into account and will therefore provide inaccurate results:
//! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`] //! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`]
@@ -16,27 +35,55 @@
//! - [`Window::set_outer_position()`] //! - [`Window::set_outer_position()`]
//! //!
//! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized //! [`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::Occluded`]: crate::event::WindowEvent::Occluded
//! [`WindowEvent::CursorMoved`]: crate::event::WindowEvent::CursorMoved //! [`WindowEvent::CursorMoved`]: crate::event::WindowEvent::CursorMoved
//! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered //! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered
//! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft //! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft
//! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch //! [`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 crate::event::Event; use std::error::Error;
use crate::event_loop::EventLoop; use std::fmt::{self, Display, Formatter};
use crate::event_loop::EventLoopWindowTarget; use std::future::Future;
use crate::window::{Window, WindowBuilder}; use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
#[cfg(web_platform)]
use web_sys::HtmlCanvasElement; 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 { pub trait WindowExtWebSys {
/// Only returns the canvas if called from inside the window. /// Only returns the canvas if called from inside the window context (the
/// main thread).
fn canvas(&self) -> Option<HtmlCanvasElement>; fn canvas(&self) -> Option<HtmlCanvasElement>;
/// Returns [`true`] if calling `event.preventDefault()` is enabled.
///
/// See [`Window::set_prevent_default()`] for more details.
fn prevent_default(&self) -> bool;
/// Sets whether `event.preventDefault()` should be called on events on the
/// canvas that have side effects.
///
/// For example, by default using the mouse wheel would cause the page to scroll, enabling this
/// would prevent that.
///
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
/// context menu with Shift+Rightclick.
fn set_prevent_default(&self, prevent_default: bool);
} }
impl WindowExtWebSys for Window { impl WindowExtWebSys for Window {
@@ -44,25 +91,34 @@ impl WindowExtWebSys for Window {
fn canvas(&self) -> Option<HtmlCanvasElement> { fn canvas(&self) -> Option<HtmlCanvasElement> {
self.window.canvas() self.window.canvas()
} }
fn prevent_default(&self) -> bool {
self.window.prevent_default()
}
fn set_prevent_default(&self, prevent_default: bool) {
self.window.set_prevent_default(prevent_default)
}
} }
pub trait WindowBuilderExtWebSys { pub trait WindowAttributesExtWebSys {
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`], /// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`],
/// [`WindowBuilder::build()`] will create one. /// [`WindowAttributes::default()`] will create one.
/// ///
/// In any case, the canvas won't be automatically inserted into the web page. /// In any case, the canvas won't be automatically inserted into the web page.
/// ///
/// [`None`] by default. /// [`None`] by default.
#[cfg_attr(
not(web_platform),
doc = "",
doc = "[`HtmlCanvasElement`]: #only-available-on-wasm"
)]
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self; fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
/// Whether `event.preventDefault` should be automatically called to prevent event propagation /// Sets whether `event.preventDefault()` should be called on events on the
/// when appropriate. /// canvas that have side effects.
/// ///
/// For example, mouse wheel events are only handled by the canvas by default. This avoids /// See [`Window::set_prevent_default()`] for more details.
/// the default behavior of scrolling the page.
///
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
/// context menu with Shift+Rightclick.
/// ///
/// Enabled by default. /// Enabled by default.
fn with_prevent_default(self, prevent_default: bool) -> Self; fn with_prevent_default(self, prevent_default: bool) -> Self;
@@ -79,28 +135,24 @@ pub trait WindowBuilderExtWebSys {
fn with_append(self, append: bool) -> Self; fn with_append(self, append: bool) -> Self;
} }
impl WindowBuilderExtWebSys for WindowBuilder { impl WindowAttributesExtWebSys for WindowAttributes {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self { fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.canvas = canvas; self.platform_specific.set_canvas(canvas);
self self
} }
fn with_prevent_default(mut self, prevent_default: bool) -> Self { fn with_prevent_default(mut self, prevent_default: bool) -> Self {
self.platform_specific.prevent_default = prevent_default; self.platform_specific.prevent_default = prevent_default;
self self
} }
fn with_focusable(mut self, focusable: bool) -> Self { fn with_focusable(mut self, focusable: bool) -> Self {
self.platform_specific.focusable = focusable; self.platform_specific.focusable = focusable;
self self
} }
fn with_append(mut self, append: bool) -> Self { fn with_append(mut self, append: bool) -> Self {
self.platform_specific.append = append; self.platform_specific.append = append;
self self
} }
} }
@@ -112,16 +164,31 @@ pub trait EventLoopExtWebSys {
/// Initializes the winit event loop. /// Initializes the winit event loop.
/// ///
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to /// Unlike
/// satisfy its `!` return type. #[cfg_attr(
all(web_platform, target_feature = "exception-handling"),
doc = "`run()`"
)]
#[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")),
doc = "[`run()`]"
)]
/// [^1], this returns immediately, and doesn't throw an exception in order to
/// satisfy its [`!`] return type.
/// ///
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop /// Once the event loop has been destroyed, it's possible to reinitialize another event loop
/// by calling this function again. This can be useful if you want to recreate the event loop /// by calling this function again. This can be useful if you want to recreate the event loop
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the /// while the WebAssembly module is still loaded. For example, this can be used to recreate the
/// event loop when switching between tabs on a single page application. /// event loop when switching between tabs on a single page application.
///
#[cfg_attr(
not(all(web_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) fn spawn<F>(self, event_handler: F)
where where
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>); F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
} }
impl<T> EventLoopExtWebSys for EventLoop<T> { impl<T> EventLoopExtWebSys for EventLoop<T> {
@@ -129,13 +196,13 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
fn spawn<F>(self, event_handler: F) fn spawn<F>(self, event_handler: F)
where where
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>), F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{ {
self.event_loop.spawn(event_handler) self.event_loop.spawn(event_handler)
} }
} }
pub trait EventLoopWindowTargetExtWebSys { pub trait ActiveEventLoopExtWebSys {
/// Sets the strategy for [`ControlFlow::Poll`]. /// Sets the strategy for [`ControlFlow::Poll`].
/// ///
/// See [`PollStrategy`]. /// See [`PollStrategy`].
@@ -149,9 +216,18 @@ pub trait EventLoopWindowTargetExtWebSys {
/// ///
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn poll_strategy(&self) -> PollStrategy; 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<T> EventLoopWindowTargetExtWebSys for EventLoopWindowTarget<T> { impl ActiveEventLoopExtWebSys for ActiveEventLoop {
#[inline]
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture {
self.p.create_custom_cursor_async(source)
}
#[inline] #[inline]
fn set_poll_strategy(&self, strategy: PollStrategy) { fn set_poll_strategy(&self, strategy: PollStrategy) {
self.p.set_poll_strategy(strategy); self.p.set_poll_strategy(strategy);
@@ -188,3 +264,111 @@ pub enum PollStrategy {
#[default] #[default]
Scheduler, Scheduler,
} }
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>;
}
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 {
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,14 +1,15 @@
//! # Windows
//!
//! The supported OS version is Windows 7 or higher, though Windows 10 is
//! tested regularly.
use std::{ffi::c_void, path::Path}; use std::{ffi::c_void, path::Path};
use crate::{ use crate::{
dpi::PhysicalSize, dpi::PhysicalSize,
event::{DeviceId, KeyEvent}, event::DeviceId,
event_loop::EventLoopBuilder, event_loop::EventLoopBuilder,
keyboard::Key,
monitor::MonitorHandle, monitor::MonitorHandle,
platform::modifier_supplement::KeyEventExtModifierSupplement, window::{BadIcon, Icon, Window, WindowAttributes},
platform_impl::WinIcon,
window::{BadIcon, Icon, Window, WindowBuilder},
}; };
/// Window Handle type used by Win32 API /// Window Handle type used by Win32 API
@@ -18,6 +19,92 @@ pub type HMENU = isize;
/// Monitor Handle type used by Win32 API /// Monitor Handle type used by Win32 API
pub type HMONITOR = isize; pub type HMONITOR = isize;
/// Describes a system-drawn backdrop material of a window.
///
/// For a detailed explanation, see [`DWM_SYSTEMBACKDROP_TYPE docs`].
///
/// [`DWM_SYSTEMBACKDROP_TYPE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BackdropType {
/// Corresponds to `DWMSBT_AUTO`.
///
/// Usually draws a default backdrop effect on the title bar.
#[default]
Auto = 0,
/// Corresponds to `DWMSBT_NONE`.
None = 1,
/// Corresponds to `DWMSBT_MAINWINDOW`.
///
/// Draws the Mica backdrop material.
MainWindow = 2,
/// Corresponds to `DWMSBT_TRANSIENTWINDOW`.
///
/// Draws the Background Acrylic backdrop material.
TransientWindow = 3,
/// Corresponds to `DWMSBT_TABBEDWINDOW`.
///
/// Draws the Alt Mica backdrop material.
TabbedWindow = 4,
}
/// Describes a color used by Windows
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Color(u32);
impl Color {
/// Use the system's default color
pub const SYSTEM_DEFAULT: Color = Color(0xFFFFFFFF);
//Special constant only valid for the window border and therefore modeled using Option<Color> for user facing code
const NONE: Color = Color(0xFFFFFFFE);
/// Create a new color from the given RGB values
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self((r as u32) | ((g as u32) << 8) | ((b as u32) << 16))
}
}
impl Default for Color {
fn default() -> Self {
Self::SYSTEM_DEFAULT
}
}
/// Describes how the corners of a window should look like.
///
/// For a detailed explanation, see [`DWM_WINDOW_CORNER_PREFERENCE docs`].
///
/// [`DWM_WINDOW_CORNER_PREFERENCE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
#[repr(i32)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CornerPreference {
/// Corresponds to `DWMWCP_DEFAULT`.
///
/// Let the system decide when to round window corners.
#[default]
Default = 0,
/// Corresponds to `DWMWCP_DONOTROUND`.
///
/// Never round window corners.
DoNotRound = 1,
/// Corresponds to `DWMWCP_ROUND`.
///
/// Round the corners, if appropriate.
Round = 2,
/// Corresponds to `DWMWCP_ROUNDSMALL`.
///
/// Round the corners if appropriate, with a small radius.
RoundSmall = 3,
}
/// Additional methods on `EventLoop` that are specific to Windows. /// Additional methods on `EventLoop` that are specific to Windows.
pub trait EventLoopBuilderExtWindows { pub trait EventLoopBuilderExtWindows {
/// Whether to allow the event loop to be created off of the main thread. /// Whether to allow the event loop to be created off of the main thread.
@@ -115,7 +202,7 @@ pub trait WindowExtWindows {
/// ///
/// A window must be enabled before it can be activated. /// A window must be enabled before it can be activated.
/// If an application has create a modal dialog box by disabling its owner window /// If an application has create a modal dialog box by disabling its owner window
/// (as described in [`WindowBuilderExtWindows::with_owner_window`]), the application must enable /// (as described in [`WindowAttributesExtWindows::with_owner_window`]), the application must enable
/// the owner window before destroying the dialog box. /// the owner window before destroying the dialog box.
/// Otherwise, another window will receive the keyboard focus and be activated. /// Otherwise, another window will receive the keyboard focus and be activated.
/// ///
@@ -136,6 +223,31 @@ pub trait WindowExtWindows {
/// ///
/// Enabling the shadow causes a thin 1px line to appear on the top of the window. /// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn set_undecorated_shadow(&self, shadow: bool); fn set_undecorated_shadow(&self, shadow: bool);
/// Sets system-drawn backdrop type.
///
/// Requires Windows 11 build 22523+.
fn set_system_backdrop(&self, backdrop_type: BackdropType);
/// Sets the color of the window border.
///
/// Supported starting with Windows 11 Build 22000.
fn set_border_color(&self, color: Option<Color>);
/// Sets the background color of the title bar.
///
/// Supported starting with Windows 11 Build 22000.
fn set_title_background_color(&self, color: Option<Color>);
/// Sets the color of the window title.
///
/// Supported starting with Windows 11 Build 22000.
fn set_title_text_color(&self, color: Color);
/// Sets the preferred style of the window corners.
///
/// Supported starting with Windows 11 Build 22000.
fn set_corner_preference(&self, preference: CornerPreference);
} }
impl WindowExtWindows for Window { impl WindowExtWindows for Window {
@@ -158,13 +270,41 @@ impl WindowExtWindows for Window {
fn set_undecorated_shadow(&self, shadow: bool) { fn set_undecorated_shadow(&self, shadow: bool) {
self.window.set_undecorated_shadow(shadow) self.window.set_undecorated_shadow(shadow)
} }
#[inline]
fn set_system_backdrop(&self, backdrop_type: BackdropType) {
self.window.set_system_backdrop(backdrop_type)
}
#[inline]
fn set_border_color(&self, color: Option<Color>) {
self.window.set_border_color(color.unwrap_or(Color::NONE))
}
#[inline]
fn set_title_background_color(&self, color: Option<Color>) {
// The windows docs don't mention NONE as a valid options but it works in practice and is useful
// to circumvent the Windows option "Show accent color on title bars and window borders"
self.window
.set_title_background_color(color.unwrap_or(Color::NONE))
}
#[inline]
fn set_title_text_color(&self, color: Color) {
self.window.set_title_text_color(color)
}
#[inline]
fn set_corner_preference(&self, preference: CornerPreference) {
self.window.set_corner_preference(preference)
}
} }
/// Additional methods on `WindowBuilder` that are specific to Windows. /// Additional methods on `WindowAttributes` that are specific to Windows.
#[allow(rustdoc::broken_intra_doc_links)] #[allow(rustdoc::broken_intra_doc_links)]
pub trait WindowBuilderExtWindows { pub trait WindowAttributesExtWindows {
/// Set an owner to the window to be created. Can be used to create a dialog box, for example. /// Set an owner to the window to be created. Can be used to create a dialog box, for example.
/// This only works when [`WindowBuilder::with_parent_window`] isn't called or set to `None`. /// This only works when [`WindowAttributes::with_parent_window`] isn't called or set to `None`.
/// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable) /// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable)
/// on the owner window to create a modal dialog box. /// on the owner window to create a modal dialog box.
/// ///
@@ -185,7 +325,14 @@ pub trait WindowBuilderExtWindows {
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look. /// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect. /// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
/// ///
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu #[cfg_attr(
platform_windows,
doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
)]
#[cfg_attr(
not(platform_windows),
doc = "[`CreateMenu`]: #only-available-on-windows"
)]
fn with_menu(self, menu: HMENU) -> Self; fn with_menu(self, menu: HMENU) -> Self;
/// This sets `ICON_BIG`. A good ceiling here is 256x256. /// This sets `ICON_BIG`. A good ceiling here is 256x256.
@@ -213,9 +360,37 @@ pub trait WindowBuilderExtWindows {
/// The shadow is hidden by default. /// The shadow is hidden by default.
/// Enabling the shadow causes a thin 1px line to appear on the top of the window. /// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn with_undecorated_shadow(self, shadow: bool) -> Self; fn with_undecorated_shadow(self, shadow: bool) -> Self;
/// Sets system-drawn backdrop type.
///
/// Requires Windows 11 build 22523+.
fn with_system_backdrop(self, backdrop_type: BackdropType) -> Self;
/// This sets or removes `WS_CLIPCHILDREN` style.
fn with_clip_children(self, flag: bool) -> Self;
/// Sets the color of the window border.
///
/// Supported starting with Windows 11 Build 22000.
fn with_border_color(self, color: Option<Color>) -> Self;
/// Sets the background color of the title bar.
///
/// Supported starting with Windows 11 Build 22000.
fn with_title_background_color(self, color: Option<Color>) -> Self;
/// Sets the color of the window title.
///
/// Supported starting with Windows 11 Build 22000.
fn with_title_text_color(self, color: Color) -> Self;
/// Sets the preferred style of the window corners.
///
/// Supported starting with Windows 11 Build 22000.
fn with_corner_preference(self, corners: CornerPreference) -> Self;
} }
impl WindowBuilderExtWindows for WindowBuilder { impl WindowAttributesExtWindows for WindowAttributes {
#[inline] #[inline]
fn with_owner_window(mut self, parent: HWND) -> Self { fn with_owner_window(mut self, parent: HWND) -> Self {
self.platform_specific.owner = Some(parent); self.platform_specific.owner = Some(parent);
@@ -263,6 +438,42 @@ impl WindowBuilderExtWindows for WindowBuilder {
self.platform_specific.decoration_shadow = shadow; self.platform_specific.decoration_shadow = shadow;
self 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. /// Additional methods on `MonitorHandle` that are specific to Windows.
@@ -328,27 +539,12 @@ impl IconExtWindows for Icon {
path: P, path: P,
size: Option<PhysicalSize<u32>>, size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> { ) -> Result<Self, BadIcon> {
let win_icon = WinIcon::from_path(path, size)?; let win_icon = crate::platform_impl::WinIcon::from_path(path, size)?;
Ok(Icon { inner: win_icon }) Ok(Icon { inner: win_icon })
} }
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> { fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = WinIcon::from_resource(ordinal, size)?; let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon }) Ok(Icon { inner: win_icon })
} }
} }
impl KeyEventExtModifierSupplement for KeyEvent {
#[inline]
fn text_with_all_modifiers(&self) -> Option<&str> {
self.platform_specific
.text_with_all_modifers
.as_ref()
.map(|s| s.as_str())
}
#[inline]
fn key_without_modifiers(&self) -> Key {
self.platform_specific.key_without_modifiers.clone()
}
}

View File

@@ -1,13 +1,58 @@
//! # X11
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget}, event_loop::{ActiveEventLoop, EventLoopBuilder},
monitor::MonitorHandle, monitor::MonitorHandle,
window::{Window, WindowBuilder}, window::{Window, WindowAttributes},
}; };
use crate::dpi::Size; use crate::dpi::Size;
use crate::platform_impl::{ApplicationName, Backend, XLIB_ERROR_HOOKS};
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported}; /// 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,
}
/// The first argument in the provided hook will be the pointer to `XDisplay` /// The first argument in the provided hook will be the pointer to `XDisplay`
/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an /// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
@@ -17,7 +62,7 @@ pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupport
pub type XlibErrorHook = pub type XlibErrorHook =
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>; Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
/// A unique identifer for an X11 visual. /// A unique identifier for an X11 visual.
pub type XVisualID = u32; pub type XVisualID = u32;
/// A unique identifier for an X11 window. /// A unique identifier for an X11 window.
@@ -25,7 +70,7 @@ pub type XWindow = u32;
/// Hook to winit's xlib error handling callback. /// Hook to winit's xlib error handling callback.
/// ///
/// This method is provided as a safe way to handle the errors comming from X11 /// This method is provided as a safe way to handle the errors coming from X11
/// when using xlib in external crates, like glutin for GLX access. Trying to /// when using xlib in external crates, like glutin for GLX access. Trying to
/// handle errors by speculating with `XSetErrorHandler` is [`unsafe`]. /// handle errors by speculating with `XSetErrorHandler` is [`unsafe`].
/// ///
@@ -38,17 +83,20 @@ pub type XWindow = u32;
pub fn register_xlib_error_hook(hook: XlibErrorHook) { pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook. // Append new hook.
unsafe { unsafe {
XLIB_ERROR_HOOKS.lock().unwrap().push(hook); crate::platform_impl::XLIB_ERROR_HOOKS
.lock()
.unwrap()
.push(hook);
} }
} }
/// Additional methods on [`EventLoopWindowTarget`] that are specific to X11. /// Additional methods on [`ActiveEventLoop`] that are specific to X11.
pub trait EventLoopWindowTargetExtX11 { pub trait ActiveEventLoopExtX11 {
/// True if the [`EventLoopWindowTarget`] uses X11. /// True if the [`ActiveEventLoop`] uses X11.
fn is_x11(&self) -> bool; fn is_x11(&self) -> bool;
} }
impl<T> EventLoopWindowTargetExtX11 for EventLoopWindowTarget<T> { impl ActiveEventLoopExtX11 for ActiveEventLoop {
#[inline] #[inline]
fn is_x11(&self) -> bool { fn is_x11(&self) -> bool {
!self.p.is_wayland() !self.p.is_wayland()
@@ -70,7 +118,7 @@ pub trait EventLoopBuilderExtX11 {
impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> { impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
#[inline] #[inline]
fn with_x11(&mut self) -> &mut Self { fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::X); self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X);
self self
} }
@@ -86,8 +134,8 @@ pub trait WindowExtX11 {}
impl WindowExtX11 for Window {} impl WindowExtX11 for Window {}
/// Additional methods on [`WindowBuilder`] that are specific to X11. /// Additional methods on [`WindowAttributes`] that are specific to X11.
pub trait WindowBuilderExtX11 { pub trait WindowAttributesExtX11 {
/// Create this window with a specific X11 visual. /// Create this window with a specific X11 visual.
fn with_x11_visual(self, visual_id: XVisualID) -> Self; fn with_x11_visual(self, visual_id: XVisualID) -> Self;
@@ -96,29 +144,29 @@ pub trait WindowBuilderExtX11 {
/// Build window with the given `general` and `instance` names. /// Build window with the given `general` and `instance` names.
/// ///
/// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the /// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`. /// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "instance", "general"`.
/// ///
/// For details about application ID conventions, see the /// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id) /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self; fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11. /// Build window with override-redirect flag; defaults to false.
fn with_override_redirect(self, override_redirect: bool) -> Self; fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11. /// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`.
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self; fn with_x11_window_type(self, x11_window_type: Vec<WindowType>) -> Self;
/// Build window with base size hint. Only implemented on X11. /// Build window with base size hint.
/// ///
/// ``` /// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize}; /// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder; /// # use winit::window::Window;
/// # use winit::platform::x11::WindowBuilderExtX11; /// # use winit::platform::x11::WindowAttributesExtX11;
/// // Specify the size in logical dimensions like this: /// // Specify the size in logical dimensions like this:
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0)); /// Window::default_attributes().with_base_size(LogicalSize::new(400.0, 200.0));
/// ///
/// // Or specify the size in physical dimensions like this: /// // Or specify the size in physical dimensions like this:
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200)); /// Window::default_attributes().with_base_size(PhysicalSize::new(400, 200));
/// ``` /// ```
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self; fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
@@ -127,20 +175,19 @@ pub trait WindowBuilderExtX11 {
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// use winit::window::WindowBuilder; /// use winit::window::Window;
/// use winit::platform::x11::{XWindow, WindowBuilderExtX11}; /// use winit::event_loop::ActiveEventLoop;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// use winit::platform::x11::{XWindow, WindowAttributesExtX11};
/// let event_loop = winit::event_loop::EventLoop::new().unwrap(); /// # fn create_window(event_loop: &ActiveEventLoop) -> Result<(), Box<dyn std::error::Error>> {
/// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?; /// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?;
/// let window = WindowBuilder::new() /// let window_attributes = Window::default_attributes().with_embed_parent_window(parent_window_id);
/// .with_embed_parent_window(parent_window_id) /// let window = event_loop.create_window(window_attributes)?;
/// .build(&event_loop)?;
/// # Ok(()) } /// # Ok(()) }
/// ``` /// ```
fn with_embed_parent_window(self, parent_window_id: XWindow) -> Self; fn with_embed_parent_window(self, parent_window_id: XWindow) -> Self;
} }
impl WindowBuilderExtX11 for WindowBuilder { impl WindowAttributesExtX11 for WindowAttributes {
#[inline] #[inline]
fn with_x11_visual(mut self, visual_id: XVisualID) -> Self { fn with_x11_visual(mut self, visual_id: XVisualID) -> Self {
self.platform_specific.x11.visual_id = Some(visual_id); self.platform_specific.x11.visual_id = Some(visual_id);
@@ -155,7 +202,10 @@ impl WindowBuilderExtX11 for WindowBuilder {
#[inline] #[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self { fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into())); self.platform_specific.name = Some(crate::platform_impl::ApplicationName::new(
general.into(),
instance.into(),
));
self self
} }
@@ -166,7 +216,7 @@ impl WindowBuilderExtX11 for WindowBuilder {
} }
#[inline] #[inline]
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self { fn with_x11_window_type(mut self, x11_window_types: Vec<WindowType>) -> Self {
self.platform_specific.x11.x11_window_types = x11_window_types; self.platform_specific.x11.x11_window_types = x11_window_types;
self self
} }

View File

@@ -3,10 +3,10 @@ use android_activity::{
AndroidApp, AndroidApp,
}; };
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode}; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
pub fn to_physical_keycode(keycode: Keycode) -> KeyCode { pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
match keycode { PhysicalKey::Code(match keycode {
Keycode::A => KeyCode::KeyA, Keycode::A => KeyCode::KeyA,
Keycode::B => KeyCode::KeyB, Keycode::B => KeyCode::KeyB,
Keycode::C => KeyCode::KeyC, Keycode::C => KeyCode::KeyC,
@@ -155,8 +155,8 @@ pub fn to_physical_keycode(keycode: Keycode) -> KeyCode {
Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep? Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep?
Keycode::Wakeup => KeyCode::WakeUp, Keycode::Wakeup => KeyCode::WakeUp,
keycode => KeyCode::Unidentified(NativeKeyCode::Android(keycode.into())), keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())),
} })
} }
/// Tries to map the `key_event` to a `KeyMapChar` containing a unicode character or dead key accent /// Tries to map the `key_event` to a `KeyMapChar` containing a unicode character or dead key accent
@@ -231,10 +231,10 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
None | Some(KeyMapChar::None) => match keycode { None | Some(KeyMapChar::None) => match keycode {
// Using `BrowserHome` instead of `GoHome` according to // Using `BrowserHome` instead of `GoHome` according to
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
Home => Key::BrowserHome, Home => Key::Named(NamedKey::BrowserHome),
Back => Key::BrowserBack, Back => Key::Named(NamedKey::BrowserBack),
Call => Key::Call, Call => Key::Named(NamedKey::Call),
Endcall => Key::EndCall, Endcall => Key::Named(NamedKey::EndCall),
//------------------------------------------------------------------------------- //-------------------------------------------------------------------------------
// These should be redundant because they should have already been matched // These should be redundant because they should have already been matched
@@ -291,81 +291,81 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
At => Key::Character("@".into()), At => Key::Character("@".into()),
Plus => Key::Character("+".into()), Plus => Key::Character("+".into()),
//------------------------------------------------------------------------------- //-------------------------------------------------------------------------------
DpadUp => Key::ArrowUp, DpadUp => Key::Named(NamedKey::ArrowUp),
DpadDown => Key::ArrowDown, DpadDown => Key::Named(NamedKey::ArrowDown),
DpadLeft => Key::ArrowLeft, DpadLeft => Key::Named(NamedKey::ArrowLeft),
DpadRight => Key::ArrowRight, DpadRight => Key::Named(NamedKey::ArrowRight),
DpadCenter => Key::Enter, DpadCenter => Key::Named(NamedKey::Enter),
VolumeUp => Key::AudioVolumeUp, VolumeUp => Key::Named(NamedKey::AudioVolumeUp),
VolumeDown => Key::AudioVolumeDown, VolumeDown => Key::Named(NamedKey::AudioVolumeDown),
Power => Key::Power, Power => Key::Named(NamedKey::Power),
Camera => Key::Camera, Camera => Key::Named(NamedKey::Camera),
Clear => Key::Clear, Clear => Key::Named(NamedKey::Clear),
AltLeft => Key::Alt, AltLeft => Key::Named(NamedKey::Alt),
AltRight => Key::Alt, AltRight => Key::Named(NamedKey::Alt),
ShiftLeft => Key::Shift, ShiftLeft => Key::Named(NamedKey::Shift),
ShiftRight => Key::Shift, ShiftRight => Key::Named(NamedKey::Shift),
Tab => Key::Tab, Tab => Key::Named(NamedKey::Tab),
Space => Key::Space, Space => Key::Named(NamedKey::Space),
Sym => Key::Symbol, Sym => Key::Named(NamedKey::Symbol),
Explorer => Key::LaunchWebBrowser, Explorer => Key::Named(NamedKey::LaunchWebBrowser),
Envelope => Key::LaunchMail, Envelope => Key::Named(NamedKey::LaunchMail),
Enter => Key::Enter, Enter => Key::Named(NamedKey::Enter),
Del => Key::Backspace, Del => Key::Named(NamedKey::Backspace),
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
Num => Key::Alt, Num => Key::Named(NamedKey::Alt),
Headsethook => Key::HeadsetHook, Headsethook => Key::Named(NamedKey::HeadsetHook),
Focus => Key::CameraFocus, Focus => Key::Named(NamedKey::CameraFocus),
Notification => Key::Notification, Notification => Key::Named(NamedKey::Notification),
Search => Key::BrowserSearch, Search => Key::Named(NamedKey::BrowserSearch),
MediaPlayPause => Key::MediaPlayPause, MediaPlayPause => Key::Named(NamedKey::MediaPlayPause),
MediaStop => Key::MediaStop, MediaStop => Key::Named(NamedKey::MediaStop),
MediaNext => Key::MediaTrackNext, MediaNext => Key::Named(NamedKey::MediaTrackNext),
MediaPrevious => Key::MediaTrackPrevious, MediaPrevious => Key::Named(NamedKey::MediaTrackPrevious),
MediaRewind => Key::MediaRewind, MediaRewind => Key::Named(NamedKey::MediaRewind),
MediaFastForward => Key::MediaFastForward, MediaFastForward => Key::Named(NamedKey::MediaFastForward),
Mute => Key::MicrophoneVolumeMute, Mute => Key::Named(NamedKey::MicrophoneVolumeMute),
PageUp => Key::PageUp, PageUp => Key::Named(NamedKey::PageUp),
PageDown => Key::PageDown, PageDown => Key::Named(NamedKey::PageDown),
Escape => Key::Escape, Escape => Key::Named(NamedKey::Escape),
ForwardDel => Key::Delete, ForwardDel => Key::Named(NamedKey::Delete),
CtrlLeft => Key::Control, CtrlLeft => Key::Named(NamedKey::Control),
CtrlRight => Key::Control, CtrlRight => Key::Named(NamedKey::Control),
CapsLock => Key::CapsLock, CapsLock => Key::Named(NamedKey::CapsLock),
ScrollLock => Key::ScrollLock, ScrollLock => Key::Named(NamedKey::ScrollLock),
MetaLeft => Key::Super, MetaLeft => Key::Named(NamedKey::Super),
MetaRight => Key::Super, MetaRight => Key::Named(NamedKey::Super),
Function => Key::Fn, Function => Key::Named(NamedKey::Fn),
Sysrq => Key::PrintScreen, Sysrq => Key::Named(NamedKey::PrintScreen),
Break => Key::Pause, Break => Key::Named(NamedKey::Pause),
MoveHome => Key::Home, MoveHome => Key::Named(NamedKey::Home),
MoveEnd => Key::End, MoveEnd => Key::Named(NamedKey::End),
Insert => Key::Insert, Insert => Key::Named(NamedKey::Insert),
Forward => Key::BrowserForward, Forward => Key::Named(NamedKey::BrowserForward),
MediaPlay => Key::MediaPlay, MediaPlay => Key::Named(NamedKey::MediaPlay),
MediaPause => Key::MediaPause, MediaPause => Key::Named(NamedKey::MediaPause),
MediaClose => Key::MediaClose, MediaClose => Key::Named(NamedKey::MediaClose),
MediaEject => Key::Eject, MediaEject => Key::Named(NamedKey::Eject),
MediaRecord => Key::MediaRecord, MediaRecord => Key::Named(NamedKey::MediaRecord),
F1 => Key::F1, F1 => Key::Named(NamedKey::F1),
F2 => Key::F2, F2 => Key::Named(NamedKey::F2),
F3 => Key::F3, F3 => Key::Named(NamedKey::F3),
F4 => Key::F4, F4 => Key::Named(NamedKey::F4),
F5 => Key::F5, F5 => Key::Named(NamedKey::F5),
F6 => Key::F6, F6 => Key::Named(NamedKey::F6),
F7 => Key::F7, F7 => Key::Named(NamedKey::F7),
F8 => Key::F8, F8 => Key::Named(NamedKey::F8),
F9 => Key::F9, F9 => Key::Named(NamedKey::F9),
F10 => Key::F10, F10 => Key::Named(NamedKey::F10),
F11 => Key::F11, F11 => Key::Named(NamedKey::F11),
F12 => Key::F12, F12 => Key::Named(NamedKey::F12),
NumLock => Key::NumLock, NumLock => Key::Named(NamedKey::NumLock),
Numpad0 => Key::Character("0".into()), Numpad0 => Key::Character("0".into()),
Numpad1 => Key::Character("1".into()), Numpad1 => Key::Character("1".into()),
Numpad2 => Key::Character("2".into()), Numpad2 => Key::Character("2".into()),
@@ -382,97 +382,97 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
NumpadAdd => Key::Character("+".into()), NumpadAdd => Key::Character("+".into()),
NumpadDot => Key::Character(".".into()), NumpadDot => Key::Character(".".into()),
NumpadComma => Key::Character(",".into()), NumpadComma => Key::Character(",".into()),
NumpadEnter => Key::Enter, NumpadEnter => Key::Named(NamedKey::Enter),
NumpadEquals => Key::Character("=".into()), NumpadEquals => Key::Character("=".into()),
NumpadLeftParen => Key::Character("(".into()), NumpadLeftParen => Key::Character("(".into()),
NumpadRightParen => Key::Character(")".into()), NumpadRightParen => Key::Character(")".into()),
VolumeMute => Key::AudioVolumeMute, VolumeMute => Key::Named(NamedKey::AudioVolumeMute),
Info => Key::Info, Info => Key::Named(NamedKey::Info),
ChannelUp => Key::ChannelUp, ChannelUp => Key::Named(NamedKey::ChannelUp),
ChannelDown => Key::ChannelDown, ChannelDown => Key::Named(NamedKey::ChannelDown),
ZoomIn => Key::ZoomIn, ZoomIn => Key::Named(NamedKey::ZoomIn),
ZoomOut => Key::ZoomOut, ZoomOut => Key::Named(NamedKey::ZoomOut),
Tv => Key::TV, Tv => Key::Named(NamedKey::TV),
Guide => Key::Guide, Guide => Key::Named(NamedKey::Guide),
Dvr => Key::DVR, Dvr => Key::Named(NamedKey::DVR),
Bookmark => Key::BrowserFavorites, Bookmark => Key::Named(NamedKey::BrowserFavorites),
Captions => Key::ClosedCaptionToggle, Captions => Key::Named(NamedKey::ClosedCaptionToggle),
Settings => Key::Settings, Settings => Key::Named(NamedKey::Settings),
TvPower => Key::TVPower, TvPower => Key::Named(NamedKey::TVPower),
TvInput => Key::TVInput, TvInput => Key::Named(NamedKey::TVInput),
StbPower => Key::STBPower, StbPower => Key::Named(NamedKey::STBPower),
StbInput => Key::STBInput, StbInput => Key::Named(NamedKey::STBInput),
AvrPower => Key::AVRPower, AvrPower => Key::Named(NamedKey::AVRPower),
AvrInput => Key::AVRInput, AvrInput => Key::Named(NamedKey::AVRInput),
ProgRed => Key::ColorF0Red, ProgRed => Key::Named(NamedKey::ColorF0Red),
ProgGreen => Key::ColorF1Green, ProgGreen => Key::Named(NamedKey::ColorF1Green),
ProgYellow => Key::ColorF2Yellow, ProgYellow => Key::Named(NamedKey::ColorF2Yellow),
ProgBlue => Key::ColorF3Blue, ProgBlue => Key::Named(NamedKey::ColorF3Blue),
AppSwitch => Key::AppSwitch, AppSwitch => Key::Named(NamedKey::AppSwitch),
LanguageSwitch => Key::GroupNext, LanguageSwitch => Key::Named(NamedKey::GroupNext),
MannerMode => Key::MannerMode, MannerMode => Key::Named(NamedKey::MannerMode),
Keycode3dMode => Key::TV3DMode, Keycode3dMode => Key::Named(NamedKey::TV3DMode),
Contacts => Key::LaunchContacts, Contacts => Key::Named(NamedKey::LaunchContacts),
Calendar => Key::LaunchCalendar, Calendar => Key::Named(NamedKey::LaunchCalendar),
Music => Key::LaunchMusicPlayer, Music => Key::Named(NamedKey::LaunchMusicPlayer),
Calculator => Key::LaunchApplication2, Calculator => Key::Named(NamedKey::LaunchApplication2),
ZenkakuHankaku => Key::ZenkakuHankaku, ZenkakuHankaku => Key::Named(NamedKey::ZenkakuHankaku),
Eisu => Key::Eisu, Eisu => Key::Named(NamedKey::Eisu),
Muhenkan => Key::NonConvert, Muhenkan => Key::Named(NamedKey::NonConvert),
Henkan => Key::Convert, Henkan => Key::Named(NamedKey::Convert),
KatakanaHiragana => Key::HiraganaKatakana, KatakanaHiragana => Key::Named(NamedKey::HiraganaKatakana),
Kana => Key::KanjiMode, Kana => Key::Named(NamedKey::KanjiMode),
BrightnessDown => Key::BrightnessDown, BrightnessDown => Key::Named(NamedKey::BrightnessDown),
BrightnessUp => Key::BrightnessUp, BrightnessUp => Key::Named(NamedKey::BrightnessUp),
MediaAudioTrack => Key::MediaAudioTrack, MediaAudioTrack => Key::Named(NamedKey::MediaAudioTrack),
Sleep => Key::Standby, Sleep => Key::Named(NamedKey::Standby),
Wakeup => Key::WakeUp, Wakeup => Key::Named(NamedKey::WakeUp),
Pairing => Key::Pairing, Pairing => Key::Named(NamedKey::Pairing),
MediaTopMenu => Key::MediaTopMenu, MediaTopMenu => Key::Named(NamedKey::MediaTopMenu),
LastChannel => Key::MediaLast, LastChannel => Key::Named(NamedKey::MediaLast),
TvDataService => Key::TVDataService, TvDataService => Key::Named(NamedKey::TVDataService),
VoiceAssist => Key::VoiceDial, VoiceAssist => Key::Named(NamedKey::VoiceDial),
TvRadioService => Key::TVRadioService, TvRadioService => Key::Named(NamedKey::TVRadioService),
TvTeletext => Key::Teletext, TvTeletext => Key::Named(NamedKey::Teletext),
TvNumberEntry => Key::TVNumberEntry, TvNumberEntry => Key::Named(NamedKey::TVNumberEntry),
TvTerrestrialAnalog => Key::TVTerrestrialAnalog, TvTerrestrialAnalog => Key::Named(NamedKey::TVTerrestrialAnalog),
TvTerrestrialDigital => Key::TVTerrestrialDigital, TvTerrestrialDigital => Key::Named(NamedKey::TVTerrestrialDigital),
TvSatellite => Key::TVSatellite, TvSatellite => Key::Named(NamedKey::TVSatellite),
TvSatelliteBs => Key::TVSatelliteBS, TvSatelliteBs => Key::Named(NamedKey::TVSatelliteBS),
TvSatelliteCs => Key::TVSatelliteCS, TvSatelliteCs => Key::Named(NamedKey::TVSatelliteCS),
TvSatelliteService => Key::TVSatelliteToggle, TvSatelliteService => Key::Named(NamedKey::TVSatelliteToggle),
TvNetwork => Key::TVNetwork, TvNetwork => Key::Named(NamedKey::TVNetwork),
TvAntennaCable => Key::TVAntennaCable, TvAntennaCable => Key::Named(NamedKey::TVAntennaCable),
TvInputHdmi1 => Key::TVInputHDMI1, TvInputHdmi1 => Key::Named(NamedKey::TVInputHDMI1),
TvInputHdmi2 => Key::TVInputHDMI2, TvInputHdmi2 => Key::Named(NamedKey::TVInputHDMI2),
TvInputHdmi3 => Key::TVInputHDMI3, TvInputHdmi3 => Key::Named(NamedKey::TVInputHDMI3),
TvInputHdmi4 => Key::TVInputHDMI4, TvInputHdmi4 => Key::Named(NamedKey::TVInputHDMI4),
TvInputComposite1 => Key::TVInputComposite1, TvInputComposite1 => Key::Named(NamedKey::TVInputComposite1),
TvInputComposite2 => Key::TVInputComposite2, TvInputComposite2 => Key::Named(NamedKey::TVInputComposite2),
TvInputComponent1 => Key::TVInputComponent1, TvInputComponent1 => Key::Named(NamedKey::TVInputComponent1),
TvInputComponent2 => Key::TVInputComponent2, TvInputComponent2 => Key::Named(NamedKey::TVInputComponent2),
TvInputVga1 => Key::TVInputVGA1, TvInputVga1 => Key::Named(NamedKey::TVInputVGA1),
TvAudioDescription => Key::TVAudioDescription, TvAudioDescription => Key::Named(NamedKey::TVAudioDescription),
TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp, TvAudioDescriptionMixUp => Key::Named(NamedKey::TVAudioDescriptionMixUp),
TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown, TvAudioDescriptionMixDown => Key::Named(NamedKey::TVAudioDescriptionMixDown),
TvZoomMode => Key::ZoomToggle, TvZoomMode => Key::Named(NamedKey::ZoomToggle),
TvContentsMenu => Key::TVContentsMenu, TvContentsMenu => Key::Named(NamedKey::TVContentsMenu),
TvMediaContextMenu => Key::TVMediaContext, TvMediaContextMenu => Key::Named(NamedKey::TVMediaContext),
TvTimerProgramming => Key::TVTimer, TvTimerProgramming => Key::Named(NamedKey::TVTimer),
Help => Key::Help, Help => Key::Named(NamedKey::Help),
NavigatePrevious => Key::NavigatePrevious, NavigatePrevious => Key::Named(NamedKey::NavigatePrevious),
NavigateNext => Key::NavigateNext, NavigateNext => Key::Named(NamedKey::NavigateNext),
NavigateIn => Key::NavigateIn, NavigateIn => Key::Named(NamedKey::NavigateIn),
NavigateOut => Key::NavigateOut, NavigateOut => Key::Named(NamedKey::NavigateOut),
MediaSkipForward => Key::MediaSkipForward, MediaSkipForward => Key::Named(NamedKey::MediaSkipForward),
MediaSkipBackward => Key::MediaSkipBackward, MediaSkipBackward => Key::Named(NamedKey::MediaSkipBackward),
MediaStepForward => Key::MediaStepForward, MediaStepForward => Key::Named(NamedKey::MediaStepForward),
MediaStepBackward => Key::MediaStepBackward, MediaStepBackward => Key::Named(NamedKey::MediaStepBackward),
Cut => Key::Cut, Cut => Key::Named(NamedKey::Cut),
Copy => Key::Copy, Copy => Key::Named(NamedKey::Copy),
Paste => Key::Paste, Paste => Key::Named(NamedKey::Paste),
Refresh => Key::BrowserRefresh, Refresh => Key::Named(NamedKey::BrowserRefresh),
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// Keycodes that don't have a logical Key mapping // Keycodes that don't have a logical Key mapping
@@ -555,6 +555,10 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
ThumbsUp => Key::Unidentified(native), ThumbsUp => Key::Unidentified(native),
ThumbsDown => Key::Unidentified(native), ThumbsDown => Key::Unidentified(native),
ProfileSwitch => Key::Unidentified(native), ProfileSwitch => Key::Unidentified(native),
// It's always possible that new versions of Android could introduce
// key codes we can't know about at compile time.
_ => Key::Unidentified(native),
}, },
} }
} }

View File

@@ -4,9 +4,10 @@ use std::{
cell::Cell, cell::Cell,
collections::VecDeque, collections::VecDeque,
hash::Hash, hash::Hash,
marker::PhantomData,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
mpsc, Arc, Mutex, RwLock, mpsc, Arc, Mutex,
}, },
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@@ -15,23 +16,29 @@ use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
use android_activity::{ use android_activity::{
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect, AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
}; };
use once_cell::sync::Lazy; use log::{debug, trace, warn};
use crate::{ use crate::{
cursor::Cursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size}, dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error, error,
event::{self, Force, InnerSizeWriter, StartCause}, event::{self, Force, InnerSizeWriter, StartCause},
event_loop::{self, ControlFlow, DeviceEvents, EventLoopWindowTarget as RootELW}, event_loop::{self, ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents},
platform::pump_events::PumpStatus, platform::pump_events::PumpStatus,
window::{ window::{
self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel, self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme,
WindowButtons, WindowLevel,
}, },
}; };
use crate::{error::EventLoopError, platform_impl::Fullscreen}; use crate::{error::EventLoopError, platform_impl::Fullscreen};
mod keycodes; mod keycodes;
static HAS_FOCUS: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true)); 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);
/// Returns the minimum `Option<Duration>`, taking into account that `None` /// Returns the minimum `Option<Duration>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use /// equates to an infinite timeout, not a zero timeout (so can't just use
@@ -138,7 +145,7 @@ pub struct KeyEventExtra {}
pub struct EventLoop<T: 'static> { pub struct EventLoop<T: 'static> {
android_app: AndroidApp, android_app: AndroidApp,
window_target: event_loop::EventLoopWindowTarget<T>, window_target: event_loop::ActiveEventLoop,
redraw_flag: SharedFlag, redraw_flag: SharedFlag,
user_events_sender: mpsc::Sender<T>, user_events_sender: mpsc::Sender<T>,
user_events_receiver: PeekableReceiver<T>, //must wake looper whenever something gets sent user_events_receiver: PeekableReceiver<T>, //must wake looper whenever something gets sent
@@ -176,8 +183,8 @@ impl<T: 'static> EventLoop<T> {
Ok(Self { Ok(Self {
android_app: android_app.clone(), android_app: android_app.clone(),
window_target: event_loop::EventLoopWindowTarget { window_target: event_loop::ActiveEventLoop {
p: EventLoopWindowTarget { p: ActiveEventLoop {
app: android_app.clone(), app: android_app.clone(),
control_flow: Cell::new(ControlFlow::default()), control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(false), exit: Cell::new(false),
@@ -185,9 +192,8 @@ impl<T: 'static> EventLoop<T> {
&redraw_flag, &redraw_flag,
android_app.create_waker(), android_app.create_waker(),
), ),
_marker: std::marker::PhantomData,
}, },
_marker: std::marker::PhantomData, _marker: PhantomData,
}, },
redraw_flag, redraw_flag,
user_events_sender, user_events_sender,
@@ -203,7 +209,7 @@ impl<T: 'static> EventLoop<T> {
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F) fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F)
where where
F: FnMut(event::Event<T>, &RootELW<T>), F: FnMut(event::Event<T>, &RootAEL),
{ {
trace!("Mainloop iteration"); trace!("Mainloop iteration");
@@ -229,7 +235,7 @@ impl<T: 'static> EventLoop<T> {
warn!("TODO: find a way to notify application of content rect change"); warn!("TODO: find a way to notify application of content rect change");
} }
MainEvent::GainedFocus => { MainEvent::GainedFocus => {
*HAS_FOCUS.write().unwrap() = true; HAS_FOCUS.store(true, Ordering::Relaxed);
callback( callback(
event::Event::WindowEvent { event::Event::WindowEvent {
window_id: window::WindowId(WindowId), window_id: window::WindowId(WindowId),
@@ -239,7 +245,7 @@ impl<T: 'static> EventLoop<T> {
); );
} }
MainEvent::LostFocus => { MainEvent::LostFocus => {
*HAS_FOCUS.write().unwrap() = false; HAS_FOCUS.store(false, Ordering::Relaxed);
callback( callback(
event::Event::WindowEvent { event::Event::WindowEvent {
window_id: window::WindowId(WindowId), window_id: window::WindowId(WindowId),
@@ -375,7 +381,7 @@ impl<T: 'static> EventLoop<T> {
callback: &mut F, callback: &mut F,
) -> InputStatus ) -> InputStatus
where where
F: FnMut(event::Event<T>, &RootELW<T>), F: FnMut(event::Event<T>, &RootAEL),
{ {
let mut input_status = InputStatus::Handled; let mut input_status = InputStatus::Handled;
match event { match event {
@@ -456,7 +462,7 @@ impl<T: 'static> EventLoop<T> {
device_id: event::DeviceId(DeviceId(key.device_id())), device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent { event: event::KeyEvent {
state, state,
physical_key: keycodes::to_physical_keycode(keycode), physical_key: keycodes::to_physical_key(keycode),
logical_key: keycodes::to_logical(key_char, keycode), logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode), location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0, repeat: key.repeat_count() > 0,
@@ -480,19 +486,15 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError> pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>), F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{ {
self.run_on_demand(event_handler) self.run_on_demand(event_handler)
} }
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError> pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where where
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>), F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{ {
if self.loop_running {
return Err(EventLoopError::AlreadyRunning);
}
loop { loop {
match self.pump_events(None, &mut event_handler) { match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => { PumpStatus::Exit(0) => {
@@ -510,7 +512,7 @@ impl<T: 'static> EventLoop<T> {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where where
F: FnMut(event::Event<T>, &RootELW<T>), F: FnMut(event::Event<T>, &RootAEL),
{ {
if !self.loop_running { if !self.loop_running {
self.loop_running = true; self.loop_running = true;
@@ -543,7 +545,7 @@ impl<T: 'static> EventLoop<T> {
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F) fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where where
F: FnMut(event::Event<T>, &RootELW<T>), F: FnMut(event::Event<T>, &RootAEL),
{ {
let start = Instant::now(); let start = Instant::now();
@@ -619,7 +621,7 @@ impl<T: 'static> EventLoop<T> {
}); });
} }
pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget<T> { pub fn window_target(&self) -> &event_loop::ActiveEventLoop {
&self.window_target &self.window_target
} }
@@ -663,19 +665,25 @@ impl<T> EventLoopProxy<T> {
} }
} }
pub struct EventLoopWindowTarget<T: 'static> { pub struct ActiveEventLoop {
app: AndroidApp, app: AndroidApp,
control_flow: Cell<ControlFlow>, control_flow: Cell<ControlFlow>,
exit: Cell<bool>, exit: Cell<bool>,
redraw_requester: RedrawRequester, redraw_requester: RedrawRequester,
_marker: std::marker::PhantomData<T>,
} }
impl<T: 'static> EventLoopWindowTarget<T> { impl ActiveEventLoop {
pub fn primary_monitor(&self) -> Option<MonitorHandle> { pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(self.app.clone())) Some(MonitorHandle::new(self.app.clone()))
} }
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor {
let _ = source.inner;
CustomCursor {
inner: PlatformCustomCursor,
}
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> { pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut v = VecDeque::with_capacity(1); let mut v = VecDeque::with_capacity(1);
v.push_back(MonitorHandle::new(self.app.clone())); v.push_back(MonitorHandle::new(self.app.clone()));
@@ -713,9 +721,36 @@ impl<T: 'static> EventLoopWindowTarget<T> {
self.exit.set(true) self.exit.set(true)
} }
pub(crate) fn clear_exit(&self) {
self.exit.set(false)
}
pub(crate) fn exiting(&self) -> bool { pub(crate) fn exiting(&self) -> bool {
self.exit.get() self.exit.get()
} }
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle
}
}
#[derive(Clone)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::AndroidDisplayHandle::empty().into()
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::AndroidDisplayHandle::new().into())
}
} }
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
@@ -749,7 +784,7 @@ impl DeviceId {
} }
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowBuilderAttributes; pub struct PlatformSpecificWindowAttributes;
pub(crate) struct Window { pub(crate) struct Window {
app: AndroidApp, app: AndroidApp,
@@ -757,10 +792,9 @@ pub(crate) struct Window {
} }
impl Window { impl Window {
pub(crate) fn new<T: 'static>( pub(crate) fn new(
el: &EventLoopWindowTarget<T>, el: &ActiveEventLoop,
_window_attrs: window::WindowAttributes, _window_attrs: window::WindowAttributes,
_: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, error::OsError> { ) -> Result<Self, error::OsError> {
// FIXME this ignores requested window attributes // FIXME this ignores requested window attributes
@@ -904,7 +938,7 @@ impl Window {
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {} pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
pub fn set_cursor_icon(&self, _: window::CursorIcon) {} pub fn set_cursor(&self, _: Cursor) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported( Err(error::ExternalError::NotSupported(
@@ -946,10 +980,10 @@ impl Window {
#[cfg(feature = "rwh_04")] #[cfg(feature = "rwh_04")]
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
use rwh_04::HasRawWindowHandle;
if let Some(native_window) = self.app.native_window().as_ref() { if let Some(native_window) = self.app.native_window().as_ref() {
let mut handle = rwh_04::AndroidNdkHandle::empty(); native_window.raw_window_handle()
handle.a_native_window = native_window.ptr().as_ptr() as *mut _;
rwh_04::RawWindowHandle::AndroidNdk(handle)
} else { } else {
panic!("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."); panic!("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.");
} }
@@ -972,10 +1006,13 @@ impl Window {
} }
#[cfg(feature = "rwh_06")] #[cfg(feature = "rwh_06")]
// Allow the usage of HasRawWindowHandle inside this function
#[allow(deprecated)]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> { pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
use rwh_06::HasRawWindowHandle;
if let Some(native_window) = self.app.native_window().as_ref() { if let Some(native_window) = self.app.native_window().as_ref() {
let handle = rwh_06::AndroidNdkWindowHandle::new(native_window.ptr().cast()); native_window.raw_window_handle()
Ok(rwh_06::RawWindowHandle::AndroidNdk(handle))
} else { } else {
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."); 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) Err(rwh_06::HandleError::Unavailable)
@@ -1008,7 +1045,7 @@ impl Window {
pub fn set_content_protected(&self, _protected: bool) {} pub fn set_content_protected(&self, _protected: bool) {}
pub fn has_focus(&self) -> bool { pub fn has_focus(&self) -> bool {
*HAS_FOCUS.read().unwrap() HAS_FOCUS.load(Ordering::Relaxed)
} }
pub fn title(&self) -> String { pub fn title(&self) -> String {
@@ -1028,8 +1065,6 @@ impl Display for OsError {
} }
} }
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MonitorHandle { pub struct MonitorHandle {
app: AndroidApp, app: AndroidApp,
@@ -1079,11 +1114,11 @@ impl MonitorHandle {
None None
} }
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
let size = self.size().into(); let size = self.size().into();
// FIXME this is not the real refresh rate // FIXME this is not the real refresh rate
// (it is guaranteed to support 32 bit color though) // (it is guaranteed to support 32 bit color though)
std::iter::once(VideoMode { std::iter::once(VideoModeHandle {
size, size,
bit_depth: 32, bit_depth: 32,
refresh_rate_millihertz: 60000, refresh_rate_millihertz: 60000,
@@ -1093,14 +1128,14 @@ impl MonitorHandle {
} }
#[derive(Clone, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct VideoMode { pub struct VideoModeHandle {
size: (u32, u32), size: (u32, u32),
bit_depth: u16, bit_depth: u16,
refresh_rate_millihertz: u32, refresh_rate_millihertz: u32,
monitor: MonitorHandle, monitor: MonitorHandle,
} }
impl VideoMode { impl VideoModeHandle {
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
self.size.into() self.size.into()
} }

View File

@@ -0,0 +1,103 @@
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::{ use std::{
cell::{RefCell, RefMut}, cell::{RefCell, RefMut},
collections::HashSet, collections::HashSet,
mem, fmt, mem,
os::raw::c_void, os::raw::c_void,
ptr, ptr,
sync::{Arc, Mutex}, sync::{Arc, Mutex, OnceLock},
time::Instant, time::Instant,
}; };
@@ -22,15 +22,13 @@ use icrate::Foundation::{
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::AnyObject; use objc2::runtime::AnyObject;
use objc2::{msg_send, sel}; use objc2::{msg_send, sel};
use once_cell::sync::Lazy;
use super::event_loop::{EventHandler, Never};
use super::uikit::UIView; use super::uikit::UIView;
use super::view::WinitUIWindow; use super::window::WinitUIWindow;
use crate::{ use crate::{
dpi::PhysicalSize, dpi::PhysicalSize,
event::{Event, InnerSizeWriter, StartCause, WindowEvent}, event::{Event, InnerSizeWriter, StartCause, WindowEvent},
event_loop::ControlFlow, event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow},
window::WindowId as RootWindowId, window::WindowId as RootWindowId,
}; };
@@ -47,8 +45,32 @@ macro_rules! bug_assert {
} }
#[derive(Debug)] #[derive(Debug)]
pub enum EventWrapper { pub(crate) struct HandlePendingUserEvents;
StaticEvent(Event<Never>),
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>),
ScaleFactorChanged(ScaleFactorChanged), ScaleFactorChanged(ScaleFactorChanged),
} }
@@ -61,7 +83,7 @@ pub struct ScaleFactorChanged {
enum UserCallbackTransitionResult<'a> { enum UserCallbackTransitionResult<'a> {
Success { Success {
event_handler: Box<dyn EventHandler>, handler: EventLoopHandler,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
processing_redraws: bool, processing_redraws: bool,
}, },
@@ -70,7 +92,7 @@ enum UserCallbackTransitionResult<'a> {
}, },
} }
impl Event<Never> { impl Event<HandlePendingUserEvents> {
fn is_redraw(&self) -> bool { fn is_redraw(&self) -> bool {
matches!( matches!(
self, self,
@@ -94,11 +116,11 @@ enum AppStateImpl {
Launching { Launching {
queued_windows: Vec<Id<WinitUIWindow>>, queued_windows: Vec<Id<WinitUIWindow>>,
queued_events: Vec<EventWrapper>, queued_events: Vec<EventWrapper>,
queued_event_handler: Box<dyn EventHandler>, queued_handler: EventLoopHandler,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
}, },
ProcessingEvents { ProcessingEvents {
event_handler: Box<dyn EventHandler>, handler: EventLoopHandler,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
}, },
@@ -108,15 +130,15 @@ enum AppStateImpl {
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
}, },
ProcessingRedraws { ProcessingRedraws {
event_handler: Box<dyn EventHandler>, handler: EventLoopHandler,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
}, },
Waiting { Waiting {
waiting_event_handler: Box<dyn EventHandler>, waiting_handler: EventLoopHandler,
start: Instant, start: Instant,
}, },
PollFinished { PollFinished {
waiting_event_handler: Box<dyn EventHandler>, waiting_handler: EventLoopHandler,
}, },
Terminated, Terminated,
} }
@@ -200,7 +222,11 @@ impl AppState {
) )
} }
fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) { fn has_terminated(&self) -> bool {
matches!(self.state(), AppStateImpl::Terminated)
}
fn will_launch_transition(&mut self, queued_handler: EventLoopHandler) {
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() { let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::NotLaunched { AppStateImpl::NotLaunched {
queued_windows, queued_windows,
@@ -212,28 +238,28 @@ impl AppState {
self.set_state(AppStateImpl::Launching { self.set_state(AppStateImpl::Launching {
queued_windows, queued_windows,
queued_events, queued_events,
queued_event_handler, queued_handler,
queued_gpu_redraws, queued_gpu_redraws,
}); });
} }
fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) { fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) {
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() { let (windows, events, handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching { AppStateImpl::Launching {
queued_windows, queued_windows,
queued_events, queued_events,
queued_event_handler, queued_handler,
queued_gpu_redraws, queued_gpu_redraws,
} => ( } => (
queued_windows, queued_windows,
queued_events, queued_events,
queued_event_handler, queued_handler,
queued_gpu_redraws, queued_gpu_redraws,
), ),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingEvents { self.set_state(AppStateImpl::ProcessingEvents {
event_handler, handler,
active_control_flow: self.control_flow, active_control_flow: self.control_flow,
queued_gpu_redraws, queued_gpu_redraws,
}); });
@@ -243,28 +269,23 @@ impl AppState {
fn wakeup_transition(&mut self) -> Option<EventWrapper> { fn wakeup_transition(&mut self) -> Option<EventWrapper> {
// before `AppState::did_finish_launching` is called, pretend there is no running // before `AppState::did_finish_launching` is called, pretend there is no running
// event loop. // event loop.
if !self.has_launched() { if !self.has_launched() || self.has_terminated() {
return None; return None;
} }
let (event_handler, event) = match (self.control_flow, self.take_state()) { let (handler, event) = match (self.control_flow, self.take_state()) {
( (ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => (
ControlFlow::Poll, waiting_handler,
AppStateImpl::PollFinished {
waiting_event_handler,
},
) => (
waiting_event_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)), EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)),
), ),
( (
ControlFlow::Wait, ControlFlow::Wait,
AppStateImpl::Waiting { AppStateImpl::Waiting {
waiting_event_handler, waiting_handler,
start, start,
}, },
) => ( ) => (
waiting_event_handler, waiting_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start, start,
requested_resume: None, requested_resume: None,
@@ -273,7 +294,7 @@ impl AppState {
( (
ControlFlow::WaitUntil(requested_resume), ControlFlow::WaitUntil(requested_resume),
AppStateImpl::Waiting { AppStateImpl::Waiting {
waiting_event_handler, waiting_handler,
start, start,
}, },
) => { ) => {
@@ -288,13 +309,13 @@ impl AppState {
requested_resume: Some(requested_resume), requested_resume: Some(requested_resume),
})) }))
}; };
(waiting_event_handler, event) (waiting_handler, event)
} }
s => bug!("`EventHandler` unexpectedly woke up {:?}", s), s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingEvents { self.set_state(AppStateImpl::ProcessingEvents {
event_handler, handler,
queued_gpu_redraws: Default::default(), queued_gpu_redraws: Default::default(),
active_control_flow: self.control_flow, active_control_flow: self.control_flow,
}); });
@@ -339,25 +360,20 @@ impl AppState {
} }
} }
let (event_handler, queued_gpu_redraws, active_control_flow, processing_redraws) = let (handler, queued_gpu_redraws, active_control_flow, processing_redraws) =
match self.take_state() { match self.take_state() {
AppStateImpl::Launching { .. } AppStateImpl::Launching { .. }
| AppStateImpl::NotLaunched { .. } | AppStateImpl::NotLaunched { .. }
| AppStateImpl::InUserCallback { .. } => unreachable!(), | AppStateImpl::InUserCallback { .. } => unreachable!(),
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents {
event_handler, handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
} => ( } => (handler, queued_gpu_redraws, active_control_flow, false),
event_handler,
queued_gpu_redraws,
active_control_flow,
false,
),
AppStateImpl::ProcessingRedraws { AppStateImpl::ProcessingRedraws {
event_handler, handler,
active_control_flow, active_control_flow,
} => (event_handler, Default::default(), active_control_flow, true), } => (handler, Default::default(), active_control_flow, true),
AppStateImpl::PollFinished { .. } AppStateImpl::PollFinished { .. }
| AppStateImpl::Waiting { .. } | AppStateImpl::Waiting { .. }
| AppStateImpl::Terminated => unreachable!(), | AppStateImpl::Terminated => unreachable!(),
@@ -367,37 +383,37 @@ impl AppState {
queued_gpu_redraws, queued_gpu_redraws,
}); });
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success {
event_handler, handler,
active_control_flow, active_control_flow,
processing_redraws, processing_redraws,
} }
} }
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> { fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> {
let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() { let (handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents {
event_handler, handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
} => (event_handler, queued_gpu_redraws, active_control_flow), } => (handler, queued_gpu_redraws, active_control_flow),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingRedraws { self.set_state(AppStateImpl::ProcessingRedraws {
event_handler, handler,
active_control_flow, active_control_flow,
}); });
queued_gpu_redraws queued_gpu_redraws
} }
fn events_cleared_transition(&mut self) { fn events_cleared_transition(&mut self) {
if !self.has_launched() { if !self.has_launched() || self.has_terminated() {
return; return;
} }
let (waiting_event_handler, old) = match self.take_state() { let (waiting_handler, old) = match self.take_state() {
AppStateImpl::ProcessingRedraws { AppStateImpl::ProcessingRedraws {
event_handler, handler,
active_control_flow, active_control_flow,
} => (event_handler, active_control_flow), } => (handler, active_control_flow),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
@@ -406,7 +422,7 @@ impl AppState {
(ControlFlow::Wait, ControlFlow::Wait) => { (ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_event_handler, waiting_handler,
start, start,
}); });
} }
@@ -415,14 +431,14 @@ impl AppState {
{ {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_event_handler, waiting_handler,
start, start,
}); });
} }
(_, ControlFlow::Wait) => { (_, ControlFlow::Wait) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_event_handler, waiting_handler,
start, start,
}); });
self.waker.stop() self.waker.stop()
@@ -430,24 +446,22 @@ impl AppState {
(_, ControlFlow::WaitUntil(new_instant)) => { (_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting {
waiting_event_handler, waiting_handler,
start, start,
}); });
self.waker.start_at(new_instant) self.waker.start_at(new_instant)
} }
// Unlike on macOS, handle Poll to Poll transition here to call the waker // Unlike on macOS, handle Poll to Poll transition here to call the waker
(_, ControlFlow::Poll) => { (_, ControlFlow::Poll) => {
self.set_state(AppStateImpl::PollFinished { self.set_state(AppStateImpl::PollFinished { waiting_handler });
waiting_event_handler,
});
self.waker.start() self.waker.start()
} }
} }
} }
fn terminated_transition(&mut self) -> Box<dyn EventHandler> { fn terminated_transition(&mut self) -> EventLoopHandler {
match self.replace_state(AppStateImpl::Terminated) { match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler, AppStateImpl::ProcessingEvents { handler, .. } => handler,
s => bug!("`LoopExiting` happened while not processing events {:?}", s), s => bug!("`LoopExiting` happened while not processing events {:?}", s),
} }
} }
@@ -512,8 +526,8 @@ pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUI
} }
} }
pub fn will_launch(mtm: MainThreadMarker, queued_event_handler: Box<dyn EventHandler>) { pub(crate) fn will_launch(mtm: MainThreadMarker, queued_handler: EventLoopHandler) {
AppState::get_mut(mtm).will_launch_transition(queued_event_handler) AppState::get_mut(mtm).will_launch_transition(queued_handler)
} }
pub fn did_finish_launching(mtm: MainThreadMarker) { pub fn did_finish_launching(mtm: MainThreadMarker) {
@@ -536,7 +550,7 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
// //
// relevant iOS log: // relevant iOS log:
// ``` // ```
// [ApplicationLifecycle] Windows were created before application initialzation // [ApplicationLifecycle] Windows were created before application initialization
// completed. This may result in incorrect visual appearance. // completed. This may result in incorrect visual appearance.
// ``` // ```
let screen = window.screen(); let screen = window.screen();
@@ -586,17 +600,21 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
events: I, events: I,
) { ) {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
let (mut event_handler, active_control_flow, processing_redraws) = if this.has_terminated() {
return;
}
let (mut handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() { match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
queued_events.extend(events); queued_events.extend(events);
return; return;
} }
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success {
event_handler, handler,
active_control_flow, active_control_flow,
processing_redraws, processing_redraws,
} => (event_handler, active_control_flow, processing_redraws), } => (handler, active_control_flow, processing_redraws),
}; };
drop(this); drop(this);
@@ -611,11 +629,9 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
event event
); );
} }
event_handler.handle_nonuser_event(event) handler.handle_event(event)
}
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
} }
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
} }
} }
@@ -642,12 +658,12 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
"redraw queued while processing redraws" "redraw queued while processing redraws"
); );
AppStateImpl::ProcessingRedraws { AppStateImpl::ProcessingRedraws {
event_handler, handler,
active_control_flow, active_control_flow,
} }
} else { } else {
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents {
event_handler, handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
} }
@@ -667,11 +683,9 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
event event
); );
} }
event_handler.handle_nonuser_event(event) handler.handle_event(event)
}
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
} }
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
} }
} }
} }
@@ -679,23 +693,23 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
fn handle_user_events(mtm: MainThreadMarker) { fn handle_user_events(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
let (mut event_handler, active_control_flow, processing_redraws) = let (mut handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() { match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => { UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
bug!("unexpected attempted to process an event") bug!("unexpected attempted to process an event")
} }
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success {
event_handler, handler,
active_control_flow, active_control_flow,
processing_redraws, processing_redraws,
} => (event_handler, active_control_flow, processing_redraws), } => (handler, active_control_flow, processing_redraws),
}; };
if processing_redraws { if processing_redraws {
bug!("user events attempted to be sent out while `ProcessingRedraws`"); bug!("user events attempted to be sent out while `ProcessingRedraws`");
} }
drop(this); drop(this);
event_handler.handle_user_events(); handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
loop { loop {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
@@ -715,7 +729,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
_ => unreachable!(), _ => unreachable!(),
}; };
this.app_state = Some(AppStateImpl::ProcessingEvents { this.app_state = Some(AppStateImpl::ProcessingEvents {
event_handler, handler,
queued_gpu_redraws, queued_gpu_redraws,
active_control_flow, active_control_flow,
}); });
@@ -725,19 +739,18 @@ fn handle_user_events(mtm: MainThreadMarker) {
for wrapper in queued_events { for wrapper in queued_events {
match wrapper { match wrapper {
EventWrapper::StaticEvent(event) => event_handler.handle_nonuser_event(event), EventWrapper::StaticEvent(event) => handler.handle_event(event),
EventWrapper::ScaleFactorChanged(event) => { EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
handle_hidpi_proxy(&mut event_handler, event)
}
} }
} }
event_handler.handle_user_events();
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
} }
} }
pub fn handle_main_events_cleared(mtm: MainThreadMarker) { pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
if !this.has_launched() { if !this.has_launched() || this.has_terminated() {
return; return;
} }
match this.state_mut() { match this.state_mut() {
@@ -771,13 +784,13 @@ pub fn handle_events_cleared(mtm: MainThreadMarker) {
pub fn terminated(mtm: MainThreadMarker) { pub fn terminated(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
let mut event_handler = this.terminated_transition(); let mut handler = this.terminated_transition();
drop(this); drop(this);
event_handler.handle_nonuser_event(Event::LoopExiting) handler.handle_event(Event::LoopExiting)
} }
fn handle_hidpi_proxy(event_handler: &mut Box<dyn EventHandler>, event: ScaleFactorChanged) { fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) {
let ScaleFactorChanged { let ScaleFactorChanged {
suggested_size, suggested_size,
scale_factor, scale_factor,
@@ -791,7 +804,7 @@ fn handle_hidpi_proxy(event_handler: &mut Box<dyn EventHandler>, event: ScaleFac
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
}, },
}; };
event_handler.handle_nonuser_event(event); handler.handle_event(event);
let (view, screen_frame) = get_view_and_screen_frame(&window); let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_inner_size.lock().unwrap(); let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size); drop(new_inner_size);
@@ -888,10 +901,10 @@ macro_rules! os_capabilities {
os_version: NSOperatingSystemVersion, os_version: NSOperatingSystemVersion,
} }
impl From<NSOperatingSystemVersion> for OSCapabilities { impl OSCapabilities {
fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities { fn from_os_version(os_version: NSOperatingSystemVersion) -> Self {
$(let $name = meets_requirements(os_version, $major, $minor);)* $(let $name = meets_requirements(os_version, $major, $minor);)*
OSCapabilities { $($name,)* os_version, } Self { $($name,)* os_version, }
} }
} }
@@ -936,25 +949,29 @@ fn meets_requirements(
(version.majorVersion, version.minorVersion) >= (required_major, required_minor) (version.majorVersion, version.minorVersion) >= (required_major, required_minor)
} }
pub fn os_capabilities() -> OSCapabilities { fn get_version() -> NSOperatingSystemVersion {
static OS_CAPABILITIES: Lazy<OSCapabilities> = Lazy::new(|| { unsafe {
let version: NSOperatingSystemVersion = unsafe { let process_info = NSProcessInfo::processInfo();
let process_info = NSProcessInfo::processInfo(); let atleast_ios_8: bool = msg_send![
let atleast_ios_8: bool = msg_send![ &process_info,
&process_info, respondsToSelector: sel!(operatingSystemVersion)
respondsToSelector: sel!(operatingSystemVersion) ];
]; // winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
// 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
// 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
// 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
// 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?
// 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.
// The minimum required iOS version is likely to grow in the future. assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater"); process_info.operatingSystemVersion()
process_info.operatingSystemVersion() }
}; }
version.into()
}); pub fn os_capabilities() -> OSCapabilities {
OS_CAPABILITIES.clone() // 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()
} }

View File

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

View File

@@ -1,78 +0,0 @@
#![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,100 +1,46 @@
//! 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)] #![cfg(ios_platform)]
#![allow(clippy::let_unit_value)] #![allow(clippy::let_unit_value)]
mod app_delegate;
mod app_state; mod app_state;
mod event_loop; mod event_loop;
mod ffi;
mod monitor; mod monitor;
mod uikit; mod uikit;
mod view; mod view;
mod view_controller;
mod window; mod window;
use std::fmt; use std::fmt;
use crate::event::DeviceId as RootDeviceId;
pub(crate) use self::{ pub(crate) use self::{
event_loop::{ event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle,
PlatformSpecificEventLoopAttributes,
}, },
monitor::{MonitorHandle, VideoMode}, monitor::{MonitorHandle, VideoModeHandle},
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, window::{PlatformSpecificWindowAttributes, Window, WindowId},
}; };
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
use self::uikit::UIScreen; pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorSource;
pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use crate::platform_impl::Fullscreen;
/// There is no way to detect which device that performed a certain event in
/// UIKit (i.e. you can't differentiate between different external keyboards,
/// or 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)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId { pub struct DeviceId;
uiscreen: *const UIScreen,
}
impl DeviceId { impl DeviceId {
pub const unsafe fn dummy() -> Self { pub const unsafe fn dummy() -> Self {
DeviceId { DeviceId
uiscreen: std::ptr::null(),
}
} }
} }
unsafe impl Send for DeviceId {} pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
unsafe impl Sync for DeviceId {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {} pub struct KeyEventExtra {}

View File

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

View File

@@ -1,9 +1,8 @@
use icrate::Foundation::{MainThreadMarker, NSObject}; use icrate::Foundation::{MainThreadMarker, NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::super::ffi::UIUserInterfaceIdiom;
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIDevice; pub(crate) struct UIDevice;
@@ -24,3 +23,19 @@ extern_methods!(
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom; 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

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

@@ -0,0 +1,121 @@
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,9 +9,12 @@ mod application;
mod coordinate_space; mod coordinate_space;
mod device; mod device;
mod event; mod event;
mod geometry;
mod gesture_recognizer;
mod responder; mod responder;
mod screen; mod screen;
mod screen_mode; mod screen_mode;
mod status_bar_style;
mod touch; mod touch;
mod trait_collection; mod trait_collection;
mod view; mod view;
@@ -20,11 +23,17 @@ mod window;
pub(crate) use self::application::UIApplication; pub(crate) use self::application::UIApplication;
pub(crate) use self::coordinate_space::UICoordinateSpace; pub(crate) use self::coordinate_space::UICoordinateSpace;
pub(crate) use self::device::UIDevice; pub(crate) use self::device::{UIDevice, UIUserInterfaceIdiom};
pub(crate) use self::event::UIEvent; 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::responder::UIResponder;
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation}; pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
pub(crate) use self::screen_mode::UIScreenMode; pub(crate) use self::screen_mode::UIScreenMode;
pub(crate) use self::status_bar_style::UIStatusBarStyle;
pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType}; pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType};
pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection}; pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection};
#[allow(unused_imports)] #[allow(unused_imports)]

View File

@@ -0,0 +1,16 @@
use icrate::Foundation::NSInteger;
use objc2::encode::{Encode, Encoding};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIStatusBarStyle {
#[default]
Default = 0,
LightContent = 1,
DarkContent = 3,
}
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::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UICoordinateSpace, UIResponder, UIViewController}; use super::{UICoordinateSpace, UIGestureRecognizer, UIResponder, UIViewController};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@@ -65,6 +65,12 @@ extern_methods!(
#[method(setNeedsDisplay)] #[method(setNeedsDisplay)]
pub fn setNeedsDisplay(&self); pub fn setNeedsDisplay(&self);
#[method(addGestureRecognizer:)]
pub fn addGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
#[method(removeGestureRecognizer:)]
pub fn removeGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
} }
); );

View File

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

View File

@@ -1,32 +1,33 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::cell::Cell; use std::cell::RefCell;
use std::ptr::NonNull;
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet}; use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet};
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::AnyClass; use objc2::runtime::AnyClass;
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType}; use objc2::{
declare_class, extern_methods, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
};
use super::app_state::{self, EventWrapper}; use super::app_state::{self, EventWrapper};
use super::uikit::{ use super::uikit::{
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask, UIEvent, UIForceTouchCapability, UIGestureRecognizerState, UIPinchGestureRecognizer,
UIResponder, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView, UIViewController, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITouch, UITouchPhase,
UIWindow, UITouchType, UITraitCollection, UIView,
}; };
use super::window::WindowId; use super::window::WinitUIWindow;
use crate::{ use crate::{
dpi::PhysicalPosition, dpi::PhysicalPosition,
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, event::{Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::ValidOrientations, platform_impl::platform::DEVICE_ID,
platform_impl::platform::{
ffi::{UIRectEdge, UIUserInterfaceIdiom},
window::PlatformSpecificWindowBuilderAttributes,
DeviceId, Fullscreen,
},
window::{WindowAttributes, WindowId as RootWindowId}, window::{WindowAttributes, WindowId as RootWindowId},
}; };
pub struct WinitViewState {
pinch_gesture_recognizer: RefCell<Option<Id<UIPinchGestureRecognizer>>>,
doubletap_gesture_recognizer: RefCell<Option<Id<UITapGestureRecognizer>>>,
rotation_gesture_recognizer: RefCell<Option<Id<UIRotationGestureRecognizer>>>,
}
declare_class!( declare_class!(
pub(crate) struct WinitView; pub(crate) struct WinitView;
@@ -37,6 +38,10 @@ declare_class!(
const NAME: &'static str = "WinitUIView"; const NAME: &'static str = "WinitUIView";
} }
impl DeclaredClass for WinitView {
type Ivars = WinitViewState;
}
unsafe impl WinitView { unsafe impl WinitView {
#[method(drawRect:)] #[method(drawRect:)]
fn draw_rect(&self, rect: CGRect) { fn draw_rect(&self, rect: CGRect) {
@@ -157,6 +162,79 @@ declare_class!(
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) { fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[method(pinchGesture:)]
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
let window = self.window().unwrap();
let phase = match recognizer.state() {
UIGestureRecognizerState::Began => TouchPhase::Started,
UIGestureRecognizerState::Changed => TouchPhase::Moved,
UIGestureRecognizerState::Ended => TouchPhase::Ended,
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
TouchPhase::Cancelled
}
state => panic!("unexpected recognizer state: {:?}", state),
};
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::PinchGesture {
device_id: DEVICE_ID,
delta: recognizer.velocity() as _,
phase,
},
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[method(doubleTapGesture:)]
fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) {
let window = self.window().unwrap();
if recognizer.state() == UIGestureRecognizerState::Ended {
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::DoubleTapGesture {
device_id: DEVICE_ID,
},
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
}
#[method(rotationGesture:)]
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
let window = self.window().unwrap();
let phase = match recognizer.state() {
UIGestureRecognizerState::Began => TouchPhase::Started,
UIGestureRecognizerState::Changed => TouchPhase::Moved,
UIGestureRecognizerState::Ended => TouchPhase::Ended,
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
TouchPhase::Cancelled
}
state => panic!("unexpected recognizer state: {:?}", state),
};
// Flip the velocity to match macOS.
let delta = -recognizer.velocity() as _;
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::RotationGesture {
device_id: DEVICE_ID,
delta,
phase,
},
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
} }
); );
@@ -180,24 +258,73 @@ extern_methods!(
impl WinitView { impl WinitView {
pub(crate) fn new( pub(crate) fn new(
_mtm: MainThreadMarker, _mtm: MainThreadMarker,
_window_attributes: &WindowAttributes, window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect, frame: CGRect,
) -> Id<Self> { ) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] }; 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] };
this.setMultipleTouchEnabled(true); this.setMultipleTouchEnabled(true);
if let Some(scale_factor) = platform_attributes.scale_factor { if let Some(scale_factor) = window_attributes.platform_specific.scale_factor {
this.setContentScaleFactor(scale_factor as _); this.setContentScaleFactor(scale_factor as _);
} }
this this
} }
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
if should_recognize {
if self.ivars().pinch_gesture_recognizer.borrow().is_none() {
let pinch: Id<UIPinchGestureRecognizer> = unsafe {
msg_send_id![UIPinchGestureRecognizer::alloc(), initWithTarget: self, action: sel!(pinchGesture:)]
};
self.addGestureRecognizer(&pinch);
self.ivars().pinch_gesture_recognizer.replace(Some(pinch));
}
} else if let Some(recognizer) = self.ivars().pinch_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer);
}
}
pub(crate) fn recognize_doubletap_gesture(&self, should_recognize: bool) {
if should_recognize {
if self.ivars().doubletap_gesture_recognizer.borrow().is_none() {
let tap: Id<UITapGestureRecognizer> = unsafe {
msg_send_id![UITapGestureRecognizer::alloc(), initWithTarget: self, action: sel!(doubleTapGesture:)]
};
tap.setNumberOfTapsRequired(2);
tap.setNumberOfTouchesRequired(1);
self.addGestureRecognizer(&tap);
self.ivars().doubletap_gesture_recognizer.replace(Some(tap));
}
} else if let Some(recognizer) = self.ivars().doubletap_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer);
}
}
pub(crate) fn recognize_rotation_gesture(&self, should_recognize: bool) {
if should_recognize {
if self.ivars().rotation_gesture_recognizer.borrow().is_none() {
let rotation: Id<UIRotationGestureRecognizer> = unsafe {
msg_send_id![UIRotationGestureRecognizer::alloc(), initWithTarget: self, action: sel!(rotationGesture:)]
};
self.addGestureRecognizer(&rotation);
self.ivars()
.rotation_gesture_recognizer
.replace(Some(rotation));
}
} else if let Some(recognizer) = self.ivars().rotation_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer);
}
}
fn handle_touches(&self, touches: &NSSet<UITouch>) { fn handle_touches(&self, touches: &NSSet<UITouch>) {
let window = self.window().unwrap(); let window = self.window().unwrap();
let uiscreen = window.screen();
let mut touch_events = Vec::new(); let mut touch_events = Vec::new();
let os_supports_force = app_state::os_capabilities().force_touch; let os_supports_force = app_state::os_capabilities().force_touch;
for touch in touches { for touch in touches {
@@ -250,9 +377,7 @@ impl WinitView {
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()), window_id: RootWindowId(window.id()),
event: WindowEvent::Touch(Touch { event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { device_id: DEVICE_ID,
uiscreen: Id::as_ptr(&uiscreen),
}),
id: touch_id, id: touch_id,
location: physical_location, location: physical_location,
force, force,
@@ -264,324 +389,3 @@ impl WinitView {
app_state::handle_nonuser_events(mtm, touch_events); app_state::handle_nonuser_events(mtm, touch_events);
} }
} }
pub struct ViewControllerState {
prefers_status_bar_hidden: Cell<bool>,
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 {
state: IvarDrop<Box<ViewControllerState>, "_state">,
}
mod view_controller_ivars;
unsafe impl ClassType for WinitViewController {
#[inherits(UIResponder, NSObject)]
type Super = UIViewController;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitUIViewController";
}
unsafe impl WinitViewController {
#[method(init)]
unsafe fn init(this: *mut Self) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = msg_send![super(this), init];
this.map(|this| {
// These are set in WinitViewController::new, it's just to set them
// to _something_.
Ivar::write(
&mut this.state,
Box::new(ViewControllerState {
prefers_status_bar_hidden: Cell::new(false),
prefers_home_indicator_auto_hidden: Cell::new(false),
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(
UIRectEdge::NONE,
),
}),
);
NonNull::from(this)
})
}
}
unsafe impl WinitViewController {
#[method(shouldAutorotate)]
fn should_autorotate(&self) -> bool {
true
}
#[method(prefersStatusBarHidden)]
fn prefers_status_bar_hidden(&self) -> bool {
self.state.prefers_status_bar_hidden.get()
}
#[method(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool {
self.state.prefers_home_indicator_auto_hidden.get()
}
#[method(supportedInterfaceOrientations)]
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
self.state.supported_orientations.get()
}
#[method(preferredScreenEdgesDeferringSystemGestures)]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
self.state
.preferred_screen_edges_deferring_system_gestures
.get()
}
}
);
impl WinitViewController {
pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) {
self.state.prefers_status_bar_hidden.set(val);
self.setNeedsStatusBarAppearanceUpdate();
}
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.state.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.state
.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.state.supported_orientations.set(mask);
UIViewController::attemptRotationToDeviceOrientation();
}
pub(crate) fn new(
mtm: MainThreadMarker,
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: &UIView,
) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), init] };
this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
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";
}
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.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";
}
// 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

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

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