Compare commits

...

78 Commits

Author SHA1 Message Date
Osspial
49bcec1d27 Release 0.22.2 (#1570) 2020-05-16 12:27:16 -04:00
Michal Hornický
878c179761 Implement Clone for 'static events (#1478) 2020-05-15 14:58:12 -04:00
j4qfrost
bc19c04339 Fixed changelog line for core-* dependencies (#1561) 2020-05-15 14:32:04 -04:00
curldivergence
c7a33f926b Fixed a couple of typos in repo description (#1568) 2020-05-15 14:31:32 -04:00
j4qfrost
3c38afdb47 Update macOS dependencies (#1554)
* update macos libs

* modify dependency

* changelog

* update core-video-sys version
2020-05-07 22:32:09 -04:00
Jasper De Sutter
b8828105cf add android NDK event loop (#1556)
* add android NDK event loop

* add Android build documentation & cargo-apk to CI

Co-authored-by: David Craven <david@craven.ch>
2020-05-06 15:27:49 +02:00
Francesca Lovebloom
007b195a5e iOS: convert touch positions to physical (#1551) 2020-05-04 15:55:58 -07:00
Osspial
b4c6cdf9a3 Fix several crashes on Windows by heavily simplifying the event loop code (#1496) 2020-05-04 15:14:13 -04:00
Christian Duerr
26775fa0b6 Report mouse motion before click (#1490)
* Report mouse motion before click

This fixes an issue on macOS where a mouse click would be generated,
without ever getting a mouse motion to the position before the click.
This leads to the application thinking the mouse click occurred at a
position other than the actual mouse location.

This happens due to mouse motion above the window not automatically
giving focus to the window, unless it is actually clicked, making it
possible to move the window without motion events.

Fixes #942.

* Add additional mouse motion events

Co-authored-by: Ryan Goldstein <ryan@ryanisaacg.com>
2020-04-26 16:42:45 -04:00
Matthias Fauconneau
114fe9d502 wayland: rework scale factor handling (#1538)
- Always send Resized events in case of scale factor change
- Properly take into account the resize the user can do using the resize event.
2020-04-22 18:00:41 +02:00
Héctor Ramón
54bc41f68b Implement Drop for Proxy on macOS platform (#1526) 2020-04-20 17:48:42 -04:00
Osspial
47ff8d61d1 Document that platforms will display garbage data in the window by default (#1541) 2020-04-20 00:04:30 -04:00
Benjamin Saunders
849b8f5dce Clarify when RedrawRequested is useful (#1529)
Co-Authored-By: Osspial <osspial@gmail.com>
2020-04-19 17:09:08 -04:00
Yanchi Toth
aabe42d252 Preserve with_maximized on windows (#1515) 2020-04-19 15:52:48 -04:00
simlay
78a62ec547 Added more docs.rs targets (#1521) 2020-04-19 15:37:13 -04:00
Benjamin Saunders
6dae994bb4 Mention raw-window-handle in library docs (#1528) 2020-04-19 14:58:58 -04:00
Philippe Renon
4c4d0916fd control_flow example: fix wait_cancelled logic again (#1511) 2020-04-19 13:55:10 -04:00
Ryan G
d5609729cc Bump version to 0.22.1 (#1537)
There are a few relatively important bugfixes with no API impact in the
master branch. We might as well release this as a non-breaking change.
2020-04-17 13:36:42 -04:00
Jurgis
1f24a09570 Implement requestAnimationFrame for web (#1519)
* Use requestAnimationFrame for polling wasm

* Implement `requestAnimationFrame` for stdweb

Co-authored-by: Ryan G <ryanisaacg@users.noreply.github.com>
2020-04-11 15:49:07 -04:00
Ryan G
a8e777a5df Fix a possible double-borrow during event handling (#1512) 2020-04-11 15:20:38 -04:00
Murarth
0bc58f695b Fix warnings (#1530) 2020-04-10 11:29:33 -07:00
Matthew Russo
28023d9f5b upgrades x11-dl to 2.18.5 to fix #376 (#1517)
x11-dl was using std::mem::uninitialized incorrectly and when
rustlang added MaybeUninit and intrinsic panics on UB caused
by improper use of uninitialized (see rust-lang/rust/pull/69922)
it caused issues with X11 initialization. x11-dl pr
erlepereira/x11-rs/pull/101 updated x11-dl to use MaybeUninit
correctly
2020-03-25 22:38:25 -07:00
Murarth
c2aed1979d X11: Fix ResumeTimeReached being fired early (#1505)
* X11: Fix `ResumeTimeReached` being fired early

* Update CHANGELOG.md

Co-authored-by: Osspial <osspial@gmail.com>
2020-03-11 21:54:23 -07:00
Osspial
7e04273719 Replace Travis and Appveyor CI badges with GitHub Actions CI badge 2020-03-09 18:23:03 -04:00
Osspial
0683bdcd42 Add Unreleased category back to changelog 2020-03-09 17:01:34 -04:00
Osspial
29ab0bb629 Correct 0.22.0 date 2020-03-09 16:59:39 -04:00
Christian Duerr
7a9c17a520 Bump version to 0.22.0 (#1500)
There are two PRs I'm aware of that should be relatively trivial to get
merged, which would fix some issues. Other than those, I don't think it
makes sense to wait on anything.

 - Fix Windows crash: https://github.com/rust-windowing/winit/pull/1459
 - Fix macOS mouse reports: https://github.com/rust-windowing/winit/pull/1490

While #1459 seems pretty essential to actually make winit run, #1490 is
much less important and can probably be ignored if there aren't any
resources to merge it.
2020-03-09 16:58:54 -04:00
Kirill Chibisov
b208daa271 Revert "on MacOS, Fix not sending ReceivedCharacter event for s… (#1501)
This reverts commit 9daa0738a9.

This commit introduced other bug #1453 with likely much more common bindings,
so reverting it for now.

Fixes #1453.

Co-authored-by: Osspial <osspial@gmail.com>
2020-03-09 16:57:04 -04:00
Imberflur
e85a80dd65 Fix freeze when pressing modifier keys on Windows (#1503) 2020-03-08 01:22:53 -05:00
Osspial
b1d8ce24e9 Use i32 instead of u32 for position type in WindowEvent::Moved (#1502)
* Use i32 instead of u32 for position type in WindowEvent::Moved

* Mark change as breaking
2020-03-08 00:21:04 -05:00
David Hewitt
098fd5d602 Add ability to create Icons from embedded resources on Windows (#1410)
* Add IconExtWindows trait

* Move changelog entries to unreleased category

Co-authored-by: Osspial <osspial@gmail.com>
2020-03-07 14:42:21 -05:00
Philippe Renon
2f27f64cdb On Windows, fix request_redraw() related panics (#1461)
* On Windows, fix request_redraw() related panics

These panics were introduced by 6a330a2894

Fixes https://github.com/rust-windowing/winit/issues/1391
Fixes https://github.com/rust-windowing/winit/issues/1400
Fixes https://github.com/rust-windowing/winit/issues/1466
Probably fixes other related issues

See https://github.com/rust-windowing/winit/issues/1429

* On Windows, replace all calls to UpdateWindow by calls to InvalidateRgn

This avoids directly sending a WM_PAINT message,
which might cause buffering of RedrawRequested events.

We don't want to buffer RedrawRequested events because:
- we wan't to handle RedrawRequested during processing of WM_PAINT messages
- state transitionning is broken when handling buffered RedrawRequested events

Fixes https://github.com/rust-windowing/winit/issues/1469

* On Windows, panic if we are trying to buffer a RedrawRequested event

* On Windows, move modal loop jumpstart to set_modal_loop() method

This fixes a panic.
Note that the WM_PAINT event is now sent to the modal_redraw_method
which is more correct and avoids an unecessary redraw of the window.

Relates to but does does not fix https://github.com/rust-windowing/winit/issues/1484

* On Window, filter by paint messages when draining paint messages

This seems to prevent PeekMessage from dispatching unrelated sent messages

* Change recently added panic/assert calls with warn calls

This makes the code less panicky...

And actually, winit's Windoww callbacks should not panic
because the panic will unwind into Windows code.

It is currently undefined behavior to unwind from Rust code into foreign code.
See https://doc.rust-lang.org/std/panic/fn.catch_unwind.html

* add comments to clarify WM_PAINT handling in non modal loop

* made redraw_events_cleared more explicit and more comments
2020-03-07 14:04:24 -05:00
Christian Duerr
cbb60d29a2 Remove assertions from Windows dark mode code (#1459)
* Remove assertions from Windows dark mode code

In general, winit should never assert on anything unless it means that
it is impossible to continue the execution of the program. There are
several assertions in the Windows dark mode code where this is not the
case.

Based on surface level inspection, all existing assertions could be
easily replaced with just simple conditional checks, allowing the
execution of the program to proceed with sane default values.

Fixes #1458.

* Add changelog entry

* Format code

* Pass dark mode by mutable reference

* Format code

* Return bool instead of mutable reference

* Fix dark mode success reply

Co-Authored-By: daxpedda <daxpedda@gmail.com>

* Fix dark mode success reply

* Replace magic integers with constants

Co-authored-by: daxpedda <daxpedda@gmail.com>
2020-03-07 13:56:33 -05:00
Murarth
e707052f66 Move ModifiersChanged variant to WindowEvent (#1381)
* Move `ModifiersChanged` variant to `WindowEvent`

* macos: Fix flags_changed for ModifiersChanged variant move

I haven't look too deep at what this does internally, but at least
cargo-check is fully happy now. :)

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* macos: Fire a ModifiersChanged event on window_did_resign_key

From debugging, I determined that macOS' emission of a flagsChanged
around window switching is inconsistent.  It is fair to assume, I think,
that when the user switches windows, they do not expect their former
modifiers state to remain effective; so I think it's best to clear that
state by sending a ModifiersChanged(ModifiersState::empty()).

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* windows: Fix build

I don't know enough about the code to implement the fix as it is done on
this branch, but this commit at least fixes the build.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* windows: Send ModifiersChanged(ModifiersState::empty) on KILLFOCUS

Very similar to the changes made in [1], as focus is lost, send an event
to the window indicating that the modifiers have been released.

It's unclear to me (without a Windows device to test this on) whether
this is necessary, but it certainly ensures that unfocused windows will
have at least received this event, which is an improvement.

[1]: f79f21641a

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* macos: Add a hook to update stale modifiers

Sometimes, `ViewState` and `event` might have different values for their
stored `modifiers` flags.  These are internally stored as a bitmask in
the latter and an enum in the former.

We can check to see if they differ, and if they do, automatically
dispatch an event to update consumers of modifier state as well as the
stored `state.modifiers`.  That's what the hook does.

This hook is then called in the key_down, mouse_entered, mouse_exited,
mouse_click, scroll_wheel, and pressure_change_with_event callbacks,
which each will contain updated modifiers.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* Only call event_mods once when determining whether to update state

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* flags_changed: Memoize window_id collection

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* window_did_resign_key: Remove synthetic ModifiersChanged event

We no longer need to emit this event, since we are checking the state of
our modifiers before emitting most other events.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* mouse_motion: Add a call to update_potentially_stale_modifiers

Now, cover all events (that I can think of, at least) where stale
modifiers might affect how user programs behave.  Effectively, every
human-interface event (keypress, mouse click, keydown, etc.) will cause
a ModifiersChanged event to be fired if something has changed.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* key_up: Add a call to update_potentially_stale_modifiers

We also want to make sure modifiers state is synchronized here, too.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* mouse_motion: Remove update_potentially_stale_modifiers invocation

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* Retry CI

* ViewState: Promote visibility of modifiers to the macos impl

This is so that we can interact with the ViewState directly from the
WindowDelegate.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* window_delegate: Synthetically set modifiers state to empty on resignKey

This logic is implemented similarly on other platforms, so we wish to
regain parity here.  Originally this behavior was implemented to always
fire an event with ModifiersState::empty(), but that was not the best as
it was not necessarily correct and could be a duplicate event.

This solution is perhaps the most elegant possible to implement the
desired behavior of sending a synthetic empty modifiers event when a
window loses focus, trading some safety for interoperation between the
NSWindowDelegate and the NSView (as the objc runtime must now be
consulted in order to acquire access to the ViewState which is "owned"
by the NSView).

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* Check for modifiers change in window events

* Fix modifier changed on macOS

Since the `mouse_entered` function was generating a mouse motion, which
updates the modifier state, a modifiers changed event was incorrectly
generated.

The updating of the modifier state has also been changed to make sure it
consistently happens before events that have a modifier state attached
to it, without happening on any other event.

This of course means that no `CursorMoved` event is generated anymore
when the user enters the window without it being focused, however I'd
say that is consistent with how winit should behave.

* Fix unused variable warning

* Move changelog entry into `Unreleased` section

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Kristofer Rye <kristofer.rye@gmail.com>
Co-authored-by: Christian Duerr <contact@christianduerr.com>
2020-03-06 15:43:55 -07:00
Philippe Renon
71bd6e73ca windows: ignore spurious mouse move messages (#1435)
Fixes https://github.com/rust-windowing/winit/issues/1428
2020-03-06 16:15:49 -05:00
Philippe Renon
b8326f6452 In control_flow example, don't schedule a new WaitUntil if wait was cancelled (#1482) 2020-03-06 10:48:54 -07:00
HeroicKatora
ece2e70a53 Update image to 0.23 (#1485)
Also makes use of a few ergonomics improvements that were introduced or
optimized in the more recent version.
2020-03-03 16:13:53 -07:00
Murarth
2b14ec23d5 Fix GitHub Actions (#1479)
* Replaces `actions/checkout@v1` with `actions/checkout@v2` to get a bug fix
2020-02-25 09:10:31 -07:00
Murarth
9999f53329 X11: Fix deadlock when an error occurs during startup (#1475) 2020-02-19 10:38:59 -07:00
Philippe Renon
522a6e3298 fix issues in wait_until_time_or_msg function (#1423)
also removed unused return value
2020-02-18 19:27:47 -05:00
Kirill Chibisov
76d0dd7ec3 On Wayland, Hide CSD for fullscreen windows (#1473) 2020-02-18 16:58:48 -07:00
daxpedda
d1073dcecb Implement ThemeChanged for web target. (#1462)
* Implement ThemeChanged for web target.

* Add TODO upstream to stdweb.

Co-authored-by: Ryan G <ryanisaacg@users.noreply.github.com>
2020-02-17 14:25:27 -05:00
Héctor Ramón
e88e8bc194 Map UserEvent properly in Event::to_static (#1468) 2020-02-16 10:53:02 -07:00
Philippe Renon
bc29931434 Add an example that calls request_redraw() from a thread (#1467)
reproduces https://github.com/rust-windowing/winit/issues/1466
2020-02-15 11:38:29 -07:00
Philippe Renon
505f312d5f Add new example that demonstrates the different control flow schemes (#1460)
User can switch between Wait, WaitUntil and Poll modes with key '1', '2' and '3' respectivly.
User can toggle request_redraw calls with the 'R' key.

Helpful for testing all control flow modes and use of request_redraw.
2020-02-13 15:20:32 -07:00
Philippe Renon
f0093d3c54 rename dpi_factor to scale_factor where appropriate (#1463)
fixes https://github.com/rust-windowing/winit/issues/1457
2020-02-13 12:41:41 -07:00
Kirill Chibisov
83b60beba6 on Wayland, Add HiDPI cursor support (#1454)
Fixes #727.
2020-02-12 19:48:58 -07:00
hatoo
5f52d7c9d0 On macOS, Fix set_simple_screen to remember frame excluding title bar (#1430)
* On macOS, Fix `set_simple_screen` to remember frame excluding title bar

* Add CHANGELOG
2020-02-12 11:27:11 +03:00
Julien Sanchez
a1b65f7080 Ignore locale if unsupported by X11 backend (#1445)
This restores default portable 'C' locale when target locale is unsupported
by X11 backend (Xlib).

When target locale is unsupported by X11, some locale-dependent Xlib
functions like `XSetLocaleModifiers` fail or have no effect triggering
later failures and panics.

When target locale is not valid, `setLocale` should normally leave the
locale unchanged (`setLocale` returns 'C'). However, in some situations,
locale is accepted by `setLocale` (`setLocale` returns the new locale)
but the accepted locale is unsupported by Xlib (`XSupportsLocale` returns
`false`).

Fix #636
2020-02-09 22:37:06 -07:00
Kirill Chibisov
96df858961 On Wayland, fix color from close_button_icon_color not applying (#1444) 2020-02-08 19:36:44 -07:00
Kirill Chibisov
4eddd1e5bc On Wayland, fix coordinates in touch events when scale factor isn't 1 (#1439)
* On Wayland, fix coordinates in touch events when scale factor isn't 1

* Explicitly state that Wayland is using LogicalPosition internally

* Fix CHANGELOG
2020-02-08 01:25:08 -07:00
Freya Gentz
28f0eb598d Release 0.21.0 (#1440)
* Update CHANGELOG.md

* Update README.md

* Update Cargo.toml

* Update Cargo.toml

* Update README.md

* Update CHANGELOG.md
2020-02-04 19:07:31 -07:00
David Craven
c1eb7f9629 Fix deadlock wayland. (#1438) 2020-02-04 16:46:19 -07:00
Murarth
2f8aa5c52a Remove armv7-apple-ios target from CI (#1433) 2020-02-03 17:42:52 -07:00
hatoo
22dcc19898 Fix set_minimized(true) works only with decorations on macOS (#1411)
* On macOS, Fix set_minimized(true) works only with decorations

* Add CHANGELOG
2020-01-31 18:07:36 +03:00
Christian Duerr
e295104199 Remove Wayland theme intermediates (#1209)
* Remove Wayland theme intermediates

This removes the intermediate struct for passing a Wayland theme to
allow the user direct implementation of the trait.

By passing the trait directly, it is possible for downstream users to
have more freedom with customization without relying on winit to offer
these options as fields.

It should also make maintenance easier, since winit already doesn't
implement all the functions which are offered by the smithay client
toolkit.

* Reimplement SCTK's Theme and ButtonState

* Fix style issues

* Remove public signature

* Format code

* Add change log entry

Co-authored-by: Murarth <murarth@gmail.com>
2020-01-26 19:56:54 -07:00
Murarth
66fe69edd9 Fix warnings on macos (#1419) 2020-01-26 13:55:27 -07:00
Ryan G
fd946feac4 Web backend refactor and documentation (#1415)
The current implementation of the event loop runner has some significant
problems. It can't handle multiple events being emitted at once (for
example, when a keyboard event causes a key input, a text input, and a
modifier change.) It's also relatively easy to introduce bugs for the
different possible control flow states.

The new model separates intentionally emitting a NewEvents (poll
completed, wait completed, init) and emitting a normal event, as well as
providing a method for emitting multiple events in a single call.
2020-01-25 19:04:03 -05:00
Freya Gentz
8856b6ecb7 Remove unused code in X11 backend. (#1416)
Signed-off-by: Freya Gentz <zegentzy@protonmail.com>
2020-01-23 12:42:15 -07:00
Osspial
0ae78db6cb Fix building on Windows 7 and 8 (#1398)
* Fix building on Windows 7 and 8

* Format
2020-01-21 12:43:36 -07:00
David Yamnitsky
3e3bb8a8f1 add hide_application on macos (#1364)
Co-authored-by: Osspial <osspial@gmail.com>
Co-authored-by: Bogaevsky <vbogaevsky@gmail.com>
2020-01-19 16:38:52 -07:00
Steven Sheldon
e48262a797 Simplify code by switching to higher-level dispatch APIs (#1409)
* Switch to higher-level dispatch APIs

* Inline all the functions

* Switch to autoreleasepool from objc
2020-01-19 11:47:55 -07:00
Diggory Hardy
d934f94704 Fix: deadlock when requesting redraw on X11 (#1408) 2020-01-18 10:49:02 -07:00
Ryan G
1fe4a7a4ea Add the ability to pass a prebuilt canvas (#1394)
This allows Winit to take control of existing canvas elements in the
DOM, which is useful for web applications with other content in the
page.
2020-01-15 21:20:14 -05:00
hatoo
9daa0738a9 on MacOS, Fix not sending ReceivedCharacter event for some key combination (#1347)
* MacOS FIX #1267

* Add CHANGELOG

* Remove unnecessary trace!
2020-01-15 00:52:18 +03:00
Philippe Renon
ad7d4939a8 doc: change remaining EventsCleared references to MainEventsCleared (#1390) 2020-01-13 12:15:44 -07:00
Ryan G
c4d07952cb Remove TODOs from the web backen (#1395) 2020-01-13 12:14:25 -07:00
Héctor Ramón
dc302b0db4 Return physical position in CursorMoved on macOS (#1378) 2020-01-12 20:50:34 +03:00
Kirill Chibisov
a6d180cefb On Wayland, fix coordinates in mouse events when scale factor isn't 1 (#1385)
* On Wayland, fix coordinates in mouse events when scale factor isn't 1

* Refactor mouse_focus to be a surface instead of WindowId
2020-01-11 01:45:52 -07:00
Francesca Plebani
1ddceeb063 macOS: Unbundled window activation hack (#1318)
* `ns_string_id_ref` convenience fn

* Unbundled app activation hack

* Greatly improved solution

* Shortened names

* Remove cruft I left here a year ago

* Doc improvements

* Use `AtomicBool` for `activationHackFlag`

* Add CHANGELOG entry

* Doc improvements

* Fix `did_finish_launching` return type + delay more

* More reliable activation checking

* Fix merge goof

* ...fix other merge goof

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Bogaevsky <vbogaevsky@gmail.com>
2020-01-10 16:02:42 -08:00
hatoo
633d0deeae Fix run_return does not return on macOS unless it receives a message (#1380)
* On MacOS, fix `run_return` not exit immediately

* Add CHANGELOG
2020-01-10 18:25:55 +03:00
Murarth
9e3844ddd9 Fix warnings on all platforms (#1383)
* Fix warnings on all platforms

* Also fixed a trait impl bound that I noticed along the way
2020-01-09 22:29:31 -07:00
Benjamin Saunders
4b618bd6a6 Don't discard high-precision cursor position data (#1375)
* Don't discard high-precision cursor position data

Most platforms (X11, wayland, macos, stdweb, ...) provide physical
positions in f64 units, which can contain meaningful fractional
data. For example, this can be empirically observed on modern X11
using a typical laptop touchpad. This is useful for e.g. content
creation tools, where cursor motion might map to brush strokes on a
canvas with higher-than-screen resolution, or positioning of an object
in a vector space.

* Update CHANGELOG.md

Co-Authored-By: Murarth <murarth@gmail.com>

Co-authored-by: Murarth <murarth@gmail.com>
2020-01-09 21:19:50 -07:00
Osspial
09c4ed0694 Remove util from gitignore 2020-01-09 12:24:57 -05:00
Murarth
d15eb04f9e Make docs set control_flow in a more realistic way (#1376) 2020-01-07 22:55:18 -05:00
Bastian Kauschke
02ac7456e4 impl Default for WindowBuilder (#1373) 2020-01-07 14:33:56 -05:00
Murarth
6b0875728c X11: Fix deadlock on window state with certain window events (#1369) 2020-01-06 20:54:22 -07:00
Osspial
6a330a2894 On Windows, fix bug where RedrawRequested would only get emitted every other iteration of the event loop (#1366)
* Fix bug causing RedrawRequested events to only get emitted every other iteration of the event loop.

* Initialize simple_logger in examples.

This PR's primary bug was discovered because a friend of mine reported
that winit was emitting concerning log messages, which I'd never seen
since none of the examples print out the log messages. This addresses
that, to hopefully reduce the chance of bugs going unnoticed in the
future.

* Add changelog entry

* Format
2020-01-06 15:28:58 -05:00
96 changed files with 3615 additions and 2572 deletions

View File

@@ -17,7 +17,7 @@ jobs:
Check_Formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
@@ -37,25 +37,25 @@ jobs:
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' }
- { target: x86_64-apple-darwin, os: macos-latest, }
- { target: x86_64-apple-ios, os: macos-latest, }
- { target: armv7-apple-ios, os: macos-latest, }
- { target: aarch64-apple-ios, os: macos-latest, }
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
# doesn't currently work on Linux.
- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, web: web }
- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, web: web }
- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, cmd: web }
- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, cmd: web }
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0"
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
WEB: ${{ matrix.platform.web }}
CMD: ${{ matrix.platform.cmd }}
runs-on: ${{ matrix.platform.os }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
# Used to cache cargo-web
- name: Cache cargo folder
uses: actions/cache@v1
@@ -71,6 +71,9 @@ jobs:
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android')
run: cargo install cargo-apk
- name: Install cargo-web
continue-on-error: true
if: contains(matrix.platform.target, 'wasm32')
@@ -79,29 +82,35 @@ jobs:
- name: Check documentation
shell: bash
if: matrix.platform.target != 'wasm32-unknown-unknown'
run: cargo doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES
- name: Build
shell: bash
run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features $FEATURES
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features $FEATURES
- name: Build tests
shell: bash
run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES
- name: Run tests
shell: bash
if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32'))
run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features $FEATURES
if: (
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32'))
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features $FEATURES
- name: Build with serde enabled
shell: bash
run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
- name: Build tests with serde enabled
shell: bash
run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
- name: Run tests with serde enabled
shell: bash
if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32'))
run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
if: (
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32'))
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES

View File

@@ -9,7 +9,7 @@ jobs:
Publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable

1
.gitignore vendored
View File

@@ -2,7 +2,6 @@ Cargo.lock
target/
rls/
.vscode/
util/
*~
*.wasm
*.ts

View File

@@ -1,5 +1,60 @@
# Unreleased
# 0.22.2 (2020-05-16)
- Added Clone implementation for 'static events.
- On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`.
- On Windows, fix `WindowBuilder::with_maximized` being ignored.
- On Android, minimal platform support.
- On iOS, touch positions are now properly converted to physical pixels.
- On macOS, updated core-* dependencies and cocoa
# 0.22.1 (2020-04-16)
- On X11, fix `ResumeTimeReached` being fired too early.
- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame`
- On Web, fix a possible panic during event handling
- On macOS, fix `EventLoopProxy` leaking memory for every instance.
# 0.22.0 (2020-03-09)
- On Windows, fix minor timing issue in wait_until_time_or_msg
- On Windows, rework handling of request_redraw() to address panics.
- On macOS, fix `set_simple_screen` to remember frame excluding title bar.
- On Wayland, fix coordinates in touch events when scale factor isn't 1.
- On Wayland, fix color from `close_button_icon_color` not applying.
- Ignore locale if unsupported by X11 backend
- On Wayland, Add HiDPI cursor support
- On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change.
- Fix `Event::to_static` returning `None` for user events.
- On Wayland, Hide CSD for fullscreen windows.
- On Windows, ignore spurious mouse move messages.
- **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`.
- On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource
- Add `BadIcon::OsError` variant for when OS icon functionality fails
- On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode
- Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
- on macOS, fix incorrect ReceivedCharacter events for some key combinations.
- **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`.
- On macOS, a mouse motion event is now generated before every mouse click.
# 0.21.0 (2020-02-04)
- On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows.
- On macOS, fix set_minimized(true) works only with decorations.
- On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`.
- On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
- On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates.
- On macOS, fix issue where unbundled applications would sometimes open without being focused.
- On macOS, fix `run_return` does not return unless it receives a message.
- On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop.
- On X11, fix deadlock on window state when handling certain window events.
- `WindowBuilder` now implements `Default`.
- **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends
- On Wayland, fix coordinates in mouse events when scale factor isn't 1
- On Web, add the ability to provide a custom canvas
- **Breaking:** On Wayland, the `WaylandTheme` struct has been replaced with a `Theme` trait, allowing for extra configuration
# 0.20.0 (2020-01-05)
- On X11, fix `ModifiersChanged` emitting incorrect modifier change events
@@ -20,7 +75,7 @@
- On all platforms except mobile and WASM, implement `Window::set_minimized`.
- On X11, fix `CursorEntered` event being generated for non-winit windows.
- On macOS, fix crash when starting maximized without decorations.
- On macOS, fix application not to terminate on `run_return`.
- On macOS, fix application not terminating on `run_return`.
- On Wayland, fix cursor icon updates on window borders when using CSD.
- On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode.
- On Windows, theme the title bar according to whether the system theme is "Light" or "Dark".

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.20.0"
version = "0.22.2"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2018"
@@ -12,7 +12,9 @@ documentation = "https://docs.rs/winit"
categories = ["gui"]
[package.metadata.docs.rs]
features = ["serde"]
features = ["serde", "web-sys"]
default-target = "x86_64-unknown-linux-gnu"
targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"]
[features]
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
@@ -28,24 +30,26 @@ raw-window-handle = "0.3"
bitflags = "1"
[dev-dependencies]
image = "0.21"
env_logger = "0.5"
image = "0.23"
simple_logger = "1"
[target.'cfg(target_os = "android")'.dependencies.android_glue]
version = "0.2"
[target.'cfg(target_os = "android")'.dependencies]
ndk = "0.1.0"
ndk-sys = "0.1.0"
ndk-glue = "0.1.0"
[target.'cfg(target_os = "ios")'.dependencies]
objc = "0.2.3"
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.19.1"
core-foundation = "0.6"
core-graphics = "0.17.3"
dispatch = "0.1.4"
cocoa = "0.20"
core-foundation = "0.7"
core-graphics = "0.19"
dispatch = "0.2.0"
objc = "0.2.6"
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
version = "0.1.3"
version = "0.1.4"
default_features = false
features = ["display_link"]
@@ -77,8 +81,8 @@ features = [
wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] }
mio = "0.6"
mio-extras = "2.0"
smithay-client-toolkit = "0.6"
x11-dl = "2.18.3"
smithay-client-toolkit = "^0.6.6"
x11-dl = "2.18.5"
percent-encoding = "2.0"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
@@ -101,6 +105,8 @@ features = [
'HtmlCanvasElement',
'HtmlElement',
'KeyboardEvent',
'MediaQueryList',
'MediaQueryListEvent',
'MouseEvent',
'Node',
'PointerEvent',

View File

@@ -109,8 +109,8 @@ If your PR makes notable changes to Winit's features, please update this section
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
- **Gamepad/Joystick events**: Capturing input from gampads and joysticks.
- **Device movement events:**: Capturing input from the device gyroscope and accelerometer.
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows
@@ -149,6 +149,9 @@ If your PR makes notable changes to Winit's features, please update this section
* Getting the device idiom
* Getting the preferred video mode
### Web
* Get if systems preferred color scheme is "dark"
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)

View File

@@ -2,12 +2,11 @@
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![Build Status](https://travis-ci.org/rust-windowing/winit.svg?branch=master)](https://travis-ci.org/rust-windowing/winit)
[![Build status](https://ci.appveyor.com/api/projects/status/hr89but4x1n3dphq/branch/master?svg=true)](https://ci.appveyor.com/project/Osspial/winit/branch/master)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
```toml
[dependencies]
winit = "0.20.0"
winit = "0.22.2"
```
## [Documentation](https://docs.rs/winit)
@@ -46,12 +45,14 @@ fn main() {
let window = WindowBuilder::new().build(&event_loop).unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
_ => (),
}
});
}
@@ -75,3 +76,22 @@ Building a binary will yield a `.js` file. In order to use it in an HTML file, y
the element of the `<canvas>` element (in the example you would retrieve it via `document.getElementById("my_id")`).
More information [here](https://kripken.github.io/emscripten-site/docs/api_reference/module.html).
- Make sure that you insert the `.js` file generated by Rust after the `Module` variable is created.
#### Android
This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
```toml
[[example]]
name = "request_redraw_threaded"
crate-type = ["cdylib"]
```
And add this to the example file to add the native activity glue:
```rust
#[cfg(target_os = "android")]
ndk_glue::ndk_glue!(main);
```
And run the application with `cargo apk run --example request_redraw_threaded`

113
examples/control_flow.rs Normal file
View File

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

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
@@ -37,6 +38,7 @@ fn main() {
_ => (),
}
}
WindowEvent::ModifiersChanged(m) => modifiers = m,
_ => (),
},
Event::DeviceEvent { event, .. } => match event {
@@ -45,7 +47,6 @@ fn main() {
ElementState::Pressed => println!("mouse button {} pressed", button),
ElementState::Released => println!("mouse button {} released", button),
},
DeviceEvent::ModifiersChanged(m) => modifiers = m,
_ => (),
},
_ => (),

View File

@@ -11,6 +11,7 @@ fn main() {
Timer,
}
simple_logger::init().unwrap();
let event_loop = EventLoop::<CustomEvent>::with_user_event();
let _window = WindowBuilder::new()

View File

@@ -5,6 +5,7 @@ use winit::monitor::{MonitorHandle, VideoMode};
use winit::window::{Fullscreen, WindowBuilder};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()

View File

@@ -6,6 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();

View File

@@ -5,6 +5,7 @@ use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -1,6 +1,7 @@
use winit::{event_loop::EventLoop, window::WindowBuilder};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();

View File

@@ -1,7 +1,5 @@
#[cfg(not(target_arch = "wasm32"))]
fn main() {
extern crate env_logger;
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use winit::{
@@ -14,7 +12,7 @@ fn main() {
const WINDOW_COUNT: usize = 3;
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
env_logger::init();
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
for _ in 0..WINDOW_COUNT {

View File

@@ -6,6 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let mut windows = HashMap::new();

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -0,0 +1,39 @@
use std::{thread, time};
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
thread::spawn(move || loop {
thread::sleep(time::Duration::from_secs(1));
window.request_redraw();
});
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
_ => (),
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
}
_ => (),
}
});
}

View File

@@ -6,6 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let mut resizable = false;

View File

@@ -7,6 +7,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -1,6 +1,7 @@
use winit::event_loop::EventLoop;
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let monitor = event_loop.primary_monitor();

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -8,6 +8,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -7,6 +7,8 @@ use winit::{
};
fn main() {
simple_logger::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
@@ -43,13 +45,11 @@ fn main() {
fn load_icon(path: &Path) -> Icon {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path).expect("Failed to open icon path");
use image::{GenericImageView, Pixel};
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba();
let (width, height) = image.dimensions();
let mut rgba = Vec::with_capacity((width * height) as usize * 4);
for (_, _, pixel) in image.pixels() {
rgba.extend_from_slice(&pixel.to_rgba().data);
}
let rgba = image.into_raw();
(rgba, width, height)
};
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")

View File

@@ -18,6 +18,7 @@ fn main() {
};
let mut event_loop = EventLoop::new();
simple_logger::init().unwrap();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
@@ -27,6 +28,8 @@ fn main() {
while !quit {
event_loop.run_return(|event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{:?}", event);
@@ -42,7 +45,7 @@ fn main() {
Event::MainEventsCleared => {
*control_flow = ControlFlow::Exit;
}
_ => *control_flow = ControlFlow::Wait,
_ => (),
}
});

View File

@@ -151,8 +151,8 @@ impl Pixel for f64 {
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
/// otherwise, you risk panics.
#[inline]
pub fn validate_scale_factor(dpi_factor: f64) -> bool {
dpi_factor.is_sign_positive() && dpi_factor.is_normal()
pub fn validate_scale_factor(scale_factor: f64) -> bool {
scale_factor.is_sign_positive() && scale_factor.is_normal()
}
/// A position represented in logical pixels.
@@ -178,16 +178,16 @@ impl<P: Pixel> LogicalPosition<P> {
#[inline]
pub fn from_physical<T: Into<PhysicalPosition<X>>, X: Pixel>(
physical: T,
dpi_factor: f64,
scale_factor: f64,
) -> Self {
physical.into().to_logical(dpi_factor)
physical.into().to_logical(scale_factor)
}
#[inline]
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<X> {
assert!(validate_scale_factor(dpi_factor));
let x = self.x.into() * dpi_factor;
let y = self.y.into() * dpi_factor;
pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<X> {
assert!(validate_scale_factor(scale_factor));
let x = self.x.into() * scale_factor;
let y = self.y.into() * scale_factor;
PhysicalPosition::new(x, y).cast()
}
@@ -243,16 +243,16 @@ impl<P: Pixel> PhysicalPosition<P> {
#[inline]
pub fn from_logical<T: Into<LogicalPosition<X>>, X: Pixel>(
logical: T,
dpi_factor: f64,
scale_factor: f64,
) -> Self {
logical.into().to_physical(dpi_factor)
logical.into().to_physical(scale_factor)
}
#[inline]
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<X> {
assert!(validate_scale_factor(dpi_factor));
let x = self.x.into() / dpi_factor;
let y = self.y.into() / dpi_factor;
pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalPosition<X> {
assert!(validate_scale_factor(scale_factor));
let x = self.x.into() / scale_factor;
let y = self.y.into() / scale_factor;
LogicalPosition::new(x, y).cast()
}
@@ -306,15 +306,18 @@ impl<P> LogicalSize<P> {
impl<P: Pixel> LogicalSize<P> {
#[inline]
pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(physical: T, dpi_factor: f64) -> Self {
physical.into().to_logical(dpi_factor)
pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(
physical: T,
scale_factor: f64,
) -> Self {
physical.into().to_logical(scale_factor)
}
#[inline]
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<X> {
assert!(validate_scale_factor(dpi_factor));
let width = self.width.into() * dpi_factor;
let height = self.height.into() * dpi_factor;
pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalSize<X> {
assert!(validate_scale_factor(scale_factor));
let width = self.width.into() * scale_factor;
let height = self.height.into() * scale_factor;
PhysicalSize::new(width, height).cast()
}
@@ -368,15 +371,15 @@ impl<P> PhysicalSize<P> {
impl<P: Pixel> PhysicalSize<P> {
#[inline]
pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, dpi_factor: f64) -> Self {
logical.into().to_physical(dpi_factor)
pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, scale_factor: f64) -> Self {
logical.into().to_physical(scale_factor)
}
#[inline]
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalSize<X> {
assert!(validate_scale_factor(dpi_factor));
let width = self.width.into() / dpi_factor;
let height = self.height.into() / dpi_factor;
pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalSize<X> {
assert!(validate_scale_factor(scale_factor));
let width = self.width.into() / scale_factor;
let height = self.height.into() / scale_factor;
LogicalSize::new(width, height).cast()
}
@@ -426,17 +429,17 @@ impl Size {
size.into()
}
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalSize<P> {
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalSize<P> {
match *self {
Size::Physical(size) => size.to_logical(dpi_factor),
Size::Physical(size) => size.to_logical(scale_factor),
Size::Logical(size) => size.cast(),
}
}
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<P> {
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalSize<P> {
match *self {
Size::Physical(size) => size.cast(),
Size::Logical(size) => size.to_physical(dpi_factor),
Size::Logical(size) => size.to_physical(scale_factor),
}
}
}
@@ -468,17 +471,17 @@ impl Position {
position.into()
}
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<P> {
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalPosition<P> {
match *self {
Position::Physical(position) => position.to_logical(dpi_factor),
Position::Physical(position) => position.to_logical(scale_factor),
Position::Logical(position) => position.cast(),
}
}
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<P> {
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<P> {
match *self {
Position::Physical(position) => position.cast(),
Position::Logical(position) => position.to_physical(dpi_factor),
Position::Logical(position) => position.to_physical(scale_factor),
}
}
}

View File

@@ -81,10 +81,11 @@ pub enum Event<'a, T: 'static> {
///
/// This event is useful as a place to put your code that should be run after all
/// state-changing events have been handled and you want to do stuff (updating state, performing
/// calculations, etc) that happens as the "main body" of your event loop. If your program draws
/// graphics, it's usually better to do it in response to
/// calculations, etc) that happens as the "main body" of your event loop. If your program only draws
/// graphics when something changes, it's usually better to do it in response to
/// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted
/// immediately after this event.
/// immediately after this event. Programs that draw graphics continuously, like most games,
/// can render here unconditionally for simplicity.
MainEventsCleared,
/// Emitted after `MainEventsCleared` when a window should be redrawn.
@@ -97,6 +98,9 @@ pub enum Event<'a, T: 'static> {
///
/// During each iteration of the event loop, Winit will aggregate duplicate redraw requests
/// into a single event, to help avoid duplicating rendering work.
///
/// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless
/// something changes, like most non-game GUIs.
RedrawRequested(WindowId),
/// Emitted after all `RedrawRequested` events have been processed and control flow is about to
@@ -114,6 +118,30 @@ pub enum Event<'a, T: 'static> {
LoopDestroyed,
}
impl<T: Clone> Clone for Event<'static, T> {
fn clone(&self) -> Self {
use self::Event::*;
match self {
WindowEvent { window_id, event } => WindowEvent {
window_id: *window_id,
event: event.clone(),
},
UserEvent(event) => UserEvent(event.clone()),
DeviceEvent { device_id, event } => DeviceEvent {
device_id: *device_id,
event: event.clone(),
},
NewEvents(cause) => NewEvents(cause.clone()),
MainEventsCleared => MainEventsCleared,
RedrawRequested(wid) => RedrawRequested(*wid),
RedrawEventsCleared => RedrawEventsCleared,
LoopDestroyed => LoopDestroyed,
Suspended => Suspended,
Resumed => Resumed,
}
}
}
impl<'a, T> Event<'a, T> {
pub fn map_nonuser_event<U>(self) -> Result<Event<'a, U>, Event<'a, T>> {
use self::Event::*;
@@ -139,7 +167,7 @@ impl<'a, T> Event<'a, T> {
WindowEvent { window_id, event } => event
.to_static()
.map(|event| WindowEvent { window_id, event }),
UserEvent(_) => None,
UserEvent(event) => Some(UserEvent(event)),
DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }),
NewEvents(cause) => Some(NewEvents(cause)),
MainEventsCleared => Some(MainEventsCleared),
@@ -185,7 +213,7 @@ pub enum WindowEvent<'a> {
Resized(PhysicalSize<u32>),
/// The position of the window has changed. Contains the window's new position.
Moved(PhysicalPosition<u32>),
Moved(PhysicalPosition<i32>),
/// The window has been requested to close.
CloseRequested,
@@ -235,6 +263,13 @@ pub enum WindowEvent<'a> {
is_synthetic: bool,
},
/// The keyboard modifiers have changed.
///
/// Platform-specific behavior:
/// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an
/// issue, and it should get fixed - but it's the current state of the API.
ModifiersChanged(ModifiersState),
/// The cursor has moved on the window.
CursorMoved {
device_id: DeviceId,
@@ -242,8 +277,8 @@ pub enum WindowEvent<'a> {
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
position: PhysicalPosition<i32>,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
position: PhysicalPosition<f64>,
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
@@ -258,7 +293,7 @@ pub enum WindowEvent<'a> {
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
@@ -267,7 +302,7 @@ pub enum WindowEvent<'a> {
device_id: DeviceId,
state: ElementState,
button: MouseButton,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
@@ -319,6 +354,97 @@ pub enum WindowEvent<'a> {
ThemeChanged(Theme),
}
impl Clone for WindowEvent<'static> {
fn clone(&self) -> Self {
use self::WindowEvent::*;
return match self {
Resized(size) => Resized(size.clone()),
Moved(pos) => Moved(pos.clone()),
CloseRequested => CloseRequested,
Destroyed => Destroyed,
DroppedFile(file) => DroppedFile(file.clone()),
HoveredFile(file) => HoveredFile(file.clone()),
HoveredFileCancelled => HoveredFileCancelled,
ReceivedCharacter(c) => ReceivedCharacter(*c),
Focused(f) => Focused(*f),
KeyboardInput {
device_id,
input,
is_synthetic,
} => KeyboardInput {
device_id: *device_id,
input: *input,
is_synthetic: *is_synthetic,
},
ModifiersChanged(modifiers) => ModifiersChanged(modifiers.clone()),
#[allow(deprecated)]
CursorMoved {
device_id,
position,
modifiers,
} => CursorMoved {
device_id: *device_id,
position: *position,
modifiers: *modifiers,
},
CursorEntered { device_id } => CursorEntered {
device_id: *device_id,
},
CursorLeft { device_id } => CursorLeft {
device_id: *device_id,
},
#[allow(deprecated)]
MouseWheel {
device_id,
delta,
phase,
modifiers,
} => MouseWheel {
device_id: *device_id,
delta: *delta,
phase: *phase,
modifiers: *modifiers,
},
#[allow(deprecated)]
MouseInput {
device_id,
state,
button,
modifiers,
} => MouseInput {
device_id: *device_id,
state: *state,
button: *button,
modifiers: *modifiers,
},
TouchpadPressure {
device_id,
pressure,
stage,
} => TouchpadPressure {
device_id: *device_id,
pressure: *pressure,
stage: *stage,
},
AxisMotion {
device_id,
axis,
value,
} => AxisMotion {
device_id: *device_id,
axis: *axis,
value: *value,
},
Touch(touch) => Touch(*touch),
ThemeChanged(theme) => ThemeChanged(theme.clone()),
ScaleFactorChanged { .. } => {
unreachable!("Static event can't be about scale factor changing")
}
};
}
}
impl<'a> WindowEvent<'a> {
pub fn to_static(self) -> Option<WindowEvent<'static>> {
use self::WindowEvent::*;
@@ -341,6 +467,7 @@ impl<'a> WindowEvent<'a> {
input,
is_synthetic,
}),
ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)),
#[allow(deprecated)]
CursorMoved {
device_id,
@@ -464,16 +591,6 @@ pub enum DeviceEvent {
Key(KeyboardInput),
/// The keyboard modifiers have changed.
///
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
///
/// Platform-specific behavior:
/// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an
/// issue, and it should get fixed - but it's the current state of the API.
ModifiersChanged(ModifiersState),
Text {
codepoint: char,
},
@@ -502,7 +619,7 @@ pub struct KeyboardInput {
///
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
pub modifiers: ModifiersState,
}
@@ -807,8 +924,10 @@ pub enum VirtualKeyCode {
Multiply,
Mute,
MyComputer,
NavigateForward, // also called "Prior"
NavigateBackward, // also called "Next"
// also called "Next"
NavigateForward,
// also called "Prior"
NavigateBackward,
NextTrack,
NoConvert,
NumpadComma,

View File

@@ -72,7 +72,8 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
/// whether or not new events are available to process. For web, events are sent when
/// `requestAnimationFrame` fires.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
Wait,
@@ -215,14 +216,10 @@ impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopClosed<T>(pub T);
impl<T: fmt::Debug> fmt::Display for EventLoopClosed<T> {
impl<T> fmt::Display for EventLoopClosed<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", error::Error::description(self))
f.write_str("Tried to wake up a closed `EventLoop`")
}
}
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {
fn description(&self) -> &str {
"Tried to wake up a closed `EventLoop`"
}
}
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}

View File

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

View File

@@ -73,14 +73,18 @@
//! // Application update code.
//!
//! // Queue a RedrawRequested event.
//! //
//! // You only need to call this if you've determined that you need to redraw, in
//! // applications which do not always need to. Applications that redraw continuously
//! // can just render here instead.
//! window.request_redraw();
//! },
//! Event::RedrawRequested(_) => {
//! // Redraw the application.
//! //
//! // It's preferrable to render in this event rather than in MainEventsCleared, since
//! // rendering in here allows the program to gracefully handle redraws requested
//! // by the OS.
//! // It's preferable for applications that do not render continuously to render in
//! // this event rather than in MainEventsCleared, since rendering in here allows
//! // the program to gracefully handle redraws requested by the OS.
//! },
//! _ => ()
//! }
@@ -94,8 +98,15 @@
//! # Drawing on the window
//!
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
//! retrieve the raw handle of the window (see the [`platform`] module), which in turn allows you
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//! retrieve the raw handle of the window (see the [`platform`] module and/or the
//! [`raw_window_handle`] method), which in turn allows you to create an
//! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//!
//! Note that many platforms will display garbage data in the window's client area if the
//! application doesn't render anything to the window by the time the desktop compositor is ready to
//! display the window to the user. If you notice this happening, you should create the window with
//! [`visible` set to `false`](crate::window::WindowBuilder::with_visible) and explicitly make the
//! window visible only once you're ready to render into it.
//!
//! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return
@@ -116,6 +127,7 @@
//! [`UserEvent`]: event::Event::UserEvent
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
//! [`platform`]: platform
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
#![deny(rust_2018_idioms)]
#![deny(intra_doc_link_resolution_failure)]
@@ -123,6 +135,7 @@
#[allow(unused_imports)]
#[macro_use]
extern crate lazy_static;
#[allow(unused_imports)]
#[macro_use]
extern crate log;
#[cfg(feature = "serde")]

View File

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

View File

@@ -4,6 +4,7 @@ use std::os::raw::c_void;
use crate::{
dpi::LogicalSize,
event_loop::EventLoopWindowTarget,
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
@@ -209,3 +210,17 @@ impl MonitorHandleExtMacOS for MonitorHandle {
self.inner.ns_screen().map(|s| s as *mut c_void)
}
}
/// Additional methods on `EventLoopWindowTarget` that are specific to macOS.
pub trait EventLoopWindowTargetExtMacOS {
/// Hide the entire application. In most applications this is typically triggered with Command-H.
fn hide_application(&self);
}
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
fn hide_application(&self) {
let cls = objc::runtime::Class::get("NSApplication").unwrap();
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
unsafe { msg_send![app, hide: 0] }
}
}

View File

@@ -2,7 +2,7 @@
use std::{os::raw, ptr, sync::Arc};
use smithay_client_toolkit::window::{ButtonState, Theme};
use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme};
use crate::{
dpi::Size,
@@ -23,74 +23,6 @@ pub use crate::platform_impl::x11;
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
/// Theme for wayland client side decorations
///
/// Colors must be in ARGB8888 format
pub struct WaylandTheme {
/// Primary color when the window is focused
pub primary_active: [u8; 4],
/// Primary color when the window is unfocused
pub primary_inactive: [u8; 4],
/// Secondary color when the window is focused
pub secondary_active: [u8; 4],
/// Secondary color when the window is unfocused
pub secondary_inactive: [u8; 4],
/// Close button color when hovered over
pub close_button_hovered: [u8; 4],
/// Close button color
pub close_button: [u8; 4],
/// Close button color when hovered over
pub maximize_button_hovered: [u8; 4],
/// Maximize button color
pub maximize_button: [u8; 4],
/// Minimize button color when hovered over
pub minimize_button_hovered: [u8; 4],
/// Minimize button color
pub minimize_button: [u8; 4],
}
struct WaylandThemeObject(WaylandTheme);
impl Theme for WaylandThemeObject {
fn get_primary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.primary_active
} else {
self.0.primary_inactive
}
}
// Used for division line
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.secondary_active
} else {
self.0.secondary_inactive
}
}
fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.close_button_hovered,
_ => self.0.close_button,
}
}
fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.maximize_button_hovered,
_ => self.0.maximize_button,
}
}
fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.minimize_button_hovered,
_ => self.0.minimize_button,
}
}
}
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
pub trait EventLoopWindowTargetExtUnix {
/// True if the `EventLoopWindowTarget` uses Wayland.
@@ -275,7 +207,7 @@ pub trait WindowExtUnix {
fn wayland_display(&self) -> Option<*mut raw::c_void>;
/// Sets the color theme of the client side window decorations on wayland
fn set_wayland_theme(&self, theme: WaylandTheme);
fn set_wayland_theme<T: Theme>(&self, theme: T);
/// Check if the window is ready for drawing
///
@@ -353,9 +285,9 @@ impl WindowExtUnix for Window {
}
#[inline]
fn set_wayland_theme(&self, theme: WaylandTheme) {
fn set_wayland_theme<T: Theme>(&self, theme: T) {
match self.window {
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)),
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)),
_ => {}
}
}
@@ -461,3 +393,97 @@ impl MonitorHandleExtUnix for MonitorHandle {
self.inner.native_identifier()
}
}
/// Wrapper for implementing SCTK's theme trait.
struct WaylandTheme<T: Theme>(T);
pub trait Theme: Send + 'static {
/// Primary color of the scheme.
fn primary_color(&self, window_active: bool) -> [u8; 4];
/// Secondary color of the scheme.
fn secondary_color(&self, window_active: bool) -> [u8; 4];
/// Color for the close button.
fn close_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the close button, defaults to the secondary color.
#[allow(unused_variables)]
fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
}
/// Background color for the maximize button.
fn maximize_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the maximize button, defaults to the secondary color.
#[allow(unused_variables)]
fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
}
/// Background color for the minimize button.
fn minimize_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the minimize button, defaults to the secondary color.
#[allow(unused_variables)]
fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
}
}
impl<T: Theme> SCTKTheme for WaylandTheme<T> {
fn get_primary_color(&self, active: bool) -> [u8; 4] {
self.0.primary_color(active)
}
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
self.0.secondary_color(active)
}
fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.close_button_color(ButtonState::from_sctk(status))
}
fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0
.close_button_icon_color(ButtonState::from_sctk(status))
}
fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.maximize_button_color(ButtonState::from_sctk(status))
}
fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0
.maximize_button_icon_color(ButtonState::from_sctk(status))
}
fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.minimize_button_color(ButtonState::from_sctk(status))
}
fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0
.minimize_button_icon_color(ButtonState::from_sctk(status))
}
}
pub enum ButtonState {
/// Button is being hovered over by pointer.
Hovered,
/// Button is not being hovered over by pointer.
Idle,
/// Button is disabled.
Disabled,
}
impl ButtonState {
fn from_sctk(button_state: SCTKButtonState) -> Self {
match button_state {
SCTKButtonState::Hovered => Self::Hovered,
SCTKButtonState::Idle => Self::Idle,
SCTKButtonState::Disabled => Self::Disabled,
}
}
}

View File

@@ -3,7 +3,10 @@
//! The web target does not automatically insert the canvas element object into the web page, to
//! allow end users to determine how the page should be laid out. Use the `WindowExtStdweb` or
//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the
//! Window.
//! Window. Alternatively, use the `WindowBuilderExtStdweb` or `WindowBuilderExtWebSys` to provide
//! your own canvas.
use crate::window::WindowBuilder;
#[cfg(feature = "stdweb")]
use stdweb::web::html_element::CanvasElement;
@@ -11,6 +14,9 @@ use stdweb::web::html_element::CanvasElement;
#[cfg(feature = "stdweb")]
pub trait WindowExtStdweb {
fn canvas(&self) -> CanvasElement;
/// Whether the browser reports the preferred color scheme to be "dark".
fn is_dark_mode(&self) -> bool;
}
#[cfg(feature = "web-sys")]
@@ -19,4 +25,35 @@ use web_sys::HtmlCanvasElement;
#[cfg(feature = "web-sys")]
pub trait WindowExtWebSys {
fn canvas(&self) -> HtmlCanvasElement;
/// Whether the browser reports the preferred color scheme to be "dark".
fn is_dark_mode(&self) -> bool;
}
#[cfg(feature = "stdweb")]
pub trait WindowBuilderExtStdweb {
fn with_canvas(self, canvas: Option<CanvasElement>) -> Self;
}
#[cfg(feature = "stdweb")]
impl WindowBuilderExtStdweb for WindowBuilder {
fn with_canvas(mut self, canvas: Option<CanvasElement>) -> Self {
self.platform_specific.canvas = canvas;
self
}
}
#[cfg(feature = "web-sys")]
pub trait WindowBuilderExtWebSys {
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
}
#[cfg(feature = "web-sys")]
impl WindowBuilderExtWebSys for WindowBuilder {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.canvas = canvas;
self
}
}

View File

@@ -1,16 +1,19 @@
#![cfg(target_os = "windows")]
use std::os::raw::c_void;
use std::path::Path;
use libc;
use winapi::shared::minwindef::WORD;
use winapi::shared::windef::HWND;
use crate::{
dpi::PhysicalSize,
event::DeviceId,
event_loop::EventLoop,
monitor::MonitorHandle,
platform_impl::EventLoop as WindowsEventLoop,
window::{Icon, Window, WindowBuilder},
platform_impl::{EventLoop as WindowsEventLoop, WinIcon},
window::{BadIcon, Icon, Window, WindowBuilder},
};
/// Additional methods on `EventLoop` that are specific to Windows.
@@ -171,3 +174,40 @@ impl DeviceIdExtWindows for DeviceId {
self.0.persistent_identifier()
}
}
/// Additional methods on `Icon` that are specific to Windows.
pub trait IconExtWindows: Sized {
/// Create an icon from a file path.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
-> Result<Self, BadIcon>;
/// Create an icon from a resource embedded in this executable or library.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
}
impl IconExtWindows for Icon {
fn from_path<P: AsRef<Path>>(
path: P,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
let win_icon = WinIcon::from_path(path, size)?;
Ok(Icon { inner: win_icon })
}
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon })
}
}

View File

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

View File

@@ -1,448 +1,519 @@
#![cfg(target_os = "android")]
extern crate android_glue;
mod ffi;
use std::{
cell::RefCell,
collections::VecDeque,
fmt,
os::raw::c_void,
sync::mpsc::{channel, Receiver},
};
use crate::{
error::{ExternalError, NotSupportedError},
events::{Touch, TouchPhase},
window::MonitorHandle as RootMonitorHandle,
CreationError, CursorIcon, Event, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize,
WindowAttributes, WindowEvent, WindowId as RootWindowId,
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error, event,
event_loop::{self, ControlFlow},
monitor, window,
};
use ndk::{
configuration::Configuration,
event::{InputEvent, MotionAction},
looper::{ForeignLooper, Poll, ThreadLooper},
};
use ndk_glue::{Event, Rect};
use std::{
collections::VecDeque,
sync::{Arc, Mutex, RwLock},
time::{Duration, Instant},
};
use raw_window_handle::{android::AndroidHandle, RawWindowHandle};
use CreationError::OsError;
pub type OsError = std::io::Error;
pub struct EventLoop {
event_rx: Receiver<android_glue::Event>,
suspend_callback: RefCell<Option<Box<dyn Fn(bool) -> ()>>>,
lazy_static! {
static ref CONFIG: RwLock<Configuration> = RwLock::new(Configuration::new());
}
#[derive(Clone)]
pub struct EventLoopProxy;
enum EventSource {
Callback,
InputQueue,
User,
}
impl EventLoop {
pub fn new() -> EventLoop {
let (tx, rx) = channel();
android_glue::add_sender(tx);
EventLoop {
event_rx: rx,
suspend_callback: Default::default(),
fn poll(poll: Poll) -> Option<EventSource> {
match poll {
Poll::Event { data, .. } => match data as usize {
0 => Some(EventSource::Callback),
1 => Some(EventSource::InputQueue),
_ => unreachable!(),
},
Poll::Timeout => None,
Poll::Wake => Some(EventSource::User),
Poll::Callback => unreachable!(),
}
}
pub struct EventLoop<T: 'static> {
window_target: event_loop::EventLoopWindowTarget<T>,
user_queue: Arc<Mutex<VecDeque<T>>>,
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> Self {
Self {
window_target: event_loop::EventLoopWindowTarget {
p: EventLoopWindowTarget {
_marker: std::marker::PhantomData,
},
_marker: std::marker::PhantomData,
},
user_queue: Default::default(),
}
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorHandle);
rb
pub fn run<F>(self, mut event_handler: F) -> !
where
F: 'static
+ FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
{
let mut cf = ControlFlow::default();
let mut first_event = None;
let mut start_cause = event::StartCause::Init;
let looper = ThreadLooper::for_thread().unwrap();
let mut running = false;
loop {
event_handler(
event::Event::NewEvents(start_cause),
self.window_target(),
&mut cf,
);
let mut redraw = false;
let mut resized = false;
match first_event.take() {
Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() {
Event::WindowCreated => {
event_handler(event::Event::Resumed, self.window_target(), &mut cf);
}
Event::WindowResized => resized = true,
Event::WindowRedrawNeeded => redraw = true,
Event::WindowDestroyed => {
event_handler(event::Event::Suspended, self.window_target(), &mut cf);
}
Event::Pause => running = false,
Event::Resume => running = true,
Event::ConfigChanged => {
let am = ndk_glue::native_activity().asset_manager();
let config = Configuration::from_asset_manager(&am);
let old_scale_factor = MonitorHandle.scale_factor();
*CONFIG.write().unwrap() = config;
let scale_factor = MonitorHandle.scale_factor();
if (scale_factor - old_scale_factor).abs() < f64::EPSILON {
let mut size = MonitorHandle.size();
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::ScaleFactorChanged {
new_inner_size: &mut size,
scale_factor,
},
};
event_handler(event, self.window_target(), &mut cf);
}
}
_ => {}
},
Some(EventSource::InputQueue) => {
if let Some(input_queue) = ndk_glue::input_queue().as_ref() {
while let Some(event) = input_queue.get_event() {
println!("event {:?}", event);
if let Some(event) = input_queue.pre_dispatch(event) {
let window_id = window::WindowId(WindowId);
let device_id = event::DeviceId(DeviceId);
match &event {
InputEvent::MotionEvent(motion_event) => {
let phase = match motion_event.action() {
MotionAction::Down => Some(event::TouchPhase::Started),
MotionAction::Up => Some(event::TouchPhase::Ended),
MotionAction::Move => Some(event::TouchPhase::Moved),
MotionAction::Cancel => {
Some(event::TouchPhase::Cancelled)
}
_ => None, // TODO mouse events
};
let pointer = motion_event.pointer_at_index(0);
let location = PhysicalPosition {
x: pointer.x() as _,
y: pointer.y() as _,
};
if let Some(phase) = phase {
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: 0,
force: None,
}),
};
event_handler(event, self.window_target(), &mut cf);
}
}
InputEvent::KeyEvent(_) => {} // TODO
};
input_queue.finish_event(event, true);
}
}
}
}
Some(EventSource::User) => {
let mut user_queue = self.user_queue.lock().unwrap();
while let Some(event) = user_queue.pop_front() {
event_handler(
event::Event::UserEvent(event),
self.window_target(),
&mut cf,
);
}
}
None => {}
}
event_handler(
event::Event::MainEventsCleared,
self.window_target(),
&mut cf,
);
if resized && running {
let size = MonitorHandle.size();
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Resized(size),
};
event_handler(event, self.window_target(), &mut cf);
}
if redraw && running {
let event = event::Event::RedrawRequested(window::WindowId(WindowId));
event_handler(event, self.window_target(), &mut cf);
}
event_handler(
event::Event::RedrawEventsCleared,
self.window_target(),
&mut cf,
);
match cf {
ControlFlow::Exit => panic!(),
ControlFlow::Poll => {
start_cause = event::StartCause::Poll;
}
ControlFlow::Wait => {
first_event = poll(looper.poll_all().unwrap());
start_cause = event::StartCause::WaitCancelled {
start: Instant::now(),
requested_resume: None,
}
}
ControlFlow::WaitUntil(instant) => {
let start = Instant::now();
let duration = if instant <= start {
Duration::default()
} else {
instant - start
};
first_event = poll(looper.poll_all_timeout(duration).unwrap());
start_cause = if first_event.is_some() {
event::StartCause::WaitCancelled {
start,
requested_resume: Some(instant),
}
} else {
event::StartCause::ResumeTimeReached {
start,
requested_resume: instant,
}
}
}
}
}
}
pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget<T> {
&self.window_target
}
#[inline]
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}
pub fn poll_events<F>(&mut self, mut callback: F)
where
F: FnMut(::Event),
{
while let Ok(event) = self.event_rx.try_recv() {
let e = match event {
android_glue::Event::EventMotion(motion) => {
let dpi_factor = MonitorHandle.scale_factor();
let location = LogicalPosition::from_physical(
(motion.x as f64, motion.y as f64),
dpi_factor,
);
Some(Event::WindowEvent {
window_id: RootWindowId(WindowId),
event: WindowEvent::Touch(Touch {
phase: match motion.action {
android_glue::MotionAction::Down => TouchPhase::Started,
android_glue::MotionAction::Move => TouchPhase::Moved,
android_glue::MotionAction::Up => TouchPhase::Ended,
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
},
location,
force: None, // TODO
id: motion.pointer_id as u64,
device_id: DEVICE_ID,
}),
})
}
android_glue::Event::InitWindow => {
// The activity went to foreground.
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
(*cb)(false);
}
Some(Event::Resumed)
}
android_glue::Event::TermWindow => {
// The activity went to background.
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
(*cb)(true);
}
Some(Event::Suspended)
}
android_glue::Event::WindowResized | android_glue::Event::ConfigChanged => {
// Activity Orientation changed or resized.
let native_window = unsafe { android_glue::native_window() };
if native_window.is_null() {
None
} else {
let dpi_factor = MonitorHandle.scale_factor();
let physical_size = MonitorHandle.size();
let size = LogicalSize::from_physical(physical_size, dpi_factor);
Some(Event::WindowEvent {
window_id: RootWindowId(WindowId),
event: WindowEvent::Resized(size),
})
}
}
android_glue::Event::WindowRedrawNeeded => {
// The activity needs to be redrawn.
Some(Event::WindowEvent {
window_id: RootWindowId(WindowId),
event: WindowEvent::Redraw,
})
}
android_glue::Event::Wake => Some(Event::Awakened),
_ => None,
};
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut v = VecDeque::with_capacity(1);
v.push_back(self.primary_monitor());
v
}
if let Some(event) = e {
callback(event);
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
queue: self.user_queue.clone(),
looper: ForeignLooper::for_thread().expect("called from event loop thread"),
}
}
pub fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>) {
*self.suspend_callback.borrow_mut() = cb;
}
pub fn run_forever<F>(&mut self, mut callback: F)
where
F: FnMut(::Event) -> ::ControlFlow,
{
// Yeah that's a very bad implementation.
loop {
let mut control_flow = ::ControlFlow::Continue;
self.poll_events(|e| {
if let ::ControlFlow::Break = callback(e) {
control_flow = ::ControlFlow::Break;
}
});
if let ::ControlFlow::Break = control_flow {
break;
}
::std::thread::sleep(::std::time::Duration::from_millis(5));
}
}
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy
}
}
impl EventLoopProxy {
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed<()>> {
android_glue::wake_event_loop();
pub struct EventLoopProxy<T: 'static> {
queue: Arc<Mutex<VecDeque<T>>>,
looper: ForeignLooper,
}
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
self.queue.lock().unwrap().push_back(event);
self.looper.wake();
Ok(())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
queue: self.queue.clone(),
looper: self.looper.clone(),
}
}
}
pub struct EventLoopWindowTarget<T: 'static> {
_marker: std::marker::PhantomData<T>,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct WindowId;
impl WindowId {
pub unsafe fn dummy() -> Self {
pub fn dummy() -> Self {
WindowId
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
pub fn dummy() -> Self {
DeviceId
}
}
pub struct Window {
native_window: *const c_void,
}
#[derive(Clone)]
pub struct MonitorHandle;
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[derive(Debug)]
struct MonitorHandle {
name: Option<String>,
dimensions: PhysicalSize<u32>,
position: PhysicalPosition<i32>,
scale_factor: f64,
}
let monitor_id_proxy = MonitorHandle {
name: self.name(),
dimensions: self.size(),
position: self.outer_position(),
scale_factor: self.scale_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorHandle {
#[inline]
pub fn name(&self) -> Option<String> {
Some("Primary".to_string())
}
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
unsafe {
let window = android_glue::native_window();
(
ffi::ANativeWindow_getWidth(window) as f64,
ffi::ANativeWindow_getHeight(window) as f64,
)
.into()
}
}
#[inline]
pub fn outer_position(&self) -> PhysicalPosition<i32> {
// Android assumes single screen
(0, 0).into()
}
#[inline]
pub fn scale_factor(&self) -> f64 {
1.0
}
}
#[derive(Clone, Default)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowBuilderAttributes;
#[derive(Clone, Default)]
pub struct PlatformSpecificHeadlessBuilderAttributes;
pub struct Window;
impl Window {
pub fn new(
_: &EventLoop,
win_attribs: WindowAttributes,
pub fn new<T: 'static>(
_el: &EventLoopWindowTarget<T>,
_window_attrs: window::WindowAttributes,
_: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, CreationError> {
let native_window = unsafe { android_glue::native_window() };
if native_window.is_null() {
return Err(OsError(format!("Android's native window is null")));
}
android_glue::set_multitouch(true);
Ok(Window {
native_window: native_window as *const _,
})
) -> Result<Self, error::OsError> {
// FIXME this ignores requested window attributes
Ok(Self)
}
#[inline]
pub fn native_window(&self) -> *const c_void {
self.native_window
}
#[inline]
pub fn set_title(&self, _: &str) {
// N/A
}
#[inline]
pub fn show(&self) {
// N/A
}
#[inline]
pub fn hide(&self) {
// N/A
}
#[inline]
pub fn outer_position(&self) -> Option<LogicalPosition<f64>> {
// N/A
None
}
#[inline]
pub fn inner_position(&self) -> Option<LogicalPosition<f64>> {
// N/A
None
}
#[inline]
pub fn set_outer_position(&self, _position: LogicalPosition<f64>) {
// N/A
}
#[inline]
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize<f64>>) {
// N/A
}
#[inline]
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize<f64>>) {
// N/A
}
#[inline]
pub fn set_resizable(&self, _resizable: bool) {
// N/A
}
#[inline]
pub fn inner_size(&self) -> Option<LogicalSize<f64>> {
if self.native_window.is_null() {
None
} else {
let dpi_factor = self.scale_factor();
let physical_size = self.current_monitor().size();
Some(LogicalSize::from_physical(physical_size, dpi_factor))
}
}
#[inline]
pub fn outer_size(&self) -> Option<LogicalSize<f64>> {
self.inner_size()
}
#[inline]
pub fn set_inner_size(&self, _size: LogicalSize<f64>) {
// N/A
}
#[inline]
pub fn scale_factor(&self) -> f64 {
self.current_monitor().scale_factor()
}
#[inline]
pub fn set_cursor_icon(&self, _: CursorIcon) {
// N/A
}
#[inline]
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn hide_cursor(&self, _hide: bool) {
// N/A
}
#[inline]
pub fn set_cursor_position(
&self,
_position: LogicalPosition<f64>,
) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn set_minimized(&self, _minimized: bool) {
unimplemented!()
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A
// Android has single screen maximized apps so nothing to do
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
// N/A
// Android has single screen maximized apps so nothing to do
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorHandle>) {
// N/A
// Android has single screen maximized apps so nothing to do
}
#[inline]
pub fn set_decorations(&self, _decorations: bool) {
// N/A
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// N/A
}
#[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// N/A
}
#[inline]
pub fn set_ime_position(&self, _spot: LogicalPosition<f64>) {
// N/A
}
#[inline]
pub fn current_monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: MonitorHandle,
}
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorHandle);
rb
}
#[inline]
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId
}
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = AndroidHandle {
a_native_window: self.native_window,
..WindowsHandle::empty()
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut v = VecDeque::with_capacity(1);
v.push_back(MonitorHandle);
v
}
pub fn current_monitor(&self) -> monitor::MonitorHandle {
monitor::MonitorHandle {
inner: MonitorHandle,
}
}
pub fn scale_factor(&self) -> f64 {
MonitorHandle.scale_factor()
}
pub fn request_redraw(&self) {
// TODO
}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
Err(error::NotSupportedError::new())
}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
Err(error::NotSupportedError::new())
}
pub fn set_outer_position(&self, _position: Position) {
// no effect
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
self.outer_size()
}
pub fn set_inner_size(&self, _size: Size) {
panic!("Cannot set window size on Android");
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
MonitorHandle.size()
}
pub fn set_min_inner_size(&self, _: Option<Size>) {}
pub fn set_max_inner_size(&self, _: Option<Size>) {}
pub fn set_title(&self, _title: &str) {}
pub fn set_visible(&self, _visibility: bool) {}
pub fn set_resizable(&self, _resizeable: bool) {}
pub fn set_minimized(&self, _minimized: bool) {}
pub fn set_maximized(&self, _maximized: bool) {}
pub fn set_fullscreen(&self, _monitor: Option<window::Fullscreen>) {
panic!("Cannot set fullscreen on Android");
}
pub fn fullscreen(&self) -> Option<window::Fullscreen> {
None
}
pub fn set_decorations(&self, _decorations: bool) {}
pub fn set_always_on_top(&self, _always_on_top: bool) {}
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
pub fn set_ime_position(&self, _position: Position) {}
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}
pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}
pub fn set_cursor_visible(&self, _: bool) {}
pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() {
unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ }
} else {
panic!("native window null");
};
RawWindowHandle::Android(handle)
let mut handle = raw_window_handle::android::AndroidHandle::empty();
handle.a_native_window = a_native_window;
raw_window_handle::RawWindowHandle::Android(handle)
}
pub fn config(&self) -> Configuration {
CONFIG.read().unwrap().clone()
}
pub fn content_rect(&self) -> Rect {
ndk_glue::content_rect()
}
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
#[derive(Default, Clone, Debug)]
pub struct OsError;
// Constant device ID, to be removed when this backend is updated to report real device IDs.
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
use std::fmt::{self, Display, Formatter};
impl Display for OsError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(fmt, "Android OS Error")
}
}
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct MonitorHandle;
impl MonitorHandle {
pub fn name(&self) -> Option<String> {
Some("Android Device".to_owned())
}
pub fn size(&self) -> PhysicalSize<u32> {
if let Some(native_window) = ndk_glue::native_window().as_ref() {
let width = native_window.width() as _;
let height = native_window.height() as _;
PhysicalSize::new(width, height)
} else {
PhysicalSize::new(0, 0)
}
}
pub fn position(&self) -> PhysicalPosition<i32> {
(0, 0).into()
}
pub fn scale_factor(&self) -> f64 {
let config = CONFIG.read().unwrap();
config
.density()
.map(|dpi| dpi as f64 / 160.0)
.unwrap_or(1.0)
}
pub fn video_modes(&self) -> impl Iterator<Item = monitor::VideoMode> {
let size = self.size().into();
let mut v = Vec::new();
// FIXME this is not the real refresh rate
// (it is guarunteed to support 32 bit color though)
v.push(monitor::VideoMode {
video_mode: VideoMode {
size,
bit_depth: 32,
refresh_rate: 60,
monitor: self.clone(),
},
});
v.into_iter()
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct VideoMode {
size: (u32, u32),
bit_depth: u16,
refresh_rate: u16,
monitor: MonitorHandle,
}
impl VideoMode {
pub fn size(&self) -> PhysicalSize<u32> {
self.size.into()
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> monitor::MonitorHandle {
monitor::MonitorHandle {
inner: self.monitor.clone(),
}
}
}

View File

@@ -215,10 +215,10 @@ fn setup_control_flow_observers() {
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send EventsCleared before RedrawRequested. This value was
// priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `EventsCleared`.
// conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.

View File

@@ -83,6 +83,8 @@ pub use self::{
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId {
uiscreen: ffi::id,

View File

@@ -6,6 +6,7 @@ use objc::{
};
use crate::{
dpi::PhysicalPosition,
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::MonitorHandleExtIOS,
platform_impl::platform::{
@@ -127,12 +128,12 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let dpi_factor: CGFloat = msg_send![screen, scale];
let scale_factor: CGFloat = msg_send![screen, scale];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
}
.to_physical(dpi_factor.into());
.to_physical(scale_factor.into());
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
@@ -162,15 +163,15 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
// `setContentScaleFactor` may be called with a value of 0, which means "reset the
// content scale factor to a device-specific default value", so we can't use the
// parameter here. We can query the actual factor using the getter
let dpi_factor: CGFloat = msg_send![object, contentScaleFactor];
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
assert!(
!dpi_factor.is_nan()
&& dpi_factor.is_finite()
&& dpi_factor.is_sign_positive()
&& dpi_factor > 0.0,
!scale_factor.is_nan()
&& scale_factor.is_finite()
&& scale_factor.is_sign_positive()
&& scale_factor > 0.0,
"invalid scale_factor set on UIView",
);
let scale_factor: f64 = dpi_factor.into();
let scale_factor: f64 = scale_factor.into();
let bounds: CGRect = msg_send![object, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
@@ -209,7 +210,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
if touch == nil {
break;
}
let location: CGPoint = msg_send![touch, locationInView: nil];
let logical_location: CGPoint = msg_send![touch, locationInView: nil];
let touch_type: UITouchType = msg_send![touch, type];
let force = if os_supports_force {
let trait_collection: id = msg_send![object, traitCollection];
@@ -248,12 +249,19 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
let physical_location = {
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
PhysicalPosition::from_logical::<(f64, f64), f64>(
(logical_location.x as _, logical_location.y as _),
scale_factor,
)
};
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }),
id: touch_id,
location: (location.x as f64, location.y as f64).into(),
location: physical_location,
force,
phase,
}),

View File

@@ -83,8 +83,8 @@ impl Inner {
x: safe_area.origin.x as f64,
y: safe_area.origin.y as f64,
};
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
}
@@ -95,15 +95,15 @@ impl Inner {
x: screen_frame.origin.x as f64,
y: screen_frame.origin.y as f64,
};
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
}
pub fn set_outer_position(&self, physical_position: Position) {
unsafe {
let dpi_factor = self.scale_factor();
let position = physical_position.to_logical::<f64>(dpi_factor);
let scale_factor = self.scale_factor();
let position = physical_position.to_logical::<f64>(scale_factor);
let screen_frame = self.screen_frame();
let new_screen_frame = CGRect {
origin: CGPoint {
@@ -119,25 +119,25 @@ impl Inner {
pub fn inner_size(&self) -> PhysicalSize<u32> {
unsafe {
let dpi_factor = self.scale_factor();
let scale_factor = self.scale_factor();
let safe_area = self.safe_area_screen_space();
let size = LogicalSize {
width: safe_area.size.width as f64,
height: safe_area.size.height as f64,
};
size.to_physical(dpi_factor)
size.to_physical(scale_factor)
}
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
unsafe {
let dpi_factor = self.scale_factor();
let scale_factor = self.scale_factor();
let screen_frame = self.screen_frame();
let size = LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
size.to_physical(dpi_factor)
size.to_physical(scale_factor)
}
}
@@ -355,8 +355,8 @@ impl Window {
let frame = match window_attributes.inner_size {
Some(dim) => {
let dpi_factor = msg_send![screen, scale];
let size = dim.to_logical::<f64>(dpi_factor);
let scale_factor = msg_send![screen, scale];
let size = dim.to_logical::<f64>(scale_factor);
CGRect {
origin: screen_bounds.origin,
size: CGSize {
@@ -400,8 +400,8 @@ impl Window {
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let dpi_factor: CGFloat = msg_send![view, contentScaleFactor];
let scale_factor: f64 = dpi_factor.into();
let scale_factor: CGFloat = msg_send![view, contentScaleFactor];
let scale_factor: f64 = scale_factor.into();
if scale_factor != 1.0 {
let bounds: CGRect = msg_send![view, bounds];
let screen: id = msg_send![window, screen];

View File

@@ -18,6 +18,8 @@ use crate::{
window::{CursorIcon, Fullscreen, WindowAttributes},
};
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub mod wayland;
pub mod x11;
@@ -61,7 +63,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
lazy_static! {
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
{ Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) };
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new));
}
#[derive(Debug, Clone)]
@@ -584,13 +586,12 @@ impl<T: 'static> EventLoop<T> {
}
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
X11_BACKEND
.lock()
.as_ref()
.map(Arc::clone)
.map(x11::EventLoop::new)
.map(EventLoop::X)
.map_err(|err| err.clone())
let xconn = match X11_BACKEND.lock().as_ref() {
Ok(xconn) => xconn.clone(),
Err(err) => return Err(err.clone()),
};
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
}
#[inline]

View File

@@ -39,7 +39,10 @@ use crate::{
window::{CursorIcon, WindowId as RootWindowId},
};
use super::{window::WindowStore, DeviceId, WindowId};
use super::{
window::{DecorationsAction, WindowStore},
DeviceId, WindowId,
};
use smithay_client_toolkit::{
output::OutputMgr,
@@ -90,6 +93,7 @@ pub struct CursorManager {
locked_pointers: Vec<ZwpLockedPointerV1>,
cursor_visible: bool,
current_cursor: CursorIcon,
scale_factor: u32,
}
impl CursorManager {
@@ -101,6 +105,7 @@ impl CursorManager {
locked_pointers: Vec::new(),
cursor_visible: true,
current_cursor: CursorIcon::default(),
scale_factor: 1,
}
}
@@ -145,6 +150,11 @@ impl CursorManager {
}
}
pub fn update_scale_factor(&mut self, scale: u32) {
self.scale_factor = scale;
self.reload_cursor_style();
}
fn set_cursor_icon_impl(&mut self, cursor: CursorIcon) {
let cursor = match cursor {
CursorIcon::Alias => "link",
@@ -193,7 +203,7 @@ impl CursorManager {
for pointer in self.pointers.iter() {
// Ignore erros, since we don't want to fail hard in case we can't find a proper cursor
// in a given theme.
let _ = pointer.set_cursor(cursor, None);
let _ = pointer.set_cursor_with_scale(cursor, self.scale_factor, None);
}
}
@@ -704,47 +714,70 @@ impl<T> EventLoop<T> {
window_target.store.lock().unwrap().for_each(|window| {
let window_id =
crate::window::WindowId(crate::platform_impl::WindowId::Wayland(window.wid));
if let Some(frame) = window.frame {
if let Some((w, h)) = window.newsize {
// mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case
// it overlaps mutter's `bounding box`, so we can't avoid this resize call,
// which calls `set_geometry` under the hood, for now.
frame.resize(w, h);
frame.refresh();
// Don't send resize event downstream if the new size is identical to the
// current one.
if (w, h) != *window.size {
let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64);
let physical_size = logical_size
.to_physical(window.new_dpi.unwrap_or(window.prev_dpi) as f64);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Resized(physical_size),
});
*window.size = (w, h);
}
}
if let Some(dpi) = window.new_dpi {
let dpi = dpi as f64;
let logical_size = LogicalSize::<f64>::from(*window.size);
let mut new_inner_size = logical_size.to_physical(dpi);
// Update window logical .size field (for callbacks using .inner_size)
let (old_logical_size, mut logical_size) = {
let mut window_size = window.size.lock().unwrap();
let old_logical_size = *window_size;
*window_size = window.new_size.unwrap_or(old_logical_size);
(old_logical_size, *window_size)
};
if let Some(scale_factor) = window.new_scale_factor {
// Update cursor scale factor
self.cursor_manager
.lock()
.unwrap()
.update_scale_factor(scale_factor as u32);
let new_logical_size = {
let scale_factor = scale_factor as f64;
let mut physical_size =
LogicalSize::<f64>::from(logical_size).to_physical(scale_factor);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: dpi,
new_inner_size: &mut new_inner_size,
scale_factor,
new_inner_size: &mut physical_size,
},
});
let (w, h) = new_inner_size.to_logical::<u32>(dpi).into();
frame.resize(w, h);
*window.size = (w, h);
physical_size.to_logical::<u32>(scale_factor).into()
};
// Update size if changed by callback
if new_logical_size != logical_size {
logical_size = new_logical_size;
*window.size.lock().unwrap() = logical_size.into();
}
}
if window.new_size.is_some() || window.new_scale_factor.is_some() {
if let Some(frame) = window.frame {
// Update decorations state
match window.decorations_action {
Some(DecorationsAction::Hide) => frame.set_decorate(false),
Some(DecorationsAction::Show) => frame.set_decorate(true),
None => (),
}
// mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case
// it overlaps mutter's `bounding box`, so we can't avoid this resize call,
// which calls `set_geometry` under the hood, for now.
let (w, h) = logical_size;
frame.resize(w, h);
frame.refresh();
}
// Don't send resize event downstream if the new logical size and scale is identical to the
// current one
if logical_size != old_logical_size || window.new_scale_factor.is_some() {
let physical_size = LogicalSize::<f64>::from(logical_size).to_physical(
window.new_scale_factor.unwrap_or(window.prev_scale_factor) as f64,
);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Resized(physical_size),
});
}
}
if window.closed {
callback(Event::WindowEvent {
window_id,

View File

@@ -8,9 +8,7 @@ use smithay_client_toolkit::{
reexports::client::protocol::{wl_keyboard, wl_seat},
};
use crate::event::{
DeviceEvent, ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent,
};
use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
pub fn init_keyboard(
seat: &wl_seat::WlSeat,
@@ -33,9 +31,24 @@ pub fn init_keyboard(
let wid = make_wid(&surface);
my_sink.send_window_event(WindowEvent::Focused(true), wid);
*target.lock().unwrap() = Some(wid);
let modifiers = *modifiers_tracker.lock().unwrap();
if !modifiers.is_empty() {
my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid);
}
}
KbEvent::Leave { surface, .. } => {
let wid = make_wid(&surface);
let modifiers = *modifiers_tracker.lock().unwrap();
if !modifiers.is_empty() {
my_sink.send_window_event(
WindowEvent::ModifiersChanged(ModifiersState::empty()),
wid,
);
}
my_sink.send_window_event(WindowEvent::Focused(false), wid);
*target.lock().unwrap() = None;
}
@@ -88,7 +101,9 @@ pub fn init_keyboard(
*modifiers_tracker.lock().unwrap() = modifiers;
my_sink.send_device_event(DeviceEvent::ModifiersChanged(modifiers), DeviceId);
if let Some(wid) = *target.lock().unwrap() {
my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid);
}
}
}
},

View File

@@ -1,5 +1,6 @@
use std::sync::{Arc, Mutex};
use crate::dpi::LogicalPosition;
use crate::event::{
DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
WindowEvent,
@@ -7,10 +8,13 @@ use crate::event::{
use super::{
event_loop::{CursorManager, EventsSink},
make_wid,
window::WindowStore,
DeviceId,
};
use smithay_client_toolkit::surface;
use smithay_client_toolkit::reexports::client::protocol::{
wl_pointer::{self, Event as PtrEvent, WlPointer},
wl_seat,
@@ -36,6 +40,7 @@ pub fn implement_pointer(
cursor_manager: Arc<Mutex<CursorManager>>,
) -> WlPointer {
seat.get_pointer(|pointer| {
// Currently focused winit surface
let mut mouse_focus = None;
let mut axis_buffer = None;
let mut axis_discrete_buffer = None;
@@ -53,8 +58,10 @@ pub fn implement_pointer(
..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
mouse_focus = Some(wid);
let scale_factor = surface::get_dpi_factor(&surface) as f64;
mouse_focus = Some(surface);
// Reload cursor style only when we enter winit's surface. Calling
// this function every time on `PtrEvent::Enter` could interfere with
@@ -70,12 +77,16 @@ pub fn implement_pointer(
},
wid,
);
let position = LogicalPosition::new(surface_x, surface_y)
.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
position: (surface_x, surface_y).into(),
position,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
@@ -101,13 +112,19 @@ pub fn implement_pointer(
surface_y,
..
} => {
if let Some(wid) = mouse_focus {
if let Some(surface) = mouse_focus.as_ref() {
let wid = make_wid(surface);
let scale_factor = surface::get_dpi_factor(&surface) as f64;
let position = LogicalPosition::new(surface_x, surface_y)
.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
position: (surface_x, surface_y).into(),
position,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
@@ -115,7 +132,7 @@ pub fn implement_pointer(
}
}
PtrEvent::Button { button, state, .. } => {
if let Some(wid) = mouse_focus {
if let Some(surface) = mouse_focus.as_ref() {
let state = match state {
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
wl_pointer::ButtonState::Released => ElementState::Released,
@@ -137,12 +154,13 @@ pub fn implement_pointer(
button,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
make_wid(surface),
);
}
}
PtrEvent::Axis { axis, value, .. } => {
if let Some(wid) = mouse_focus {
if let Some(surface) = mouse_focus.as_ref() {
let wid = make_wid(surface);
if pointer.as_ref().version() < 5 {
let (mut x, mut y) = (0.0, 0.0);
// old seat compatibility
@@ -184,7 +202,8 @@ pub fn implement_pointer(
PtrEvent::Frame => {
let axis_buffer = axis_buffer.take();
let axis_discrete_buffer = axis_discrete_buffer.take();
if let Some(wid) = mouse_focus {
if let Some(surface) = mouse_focus.as_ref() {
let wid = make_wid(surface);
if let Some((x, y)) = axis_discrete_buffer {
sink.send_window_event(
WindowEvent::MouseWheel {

View File

@@ -1,17 +1,22 @@
use std::sync::{Arc, Mutex};
use crate::dpi::LogicalPosition;
use crate::event::{TouchPhase, WindowEvent};
use super::{event_loop::EventsSink, window::WindowStore, DeviceId, WindowId};
use super::{event_loop::EventsSink, make_wid, window::WindowStore, DeviceId};
use smithay_client_toolkit::surface;
use smithay_client_toolkit::reexports::client::protocol::{
wl_seat,
wl_surface::WlSurface,
wl_touch::{Event as TouchEvent, WlTouch},
};
// location is in logical coordinates.
struct TouchPoint {
wid: WindowId,
location: (f64, f64),
surface: WlSurface,
position: LogicalPosition<f64>,
id: i32,
}
@@ -31,21 +36,24 @@ pub(crate) fn implement_touch(
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
let scale_factor = surface::get_dpi_factor(&surface) as f64;
let position = LogicalPosition::new(x, y);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Started,
location: (x, y).into(),
location: position.to_physical(scale_factor),
force: None, // TODO
id: id as u64,
}),
wid,
);
pending_ids.push(TouchPoint {
wid,
location: (x, y),
surface,
position,
id,
});
}
@@ -54,52 +62,63 @@ pub(crate) fn implement_touch(
let idx = pending_ids.iter().position(|p| p.id == id);
if let Some(idx) = idx {
let pt = pending_ids.remove(idx);
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
let location = pt.position.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Ended,
location: pt.location.into(),
location,
force: None, // TODO
id: id as u64,
}),
pt.wid,
make_wid(&pt.surface),
);
}
}
TouchEvent::Motion { id, x, y, .. } => {
let pt = pending_ids.iter_mut().find(|p| p.id == id);
if let Some(pt) = pt {
pt.location = (x, y);
pt.position = LogicalPosition::new(x, y);
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
let location = pt.position.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Moved,
location: (x, y).into(),
location,
force: None, // TODO
id: id as u64,
}),
pt.wid,
make_wid(&pt.surface),
);
}
}
TouchEvent::Frame => (),
TouchEvent::Cancel => {
for pt in pending_ids.drain(..) {
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
let location = pt.position.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Cancelled,
location: pt.location.into(),
location,
force: None, // TODO
id: pt.id as u64,
}),
pt.wid,
make_wid(&pt.surface),
);
}
}

View File

@@ -41,6 +41,13 @@ pub struct Window {
need_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
cursor_grab_changed: Arc<Mutex<Option<bool>>>, // Update grab state
decorated: Arc<Mutex<bool>>,
}
#[derive(Clone, Copy, Debug)]
pub enum DecorationsAction {
Hide,
Show,
}
impl Window {
@@ -52,15 +59,20 @@ impl Window {
// Create the surface first to get initial DPI
let window_store = evlp.store.clone();
let cursor_manager = evlp.cursor_manager.clone();
let surface = evlp.env.create_surface(move |dpi, surface| {
window_store.lock().unwrap().dpi_change(&surface, dpi);
surface.set_buffer_scale(dpi);
let surface = evlp.env.create_surface(move |scale_factor, surface| {
window_store
.lock()
.unwrap()
.scale_factor_change(&surface, scale_factor);
surface.set_buffer_scale(scale_factor);
});
let dpi = get_dpi_factor(&surface) as f64;
// Always 1.
let scale_factor = get_dpi_factor(&surface);
let (width, height) = attributes
.inner_size
.map(|size| size.to_logical::<f64>(dpi).into())
.map(|size| size.to_logical::<f64>(scale_factor as f64).into())
.unwrap_or((800, 600));
// Create the window
@@ -69,6 +81,9 @@ impl Window {
let window_store = evlp.store.clone();
let decorated = Arc::new(Mutex::new(attributes.decorations));
let pending_decorations_action = Arc::new(Mutex::new(None));
let my_surface = surface.clone();
let mut frame = SWindow::<ConceptFrame>::init_from_env(
&evlp.env,
@@ -81,9 +96,25 @@ impl Window {
for window in &mut store.windows {
if window.surface.as_ref().equals(&my_surface.as_ref()) {
window.newsize = new_size;
window.new_size = new_size;
*(window.need_refresh.lock().unwrap()) = true;
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
{
// Get whether we're in fullscreen
let mut fullscreen = window.fullscreen.lock().unwrap();
// Fullscreen state was changed, so update decorations
if *fullscreen != is_fullscreen {
let decorated = { *window.decorated.lock().unwrap() };
if decorated {
*window.pending_decorations_action.lock().unwrap() =
if is_fullscreen {
Some(DecorationsAction::Hide)
} else {
Some(DecorationsAction::Show)
};
}
}
*fullscreen = is_fullscreen;
}
*(window.need_frame_refresh.lock().unwrap()) = true;
return;
}
@@ -147,12 +178,12 @@ impl Window {
frame.set_min_size(
attributes
.min_inner_size
.map(|size| size.to_logical::<f64>(dpi).into()),
.map(|size| size.to_logical::<f64>(scale_factor as f64).into()),
);
frame.set_max_size(
attributes
.max_inner_size
.map(|size| size.to_logical::<f64>(dpi).into()),
.map(|size| size.to_logical::<f64>(scale_factor as f64).into()),
);
let kill_switch = Arc::new(Mutex::new(false));
@@ -163,7 +194,7 @@ impl Window {
evlp.store.lock().unwrap().windows.push(InternalWindow {
closed: false,
newsize: None,
new_size: None,
size: size.clone(),
need_refresh: need_refresh.clone(),
fullscreen: fullscreen.clone(),
@@ -172,8 +203,10 @@ impl Window {
surface: surface.clone(),
kill_switch: kill_switch.clone(),
frame: Arc::downgrade(&frame),
current_dpi: 1,
new_dpi: None,
current_scale_factor: scale_factor,
new_scale_factor: None,
decorated: decorated.clone(),
pending_decorations_action: pending_decorations_action.clone(),
});
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
@@ -189,6 +222,7 @@ impl Window {
cursor_manager,
fullscreen,
cursor_grab_changed,
decorated,
})
}
@@ -221,9 +255,9 @@ impl Window {
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
let dpi = self.scale_factor() as f64;
let scale_factor = self.scale_factor() as f64;
let size = LogicalSize::<f64>::from(*self.size.lock().unwrap());
size.to_physical(dpi)
size.to_physical(scale_factor)
}
pub fn request_redraw(&self) {
@@ -232,38 +266,38 @@ impl Window {
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
let dpi = self.scale_factor() as f64;
let scale_factor = self.scale_factor() as f64;
let (w, h) = self.size.lock().unwrap().clone();
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
let size = LogicalSize::<f64>::from((w, h));
size.to_physical(dpi)
size.to_physical(scale_factor)
}
#[inline]
// NOTE: This will only resize the borders, the contents must be updated by the user
pub fn set_inner_size(&self, size: Size) {
let dpi = self.scale_factor() as f64;
let (w, h) = size.to_logical::<u32>(dpi).into();
let scale_factor = self.scale_factor() as f64;
let (w, h) = size.to_logical::<u32>(scale_factor).into();
self.frame.lock().unwrap().resize(w, h);
*(self.size.lock().unwrap()) = (w, h);
}
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
let dpi = self.scale_factor() as f64;
let scale_factor = self.scale_factor() as f64;
self.frame
.lock()
.unwrap()
.set_min_size(dimensions.map(|dim| dim.to_logical::<f64>(dpi).into()));
.set_min_size(dimensions.map(|dim| dim.to_logical::<f64>(scale_factor).into()));
}
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
let dpi = self.scale_factor() as f64;
let scale_factor = self.scale_factor() as f64;
self.frame
.lock()
.unwrap()
.set_max_size(dimensions.map(|dim| dim.to_logical::<f64>(dpi).into()));
.set_max_size(dimensions.map(|dim| dim.to_logical::<f64>(scale_factor).into()));
}
#[inline]
@@ -277,6 +311,7 @@ impl Window {
}
pub fn set_decorations(&self, decorate: bool) {
*(self.decorated.lock().unwrap()) = decorate;
self.frame.lock().unwrap().set_decorate(decorate);
*(self.need_frame_refresh.lock().unwrap()) = true;
}
@@ -398,7 +433,7 @@ impl Drop for Window {
struct InternalWindow {
surface: wl_surface::WlSurface,
// TODO: CONVERT TO LogicalSize<u32>s
newsize: Option<(u32, u32)>,
new_size: Option<(u32, u32)>,
size: Arc<Mutex<(u32, u32)>>,
need_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
@@ -407,8 +442,10 @@ struct InternalWindow {
closed: bool,
kill_switch: Arc<Mutex<bool>>,
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
current_dpi: i32,
new_dpi: Option<i32>,
current_scale_factor: i32,
new_scale_factor: Option<i32>,
decorated: Arc<Mutex<bool>>,
pending_decorations_action: Arc<Mutex<Option<DecorationsAction>>>,
}
pub struct WindowStore {
@@ -416,15 +453,16 @@ pub struct WindowStore {
}
pub struct WindowStoreForEach<'a> {
pub newsize: Option<(u32, u32)>,
pub size: &'a mut (u32, u32),
pub prev_dpi: i32,
pub new_dpi: Option<i32>,
pub new_size: Option<(u32, u32)>,
pub size: &'a Mutex<(u32, u32)>,
pub prev_scale_factor: i32,
pub new_scale_factor: Option<i32>,
pub closed: bool,
pub grab_cursor: Option<bool>,
pub surface: &'a wl_surface::WlSurface,
pub wid: WindowId,
pub frame: Option<&'a mut SWindow<ConceptFrame>>,
pub decorations_action: Option<DecorationsAction>,
}
impl WindowStore {
@@ -466,10 +504,11 @@ impl WindowStore {
}
}
fn dpi_change(&mut self, surface: &wl_surface::WlSurface, new: i32) {
fn scale_factor_change(&mut self, surface: &wl_surface::WlSurface, new: i32) {
for window in &mut self.windows {
if surface.as_ref().equals(&window.surface.as_ref()) {
window.new_dpi = Some(new);
window.new_scale_factor = Some(new);
*(window.need_refresh.lock().unwrap()) = true;
}
}
}
@@ -479,22 +518,25 @@ impl WindowStore {
F: FnMut(WindowStoreForEach<'_>),
{
for window in &mut self.windows {
let prev_scale_factor = window.current_scale_factor;
if let Some(scale_factor) = window.new_scale_factor {
window.current_scale_factor = scale_factor;
}
let opt_arc = window.frame.upgrade();
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
let decorations_action = { window.pending_decorations_action.lock().unwrap().take() };
f(WindowStoreForEach {
newsize: window.newsize.take(),
size: &mut *(window.size.lock().unwrap()),
prev_dpi: window.current_dpi,
new_dpi: window.new_dpi,
new_size: window.new_size.take(),
size: &window.size,
prev_scale_factor,
new_scale_factor: window.new_scale_factor.take(),
closed: window.closed,
grab_cursor: window.cursor_grab_changed.lock().unwrap().take(),
surface: &window.surface,
wid: make_wid(&window.surface),
frame: opt_mutex_lock.as_mut().map(|m| &mut **m),
decorations_action,
});
if let Some(dpi) = window.new_dpi.take() {
window.current_dpi = dpi;
}
// avoid re-spamming the event
window.closed = false;
}

View File

@@ -2,6 +2,8 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc};
use libc::{c_char, c_int, c_long, c_uint, c_ulong};
use parking_lot::MutexGuard;
use super::{
events, ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd,
DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
@@ -30,6 +32,8 @@ pub(super) struct EventProcessor<T: 'static> {
// Number of touch events currently in progress
pub(super) num_touch: u32,
pub(super) first_touch: Option<u64>,
// Currently focused window belonging to this process
pub(super) active_window: Option<ffi::Window>,
}
impl<T: 'static> EventProcessor<T> {
@@ -134,11 +138,12 @@ impl<T: 'static> EventProcessor<T> {
if let Some(modifiers) =
self.device_mod_state.update_state(&state, modifier)
{
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::ModifiersChanged(modifiers),
});
if let Some(window_id) = self.active_window {
callback(Event::WindowEvent {
window_id: mkwid(window_id),
event: WindowEvent::ModifiersChanged(modifiers),
});
}
}
}
}
@@ -384,9 +389,12 @@ impl<T: 'static> EventProcessor<T> {
.inner_pos_to_outer(new_inner_position.0, new_inner_position.1);
shared_state_lock.position = Some(outer);
if moved {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Moved(outer.into()),
// Temporarily unlock shared state to prevent deadlock
MutexGuard::unlocked(&mut shared_state_lock, || {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Moved(outer.into()),
});
});
}
outer
@@ -426,12 +434,15 @@ impl<T: 'static> EventProcessor<T> {
let old_inner_size = PhysicalSize::new(width, height);
let mut new_inner_size = PhysicalSize::new(new_width, new_height);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_scale_factor,
new_inner_size: &mut new_inner_size,
},
// Temporarily unlock shared state to prevent deadlock
MutexGuard::unlocked(&mut shared_state_lock, || {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_scale_factor,
new_inner_size: &mut new_inner_size,
},
});
});
if new_inner_size != old_inner_size {
@@ -461,6 +472,9 @@ impl<T: 'static> EventProcessor<T> {
}
if resized {
// Drop the shared state lock to prevent deadlock
drop(shared_state_lock);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Resized(new_inner_size.into()),
@@ -712,8 +726,7 @@ impl<T: 'static> EventProcessor<T> {
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
});
if cursor_moved == Some(true) {
let position =
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
callback(Event::WindowEvent {
window_id,
@@ -819,8 +832,7 @@ impl<T: 'static> EventProcessor<T> {
event: CursorEntered { device_id },
});
let position =
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
// The mods field on this event isn't actually populated, so query the
// pointer device. In the future, we can likely remove this round-trip by
@@ -863,45 +875,58 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_FocusIn => {
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
let window_id = mkwid(xev.event);
wt.ime
.borrow_mut()
.focus(xev.event)
.expect("Failed to focus input context");
callback(Event::WindowEvent {
window_id,
event: Focused(true),
});
let modifiers = ModifiersState::from_x11(&xev.mods);
update_modifiers!(modifiers, None);
self.device_mod_state.update_state(&modifiers, None);
// The deviceid for this event is for a keyboard instead of a pointer,
// so we have to do a little extra work.
let pointer_id = self
.devices
.borrow()
.get(&DeviceId(xev.deviceid))
.map(|device| device.attachment)
.unwrap_or(2);
if self.active_window != Some(xev.event) {
self.active_window = Some(xev.event);
let position =
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
let window_id = mkwid(xev.event);
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id: mkdid(pointer_id),
position,
modifiers,
},
});
callback(Event::WindowEvent {
window_id,
event: Focused(true),
});
// Issue key press events for all pressed keys
self.handle_pressed_keys(window_id, ElementState::Pressed, &mut callback);
if !modifiers.is_empty() {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(modifiers),
});
}
// The deviceid for this event is for a keyboard instead of a pointer,
// so we have to do a little extra work.
let pointer_id = self
.devices
.borrow()
.get(&DeviceId(xev.deviceid))
.map(|device| device.attachment)
.unwrap_or(2);
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id: mkdid(pointer_id),
position,
modifiers,
},
});
// Issue key press events for all pressed keys
self.handle_pressed_keys(
window_id,
ElementState::Pressed,
&mut callback,
);
}
}
ffi::XI_FocusOut => {
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
@@ -913,15 +938,26 @@ impl<T: 'static> EventProcessor<T> {
.unfocus(xev.event)
.expect("Failed to unfocus input context");
let window_id = mkwid(xev.event);
if self.active_window.take() == Some(xev.event) {
let window_id = mkwid(xev.event);
// Issue key release events for all pressed keys
self.handle_pressed_keys(window_id, ElementState::Released, &mut callback);
// Issue key release events for all pressed keys
self.handle_pressed_keys(
window_id,
ElementState::Released,
&mut callback,
);
callback(Event::WindowEvent {
window_id,
event: Focused(false),
})
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(ModifiersState::empty()),
});
callback(Event::WindowEvent {
window_id,
event: Focused(false),
})
}
}
ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => {
@@ -1076,10 +1112,12 @@ impl<T: 'static> EventProcessor<T> {
let new_modifiers = self.device_mod_state.modifiers();
if modifiers != new_modifiers {
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::ModifiersChanged(new_modifiers),
});
if let Some(window_id) = self.active_window {
callback(Event::WindowEvent {
window_id: mkwid(window_id),
event: WindowEvent::ModifiersChanged(new_modifiers),
});
}
}
}
}

View File

@@ -21,7 +21,8 @@ unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<f
// XSetLocaleModifiers returns...
// * The current locale modifiers if it's given a NULL pointer.
// * The new locale modifiers if we succeeded in setting them.
// * NULL if the locale modifiers string is malformed.
// * NULL if the locale modifiers string is malformed or if the
// current locale is not supported by Xlib.
(xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr());
let im = (xconn.xlib.XOpenIM)(

View File

@@ -29,6 +29,7 @@ use std::{
mem::{self, MaybeUninit},
ops::Deref,
os::raw::*,
ptr,
rc::Rc,
slice,
sync::{mpsc, Arc, Mutex, Weak},
@@ -105,7 +106,25 @@ impl<T: 'static> EventLoop<T> {
// Input methods will open successfully without setting the locale, but it won't be
// possible to actually commit pre-edit sequences.
unsafe {
// Remember default locale to restore it if target locale is unsupported
// by Xlib
let default_locale = setlocale(LC_CTYPE, ptr::null());
setlocale(LC_CTYPE, b"\0".as_ptr() as *const _);
// Check if set locale is supported by Xlib.
// If not, calls to some Xlib functions like `XSetLocaleModifiers`
// will fail.
let locale_supported = (xconn.xlib.XSupportsLocale)() == 1;
if !locale_supported {
let unsupported_locale = setlocale(LC_CTYPE, ptr::null());
warn!(
"Unsupported locale \"{}\". Restoring default locale \"{}\".",
CStr::from_ptr(unsupported_locale).to_string_lossy(),
CStr::from_ptr(default_locale).to_string_lossy()
);
// Restore default locale
setlocale(LC_CTYPE, default_locale);
}
}
let ime = RefCell::new({
let result = Ime::new(Arc::clone(&xconn));
@@ -206,6 +225,7 @@ impl<T: 'static> EventLoop<T> {
device_mod_state: Default::default(),
num_touch: 0,
first_touch: None,
active_window: None,
};
// Register for device hotplug events
@@ -248,14 +268,16 @@ impl<T: 'static> EventLoop<T> {
{
let mut control_flow = ControlFlow::default();
let mut events = Events::with_capacity(8);
callback(
crate::event::Event::NewEvents(crate::event::StartCause::Init),
&self.target,
&mut control_flow,
);
let mut cause = StartCause::Init;
loop {
sticky_exit_callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
&mut callback,
);
// Process all pending events
self.drain_events(&mut callback, &mut control_flow);
@@ -306,7 +328,7 @@ impl<T: 'static> EventLoop<T> {
}
let start = Instant::now();
let (mut cause, deadline, timeout);
let (deadline, timeout);
match control_flow {
ControlFlow::Exit => break,
@@ -337,38 +359,20 @@ impl<T: 'static> EventLoop<T> {
}
}
if self.event_processor.poll() {
// If the XConnection already contains buffered events, we don't
// need to wait for data on the socket.
// However, we still need to check for user events.
self.poll
.poll(&mut events, Some(Duration::from_millis(0)))
.unwrap();
events.clear();
callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
);
} else {
// If the XConnection already contains buffered events, we don't
// need to wait for data on the socket.
if !self.event_processor.poll() {
self.poll.poll(&mut events, timeout).unwrap();
events.clear();
}
let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline);
let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline);
if wait_cancelled {
cause = StartCause::WaitCancelled {
start,
requested_resume: deadline,
};
}
callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
);
if wait_cancelled {
cause = StartCause::WaitCancelled {
start,
requested_resume: deadline,
};
}
}
@@ -395,7 +399,6 @@ impl<T: 'static> EventLoop<T> {
let mut xev = MaybeUninit::uninit();
let wt = get_xtarget(&self.target);
let mut pending_redraws = wt.pending_redraws.lock().unwrap();
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
@@ -409,7 +412,7 @@ impl<T: 'static> EventLoop<T> {
super::WindowId::X(wid),
)) = event
{
pending_redraws.insert(wid);
wt.pending_redraws.lock().unwrap().insert(wid);
} else {
callback(event, window_target, control_flow);
}
@@ -490,21 +493,9 @@ impl<'a> Deref for DeviceInfo<'a> {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(ffi::Window);
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(c_int);
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId(0)
}
}
pub struct Window(Arc<UnownedWindow>);
impl Deref for Window {

View File

@@ -43,62 +43,4 @@ impl XConnection {
};
self.send_event(target_window, event_mask, event)
}
// Prepare yourself for the ultimate in unsafety!
// You should favor `send_client_msg` whenever possible, but some protocols (i.e. startup notification) require you
// to send more than one message worth of data.
pub fn send_client_msg_multi<T: Formattable>(
&self,
window: c_ulong, // The window this is "about"; not necessarily this window
target_window: c_ulong, // The window we're sending to
message_type: ffi::Atom,
event_mask: Option<c_long>,
data: &[T],
) -> Flusher<'_> {
let format = T::FORMAT;
let size_of_t = mem::size_of::<T>();
debug_assert_eq!(size_of_t, format.get_actual_size());
let mut event = ffi::XClientMessageEvent {
type_: ffi::ClientMessage,
display: self.display,
window,
message_type,
format: format as c_int,
data: ffi::ClientMessageData::new(),
// These fields are ignored by `XSendEvent`
serial: 0,
send_event: 0,
};
let t_per_payload = format.get_payload_size() / size_of_t;
assert!(t_per_payload > 0);
let payload_count = data.len() / t_per_payload;
let payload_remainder = data.len() % t_per_payload;
let payload_ptr = data.as_ptr() as *const ClientMsgPayload;
let mut payload_index = 0;
while payload_index < payload_count {
let payload = unsafe { payload_ptr.offset(payload_index as isize) };
payload_index += 1;
event.data = unsafe { mem::transmute(*payload) };
self.send_event(target_window, event_mask, &event).queue();
}
if payload_remainder > 0 {
let mut payload: ClientMsgPayload = [0; 5];
let t_payload = payload.as_mut_ptr() as *mut T;
let invalid_payload = unsafe { payload_ptr.offset(payload_index as isize) };
let invalid_t_payload = invalid_payload as *const T;
let mut t_index = 0;
while t_index < payload_remainder {
let valid_t = unsafe { invalid_t_payload.offset(t_index as isize) };
unsafe { (*t_payload.offset(t_index as isize)) = (*valid_t).clone() };
t_index += 1;
}
event.data = unsafe { mem::transmute(payload) };
self.send_event(target_window, event_mask, &event).queue();
}
Flusher::new(self)
}
}

View File

@@ -21,10 +21,6 @@ impl Format {
}
}
pub fn is_same_size_as<T>(&self) -> bool {
mem::size_of::<T>() == self.get_actual_size()
}
pub fn get_actual_size(&self) -> usize {
match self {
&Format::Char => mem::size_of::<c_char>(),
@@ -32,15 +28,6 @@ impl Format {
&Format::Long => mem::size_of::<c_long>(),
}
}
pub fn get_payload_size(&self) -> usize {
match self {
// Due to the wonders of X11, half the space goes unused if you're not using longs (on 64-bit).
&Format::Char => mem::size_of::<c_char>() * 20,
&Format::Short => mem::size_of::<c_short>() * 10,
&Format::Long => mem::size_of::<c_long>() * 5,
}
}
}
pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd {

View File

@@ -1,7 +1,6 @@
use std::cmp;
use super::*;
use crate::dpi::{LogicalPosition, LogicalSize};
// Friendly neighborhood axis-aligned rectangle
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -89,16 +88,6 @@ impl FrameExtents {
pub fn from_border(border: c_ulong) -> Self {
Self::new(border, border, border, border)
}
pub fn as_logical(&self, factor: f64) -> LogicalFrameExtents {
let logicalize = |value: c_ulong| value as f64 / factor;
LogicalFrameExtents {
left: logicalize(self.left),
right: logicalize(self.right),
top: logicalize(self.top),
bottom: logicalize(self.bottom),
}
}
}
#[derive(Debug, Clone)]
@@ -135,20 +124,6 @@ impl FrameExtentsHeuristic {
}
}
pub fn inner_pos_to_outer_logical(
&self,
mut logical: LogicalPosition<f64>,
factor: f64,
) -> LogicalPosition<f64> {
use self::FrameExtentsHeuristicPath::*;
if self.heuristic_path != UnsupportedBordered {
let frame_extents = self.frame_extents.as_logical(factor);
logical.x -= frame_extents.left;
logical.y -= frame_extents.top;
}
logical
}
pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
(
width.saturating_add(
@@ -163,17 +138,6 @@ impl FrameExtentsHeuristic {
),
)
}
pub fn inner_size_to_outer_logical(
&self,
mut logical: LogicalSize<f64>,
factor: f64,
) -> LogicalSize<f64> {
let frame_extents = self.frame_extents.as_logical(factor);
logical.width += frame_extents.left + frame_extents.right;
logical.height += frame_extents.top + frame_extents.bottom;
logical
}
}
impl XConnection {

View File

@@ -4,6 +4,7 @@ use std::sync::Arc;
use super::*;
#[derive(Debug)]
#[allow(dead_code)]
pub enum StateOperation {
Remove = 0, // _NET_WM_STATE_REMOVE
Add = 1, // _NET_WM_STATE_ADD
@@ -189,22 +190,6 @@ impl<'a> NormalHints<'a> {
}
}
pub fn has_flag(&self, flag: c_long) -> bool {
has_flag(self.size_hints.flags, flag)
}
fn getter(&self, flag: c_long, field1: &c_int, field2: &c_int) -> Option<(u32, u32)> {
if self.has_flag(flag) {
Some((*field1 as _, *field2 as _))
} else {
None
}
}
pub fn get_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PSize, &self.size_hints.width, &self.size_hints.height)
}
// WARNING: This hint is obsolete
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
if let Some((width, height)) = size {
@@ -216,14 +201,6 @@ impl<'a> NormalHints<'a> {
}
}
pub fn get_max_size(&self) -> Option<(u32, u32)> {
self.getter(
ffi::PMaxSize,
&self.size_hints.max_width,
&self.size_hints.max_height,
)
}
pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) {
if let Some((max_width, max_height)) = max_size {
self.size_hints.flags |= ffi::PMaxSize;
@@ -234,14 +211,6 @@ impl<'a> NormalHints<'a> {
}
}
pub fn get_min_size(&self) -> Option<(u32, u32)> {
self.getter(
ffi::PMinSize,
&self.size_hints.min_width,
&self.size_hints.min_height,
)
}
pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) {
if let Some((min_width, min_height)) = min_size {
self.size_hints.flags |= ffi::PMinSize;
@@ -252,14 +221,6 @@ impl<'a> NormalHints<'a> {
}
}
pub fn get_resize_increments(&self) -> Option<(u32, u32)> {
self.getter(
ffi::PResizeInc,
&self.size_hints.width_inc,
&self.size_hints.height_inc,
)
}
pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) {
if let Some((width_inc, height_inc)) = resize_increments {
self.size_hints.flags |= ffi::PResizeInc;
@@ -270,14 +231,6 @@ impl<'a> NormalHints<'a> {
}
}
pub fn get_base_size(&self) -> Option<(u32, u32)> {
self.getter(
ffi::PBaseSize,
&self.size_hints.base_width,
&self.size_hints.base_height,
)
}
pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) {
if let Some((base_width, base_height)) = base_size {
self.size_hints.flags |= ffi::PBaseSize;

View File

@@ -1,5 +1,5 @@
use super::*;
use crate::window::{Icon, Pixel, PIXEL_SIZE};
use crate::icon::{Icon, Pixel, PIXEL_SIZE};
impl Pixel {
pub fn to_packed_argb(&self) -> Cardinal {
@@ -18,13 +18,14 @@ impl Pixel {
impl Icon {
pub(crate) fn to_cardinals(&self) -> Vec<Cardinal> {
assert_eq!(self.rgba.len() % PIXEL_SIZE, 0);
let pixel_count = self.rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (self.width * self.height) as usize);
let rgba_icon = &self.inner;
assert_eq!(rgba_icon.rgba.len() % PIXEL_SIZE, 0);
let pixel_count = rgba_icon.rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (rgba_icon.width * rgba_icon.height) as usize);
let mut data = Vec::with_capacity(pixel_count);
data.push(self.width as Cardinal);
data.push(self.height as Cardinal);
let pixels = self.rgba.as_ptr() as *const Pixel;
data.push(rgba_icon.width as Cardinal);
data.push(rgba_icon.height as Cardinal);
let pixels = rgba_icon.rgba.as_ptr() as *const Pixel;
for pixel_index in 0..pixel_count {
let pixel = unsafe { &*pixels.offset(pixel_index as isize) };
data.push(pixel.to_packed_argb());

View File

@@ -23,18 +23,12 @@ pub use self::{
use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
ptr,
};
use super::{ffi, XConnection, XError};
pub fn reinterpret<'a, A, B>(a: &'a A) -> &'a B {
let b_ptr = a as *const _ as *const B;
unsafe { &*b_ptr }
}
pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
let wrapped = Some(value);
if *field != wrapped {
@@ -45,13 +39,6 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
}
}
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where
T: Copy + PartialEq + BitAnd<T, Output = T>,
{
bitset & flag == flag
}
#[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."]
pub struct Flusher<'a> {
xconn: &'a XConnection,

View File

@@ -26,6 +26,7 @@ impl GetPropertyError {
const PROPERTY_BUFFER_SIZE: c_long = 1024; // 4k of RAM ought to be enough for anyone!
#[derive(Debug)]
#[allow(dead_code)]
pub enum PropMode {
Replace = ffi::PropModeReplace as isize,
Prepend = ffi::PropModePrepend as isize,

View File

@@ -136,23 +136,23 @@ impl UnownedWindow {
})
.unwrap_or_else(|| monitors.swap_remove(0))
};
let dpi_factor = guessed_monitor.scale_factor();
let scale_factor = guessed_monitor.scale_factor();
info!("Guessed window scale factor: {}", dpi_factor);
info!("Guessed window scale factor: {}", scale_factor);
let max_inner_size: Option<(u32, u32)> = window_attrs
.max_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor).into());
.map(|size| size.to_physical::<u32>(scale_factor).into());
let min_inner_size: Option<(u32, u32)> = window_attrs
.min_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor).into());
.map(|size| size.to_physical::<u32>(scale_factor).into());
let dimensions = {
// x11 only applies constraints when the window is actively resized
// by the user, so we have to manually apply the initial constraints
let mut dimensions: (u32, u32) = window_attrs
.inner_size
.map(|size| size.to_physical::<u32>(dpi_factor))
.map(|size| size.to_physical::<u32>(scale_factor))
.or_else(|| Some((800, 600).into()))
.map(Into::into)
.unwrap();
@@ -324,10 +324,10 @@ impl UnownedWindow {
{
let mut min_inner_size = window_attrs
.min_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor));
.map(|size| size.to_physical::<u32>(scale_factor));
let mut max_inner_size = window_attrs
.max_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor));
.map(|size| size.to_physical::<u32>(scale_factor));
if !window_attrs.resizable {
if util::wm_name_is_one_of(&["Xfwm4"]) {
@@ -351,12 +351,12 @@ impl UnownedWindow {
normal_hints.set_resize_increments(
pl_attribs
.resize_increments
.map(|size| size.to_physical::<u32>(dpi_factor).into()),
.map(|size| size.to_physical::<u32>(scale_factor).into()),
);
normal_hints.set_base_size(
pl_attribs
.base_size
.map(|size| size.to_physical::<u32>(dpi_factor).into()),
.map(|size| size.to_physical::<u32>(scale_factor).into()),
);
xconn.set_normal_hints(window.xwindow, normal_hints).queue();
}
@@ -1053,8 +1053,8 @@ impl UnownedWindow {
#[inline]
pub fn set_inner_size(&self, size: Size) {
let dpi_factor = self.scale_factor();
let (width, height) = size.to_physical::<u32>(dpi_factor).into();
let scale_factor = self.scale_factor();
let (width, height) = size.to_physical::<u32>(scale_factor).into();
self.set_inner_size_physical(width, height);
}
@@ -1097,16 +1097,16 @@ impl UnownedWindow {
pub(crate) fn adjust_for_dpi(
&self,
old_dpi_factor: f64,
new_dpi_factor: f64,
old_scale_factor: f64,
new_scale_factor: f64,
width: u32,
height: u32,
shared_state: &SharedState,
) -> (u32, u32) {
let scale_factor = new_dpi_factor / old_dpi_factor;
let scale_factor = new_scale_factor / old_scale_factor;
self.update_normal_hints(|normal_hints| {
let dpi_adjuster =
|size: Size| -> (u32, u32) { size.to_physical::<u32>(new_dpi_factor).into() };
|size: Size| -> (u32, u32) { size.to_physical::<u32>(new_scale_factor).into() };
let max_size = shared_state.max_inner_size.map(&dpi_adjuster);
let min_size = shared_state.min_inner_size.map(&dpi_adjuster);
let resize_increments = shared_state.resize_increments.map(&dpi_adjuster);
@@ -1146,12 +1146,12 @@ impl UnownedWindow {
self.set_maximizable_inner(resizable).queue();
let dpi_factor = self.scale_factor();
let scale_factor = self.scale_factor();
let min_inner_size = min_size
.map(|size| size.to_physical::<u32>(dpi_factor))
.map(|size| size.to_physical::<u32>(scale_factor))
.map(Into::into);
let max_inner_size = max_size
.map(|size| size.to_physical::<u32>(dpi_factor))
.map(|size| size.to_physical::<u32>(scale_factor))
.map(Into::into);
self.update_normal_hints(|normal_hints| {
normal_hints.set_min_size(min_inner_size);

View File

@@ -111,12 +111,7 @@ pub struct XError {
pub minor_code: u8,
}
impl Error for XError {
#[inline]
fn description(&self) -> &str {
&self.description
}
}
impl Error for XError {}
impl fmt::Display for XError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
@@ -144,17 +139,18 @@ impl From<ffi::OpenError> for XNotSupported {
}
}
impl Error for XNotSupported {
#[inline]
fn description(&self) -> &str {
match *self {
impl XNotSupported {
fn description(&self) -> &'static str {
match self {
XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries",
XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server",
}
}
}
impl Error for XNotSupported {
#[inline]
fn cause(&self) -> Option<&dyn Error> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
XNotSupported::LibraryOpenError(ref err) => Some(err),
_ => None,

View File

@@ -0,0 +1,208 @@
// Normally when you run or distribute a macOS app, it's bundled: it's in one
// of those fun little folders that you have to right click "Show Package
// Contents" on, and usually contains myriad delights including, but not
// limited to, plists, icons, and of course, your beloved executable. However,
// when you use `cargo run`, your app is unbundled - it's just a lonely, bare
// executable.
//
// Apple isn't especially fond of unbundled apps, which is to say, they seem to
// barely be supported. If you move the mouse while opening a winit window from
// an unbundled app, the window will fail to activate and be in a grayed-out
// uninteractable state. Switching to another app and back is the only way to
// get the winit window into a normal state. None of this happens if the app is
// bundled, i.e. when running via Xcode.
//
// To workaround this, we just switch focus to the Dock and then switch back to
// our app. We only do this for unbundled apps, and only when they fail to
// become active on their own.
//
// This solution was derived from this Godot PR:
// https://github.com/godotengine/godot/pull/17187
// (which appears to be based on https://stackoverflow.com/a/7602677)
// The curious specialness of mouse motions is touched upon here:
// https://github.com/godotengine/godot/issues/8653#issuecomment-358130512
//
// We omit the 2nd step of the solution used in Godot, since it appears to have
// no effect - I speculate that it's just technical debt picked up from the SO
// answer; the API used is fairly exotic, and was historically used for very
// old versions of macOS that didn't support `activateIgnoringOtherApps`, i.e.
// in previous versions of SDL:
// https://hg.libsdl.org/SDL/file/c0bcc39a3491/src/video/cocoa/SDL_cocoaevents.m#l322
//
// The `performSelector` delays in the Godot solution are used for sequencing,
// since refocusing the app will fail if the call is made before it finishes
// unfocusing. The delays used there are much smaller than the ones in the
// original SO answer, presumably because they found the fastest delay that
// works reliably through trial and error. Instead of using delays, we just
// handle `applicationDidResignActive`; despite the app not activating reliably,
// that still triggers when we switch focus to the Dock.
//
// The Godot solution doesn't appear to skip the hack when an unbundled app
// activates normally. Checking for this is difficult, since if you call
// `isActive` too early, it will always be `NO`. Even though we receive
// `applicationDidResignActive` when switching focus to the Dock, we never
// receive a preceding `applicationDidBecomeActive` if the app fails to
// activate normally. I wasn't able to find a proper point in time to perform
// the `isActive` check, so we instead check for the cause of the quirk: if
// any mouse motion occurs prior to us receiving `applicationDidResignActive`,
// we assume the app failed to become active.
//
// Fun fact: this issue is still present in GLFW
// (https://github.com/glfw/glfw/issues/1515)
//
// A similar issue was found in SDL, but the resolution doesn't seem to work
// for us: https://bugzilla.libsdl.org/show_bug.cgi?id=3051
use super::util;
use cocoa::{
appkit::{NSApp, NSApplicationActivateIgnoringOtherApps},
base::id,
foundation::NSUInteger,
};
use objc::runtime::{Object, Sel, BOOL, NO, YES};
use std::{
os::raw::c_void,
sync::atomic::{AtomicBool, Ordering},
};
#[derive(Debug, Default)]
pub struct State {
// Indicates that the hack has either completed or been skipped.
activated: AtomicBool,
// Indicates that the mouse has moved at some point in time.
mouse_moved: AtomicBool,
// Indicates that the hack is in progress, and that we should refocus when
// the app resigns active.
needs_refocus: AtomicBool,
}
impl State {
pub fn name() -> &'static str {
"activationHackState"
}
pub fn new() -> *mut c_void {
let this = Box::new(Self::default());
Box::into_raw(this) as *mut c_void
}
pub unsafe fn free(this: *mut Self) {
Box::from_raw(this);
}
pub unsafe fn get_ptr(obj: &Object) -> *mut Self {
let this: *mut c_void = *(*obj).get_ivar(Self::name());
assert!(!this.is_null(), "`activationHackState` pointer was null");
this as *mut Self
}
pub unsafe fn set_activated(obj: &Object, value: bool) {
let this = Self::get_ptr(obj);
(*this).activated.store(value, Ordering::Release);
}
unsafe fn get_activated(obj: &Object) -> bool {
let this = Self::get_ptr(obj);
(*this).activated.load(Ordering::Acquire)
}
pub unsafe fn set_mouse_moved(obj: &Object, value: bool) {
let this = Self::get_ptr(obj);
(*this).mouse_moved.store(value, Ordering::Release);
}
pub unsafe fn get_mouse_moved(obj: &Object) -> bool {
let this = Self::get_ptr(obj);
(*this).mouse_moved.load(Ordering::Acquire)
}
pub unsafe fn set_needs_refocus(obj: &Object, value: bool) {
let this = Self::get_ptr(obj);
(*this).needs_refocus.store(value, Ordering::Release);
}
unsafe fn get_needs_refocus(obj: &Object) -> bool {
let this = Self::get_ptr(obj);
(*this).needs_refocus.load(Ordering::Acquire)
}
}
// This is the entry point for the hack - if the app is unbundled and a mouse
// movement occurs before the app activates, it will trigger the hack. Because
// mouse movements prior to activation are the cause of this quirk, they should
// be a reliable way to determine if the hack needs to be performed.
pub extern "C" fn mouse_moved(this: &Object, _: Sel, _: id) {
trace!("Triggered `activationHackMouseMoved`");
unsafe {
if !State::get_activated(this) {
// We check if `CFBundleName` is undefined to determine if the
// app is unbundled.
if let None = util::app_name() {
info!("App detected as unbundled");
unfocus(this);
} else {
info!("App detected as bundled");
}
}
}
trace!("Completed `activationHackMouseMoved`");
}
// Switch focus to the dock.
unsafe fn unfocus(this: &Object) {
// We only perform the hack if the app failed to activate, since otherwise,
// there'd be a gross (but fast) flicker as it unfocused and then refocused.
// However, we only enter this function if we detect mouse movement prior
// to activation, so this should always be `NO`.
//
// Note that this check isn't necessarily reliable in detecting a violation
// of the invariant above, since it's not guaranteed that activation will
// resolve before this point. In other words, it can spuriously return `NO`.
// This is also why the mouse motion approach was chosen, since it's not
// obvious how to sequence this check - if someone knows how to, then that
// would almost surely be a cleaner approach.
let active: BOOL = msg_send![NSApp(), isActive];
if active == YES {
error!("Unbundled app activation hack triggered on an app that's already active; this shouldn't happen!");
} else {
info!("Performing unbundled app activation hack");
let dock_bundle_id = util::ns_string_id_ref("com.apple.dock");
let dock_array: id = msg_send![
class!(NSRunningApplication),
runningApplicationsWithBundleIdentifier: *dock_bundle_id
];
let dock_array_len: NSUInteger = msg_send![dock_array, count];
if dock_array_len == 0 {
error!("The Dock doesn't seem to be running, so switching focus to it is impossible");
} else {
State::set_needs_refocus(this, true);
let dock: id = msg_send![dock_array, objectAtIndex: 0];
// This will trigger `applicationDidResignActive`, which will in
// turn call `refocus`.
let status: BOOL = msg_send![
dock,
activateWithOptions: NSApplicationActivateIgnoringOtherApps
];
if status == NO {
error!("Failed to switch focus to Dock");
}
}
}
}
// Switch focus back to our app, causing the user to rejoice!
pub unsafe fn refocus(this: &Object) {
if State::get_needs_refocus(this) {
State::set_needs_refocus(this, false);
let app: id = msg_send![class!(NSRunningApplication), currentApplication];
// Simply calling `NSApp activateIgnoringOtherApps` doesn't work. The
// nuanced difference isn't clear to me, but hey, I tried.
let success: BOOL = msg_send![
app,
activateWithOptions: NSApplicationActivateIgnoringOtherApps
];
if success == NO {
error!("Failed to refocus app");
}
}
}

View File

@@ -2,17 +2,15 @@ use std::collections::VecDeque;
use cocoa::{
appkit::{self, NSEvent},
base::id,
base::{id, nil},
};
use objc::{
declare::ClassDecl,
runtime::{Class, Object, Sel},
};
use crate::{
event::{DeviceEvent, ElementState, Event},
platform_impl::platform::{app_state::AppState, event::EventWrapper, util, DEVICE_ID},
};
use super::{activation_hack, app_state::AppState, event::EventWrapper, util, DEVICE_ID};
use crate::event::{DeviceEvent, ElementState, Event};
pub struct AppClass(pub *const Class);
unsafe impl Send for AppClass {}
@@ -51,14 +49,14 @@ extern "C" fn send_event(this: &Object, _sel: Sel, event: id) {
let key_window: id = msg_send![this, keyWindow];
let _: () = msg_send![key_window, sendEvent: event];
} else {
maybe_dispatch_device_event(event);
maybe_dispatch_device_event(this, event);
let superclass = util::superclass(this);
let _: () = msg_send![super(this, superclass), sendEvent: event];
}
}
}
unsafe fn maybe_dispatch_device_event(event: id) {
unsafe fn maybe_dispatch_device_event(this: &Object, event: id) {
let event_type = event.eventType();
match event_type {
appkit::NSMouseMoved
@@ -100,6 +98,21 @@ unsafe fn maybe_dispatch_device_event(event: id) {
}
AppState::queue_events(events);
// Notify the delegate when the first mouse move occurs. This is
// used for the unbundled app activation hack, which needs to know
// if any mouse motions occurred prior to the app activating.
let delegate: id = msg_send![this, delegate];
assert_ne!(delegate, nil);
if !activation_hack::State::get_mouse_moved(&*delegate) {
activation_hack::State::set_mouse_moved(&*delegate, true);
let () = msg_send![
delegate,
performSelector: sel!(activationHackMouseMoved:)
withObject: nil
afterDelay: 0.0
];
}
}
appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => {
let mut events = VecDeque::with_capacity(1);

View File

@@ -1,10 +1,10 @@
use super::{activation_hack, app_state::AppState};
use cocoa::base::id;
use objc::{
declare::ClassDecl,
runtime::{Class, Object, Sel, BOOL, YES},
runtime::{Class, Object, Sel},
};
use crate::platform_impl::platform::app_state::AppState;
use std::os::raw::c_void;
pub struct AppDelegateClass(pub *const Class);
unsafe impl Send for AppDelegateClass {}
@@ -15,90 +15,67 @@ lazy_static! {
let superclass = class!(NSResponder);
let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap();
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
decl.add_method(
sel!(applicationDidFinishLaunching:),
did_finish_launching as extern "C" fn(&Object, Sel, id) -> BOOL,
did_finish_launching as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationDidBecomeActive:),
did_become_active as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationWillResignActive:),
will_resign_active as extern "C" fn(&Object, Sel, id),
sel!(applicationDidResignActive:),
did_resign_active as extern "C" fn(&Object, Sel, id),
);
decl.add_ivar::<*mut c_void>(activation_hack::State::name());
decl.add_method(
sel!(applicationWillEnterForeground:),
will_enter_foreground as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationDidEnterBackground:),
did_enter_background as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationWillTerminate:),
will_terminate as extern "C" fn(&Object, Sel, id),
sel!(activationHackMouseMoved:),
activation_hack::mouse_moved as extern "C" fn(&Object, Sel, id),
);
AppDelegateClass(decl.register())
};
}
extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) -> BOOL {
trace!("Triggered `didFinishLaunching`");
extern "C" fn new(class: &Class, _: Sel) -> id {
unsafe {
let this: id = msg_send![class, alloc];
let this: id = msg_send![this, init];
(*this).set_ivar(
activation_hack::State::name(),
activation_hack::State::new(),
);
this
}
}
extern "C" fn dealloc(this: &Object, _: Sel) {
unsafe {
activation_hack::State::free(activation_hack::State::get_ptr(this));
}
}
extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) {
trace!("Triggered `applicationDidFinishLaunching`");
AppState::launched();
trace!("Completed `didFinishLaunching`");
YES
trace!("Completed `applicationDidFinishLaunching`");
}
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
trace!("Triggered `didBecomeActive`");
/*unsafe {
HANDLER.lock().unwrap().handle_nonuser_event(Event::Resumed)
}*/
trace!("Completed `didBecomeActive`");
extern "C" fn did_become_active(this: &Object, _: Sel, _: id) {
trace!("Triggered `applicationDidBecomeActive`");
unsafe {
activation_hack::State::set_activated(this, true);
}
trace!("Completed `applicationDidBecomeActive`");
}
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
trace!("Triggered `willResignActive`");
/*unsafe {
HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended)
}*/
trace!("Completed `willResignActive`");
}
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {
trace!("Triggered `willEnterForeground`");
trace!("Completed `willEnterForeground`");
}
extern "C" fn did_enter_background(_: &Object, _: Sel, _: id) {
trace!("Triggered `didEnterBackground`");
trace!("Completed `didEnterBackground`");
}
extern "C" fn will_terminate(_: &Object, _: Sel, _: id) {
trace!("Triggered `willTerminate`");
/*unsafe {
let app: id = msg_send![class!(UIApplication), sharedApplication];
let windows: id = msg_send![app, windows];
let windows_enum: id = msg_send![windows, objectEnumerator];
let mut events = Vec::new();
loop {
let window: id = msg_send![windows_enum, nextObject];
if window == nil {
break
}
let is_winit_window: BOOL = msg_send![window, isKindOfClass:class!(WinitUIWindow)];
if is_winit_window == YES {
events.push(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Destroyed,
});
}
}
HANDLER.lock().unwrap().handle_nonuser_events(events);
HANDLER.lock().unwrap().terminated();
}*/
trace!("Completed `willTerminate`");
extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) {
trace!("Triggered `applicationDidResignActive`");
unsafe {
activation_hack::refocus(this);
}
trace!("Completed `applicationDidResignActive`");
}

View File

@@ -12,11 +12,13 @@ use std::{
};
use cocoa::{
appkit::{NSApp, NSWindow},
base::nil,
foundation::{NSAutoreleasePool, NSSize, NSString},
appkit::{NSApp, NSEventType::NSApplicationDefined, NSWindow},
base::{id, nil},
foundation::{NSAutoreleasePool, NSPoint, NSSize},
};
use objc::runtime::YES;
use crate::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
@@ -29,7 +31,6 @@ use crate::{
},
window::WindowId,
};
use objc::runtime::Object;
lazy_static! {
static ref HANDLER: Handler = Default::default();
@@ -339,21 +340,23 @@ impl AppState {
let pool = NSAutoreleasePool::new(nil);
let windows: *const Object = msg_send![NSApp(), windows];
let window: *const Object = msg_send![windows, objectAtIndex:0];
let windows: id = msg_send![NSApp(), windows];
let window: id = msg_send![windows, objectAtIndex:0];
assert_ne!(window, nil);
let title: *const Object = msg_send![window, title];
assert_ne!(title, nil);
let postfix = NSString::alloc(nil).init_str("*");
let some_unique_title: *const Object =
msg_send![title, stringByAppendingString: postfix];
assert_ne!(some_unique_title, nil);
// To stop event loop immediately, we need to send some UI event here.
let _: () = msg_send![window, setTitle: some_unique_title];
// And restore it.
let _: () = msg_send![window, setTitle: title];
let dummy_event: id = msg_send![class!(NSEvent),
otherEventWithType: NSApplicationDefined
location: NSPoint::new(0.0, 0.0)
modifierFlags: 0
timestamp: 0
windowNumber: 0
context: nil
subtype: 0
data1: 0
data2: 0
];
// To stop event loop immediately, we need to post some event here.
let _: () = msg_send![window, postEvent: dummy_event atStart: YES];
pool.drain();
};

View File

@@ -287,6 +287,7 @@ pub unsafe fn modifier_event(
let scancode = get_scancode(ns_event);
let virtual_keycode = scancode_to_keycode(scancode);
#[allow(deprecated)]
Some(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {

View File

@@ -117,6 +117,14 @@ pub struct Proxy<T> {
unsafe impl<T: Send> Send for Proxy<T> {}
impl<T> Drop for Proxy<T> {
fn drop(&mut self) {
unsafe {
CFRelease(self.source as _);
}
}
}
impl<T> Clone for Proxy<T> {
fn clone(&self) -> Self {
Proxy::new(self.sender.clone())

View File

@@ -1,5 +1,6 @@
#![cfg(target_os = "macos")]
mod activation_hack;
mod app;
mod app_delegate;
mod app_state;
@@ -24,6 +25,8 @@ use crate::{
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;

View File

@@ -1,15 +1,14 @@
use std::{collections::VecDeque, fmt};
use super::ffi;
use super::{ffi, util};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::util::IdRef,
};
use cocoa::{
appkit::NSScreen,
base::{id, nil},
foundation::{NSString, NSUInteger},
foundation::NSUInteger,
};
use core_foundation::{
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
@@ -303,7 +302,7 @@ impl MonitorHandle {
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
let screens = NSScreen::screens(nil);
let count: NSUInteger = msg_send![screens, count];
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
let key = util::ns_string_id_ref("NSScreenNumber");
for i in 0..count {
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
let device_description = NSScreen::deviceDescription(screen);

View File

@@ -122,7 +122,7 @@ extern "C" fn control_flow_begin_handler(
}
// end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopDestroyed would get sent after EventsCleared
// without that, LoopDestroyed would get sent after MainEventsCleared
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,

View File

@@ -1,20 +1,36 @@
use std::{
os::raw::c_void,
ops::Deref,
sync::{Mutex, Weak},
};
use cocoa::{
appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask},
base::{id, nil},
foundation::{NSAutoreleasePool, NSPoint, NSSize, NSString},
foundation::{NSPoint, NSSize, NSString},
};
use dispatch::ffi::{dispatch_async_f, dispatch_get_main_queue, dispatch_sync_f};
use dispatch::Queue;
use objc::rc::autoreleasepool;
use crate::{
dpi::LogicalSize,
platform_impl::platform::{ffi, util::IdRef, window::SharedState},
};
// Unsafe wrapper type that allows us to dispatch things that aren't Send.
// This should *only* be used to dispatch to the main queue.
// While it is indeed not guaranteed that these types can safely be sent to
// other threads, we know that they're safe to use on the main thread.
struct MainThreadSafe<T>(T);
unsafe impl<T> Send for MainThreadSafe<T> {}
impl<T> Deref for MainThreadSafe<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
ns_window.setStyleMask_(mask);
// If we don't do this, key handling will break
@@ -22,203 +38,55 @@ unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
ns_window.makeFirstResponder_(ns_view);
}
struct SetStyleMaskData {
ns_window: id,
ns_view: id,
mask: NSWindowStyleMask,
}
impl SetStyleMaskData {
fn new_ptr(ns_window: id, ns_view: id, mask: NSWindowStyleMask) -> *mut Self {
Box::into_raw(Box::new(SetStyleMaskData {
ns_window,
ns_view,
mask,
}))
}
}
extern "C" fn set_style_mask_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetStyleMaskData;
{
let context = &*context_ptr;
set_style_mask(context.ns_window, context.ns_view, context.mask);
}
Box::from_raw(context_ptr);
}
}
// Always use this function instead of trying to modify `styleMask` directly!
// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch.
// Otherwise, this would vomit out errors about not being on the main thread
// and fail to do anything.
pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_style_mask_callback,
);
let ns_window = MainThreadSafe(ns_window);
let ns_view = MainThreadSafe(ns_view);
Queue::main().exec_async(move || {
set_style_mask(*ns_window, *ns_view, mask);
});
}
pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask);
if msg_send![class!(NSThread), isMainThread] {
set_style_mask_callback(context as *mut _);
set_style_mask(ns_window, ns_view, mask);
} else {
dispatch_sync_f(
dispatch_get_main_queue(),
context as *mut _,
set_style_mask_callback,
);
let ns_window = MainThreadSafe(ns_window);
let ns_view = MainThreadSafe(ns_view);
Queue::main().exec_sync(move || {
set_style_mask(*ns_window, *ns_view, mask);
})
}
}
struct SetContentSizeData {
ns_window: id,
size: LogicalSize<f64>,
}
impl SetContentSizeData {
fn new_ptr(ns_window: id, size: LogicalSize<f64>) -> *mut Self {
Box::into_raw(Box::new(SetContentSizeData { ns_window, size }))
}
}
extern "C" fn set_content_size_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetContentSizeData;
{
let context = &*context_ptr;
NSWindow::setContentSize_(
context.ns_window,
NSSize::new(
context.size.width as CGFloat,
context.size.height as CGFloat,
),
);
}
Box::from_raw(context_ptr);
}
}
// `setContentSize:` isn't thread-safe either, though it doesn't log any errors
// and just fails silently. Anyway, GCD to the rescue!
pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize<f64>) {
let context = SetContentSizeData::new_ptr(ns_window, size);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_content_size_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.setContentSize_(NSSize::new(size.width as CGFloat, size.height as CGFloat));
});
}
struct SetFrameTopLeftPointData {
ns_window: id,
point: NSPoint,
}
impl SetFrameTopLeftPointData {
fn new_ptr(ns_window: id, point: NSPoint) -> *mut Self {
Box::into_raw(Box::new(SetFrameTopLeftPointData { ns_window, point }))
}
}
extern "C" fn set_frame_top_left_point_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetFrameTopLeftPointData;
{
let context = &*context_ptr;
NSWindow::setFrameTopLeftPoint_(context.ns_window, context.point);
}
Box::from_raw(context_ptr);
}
}
// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy
// to log errors.
pub unsafe fn set_frame_top_left_point_async(ns_window: id, point: NSPoint) {
let context = SetFrameTopLeftPointData::new_ptr(ns_window, point);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_frame_top_left_point_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.setFrameTopLeftPoint_(point);
});
}
struct SetLevelData {
ns_window: id,
level: ffi::NSWindowLevel,
}
impl SetLevelData {
fn new_ptr(ns_window: id, level: ffi::NSWindowLevel) -> *mut Self {
Box::into_raw(Box::new(SetLevelData { ns_window, level }))
}
}
extern "C" fn set_level_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetLevelData;
{
let context = &*context_ptr;
context.ns_window.setLevel_(context.level as _);
}
Box::from_raw(context_ptr);
}
}
// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently.
pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) {
let context = SetLevelData::new_ptr(ns_window, level);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_level_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.setLevel_(level as _);
});
}
struct ToggleFullScreenData {
ns_window: id,
ns_view: id,
not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>,
}
impl ToggleFullScreenData {
fn new_ptr(
ns_window: id,
ns_view: id,
not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>,
) -> *mut Self {
Box::into_raw(Box::new(ToggleFullScreenData {
ns_window,
ns_view,
not_fullscreen,
shared_state,
}))
}
}
extern "C" fn toggle_full_screen_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut ToggleFullScreenData;
{
let context = &*context_ptr;
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
// set a normal style temporarily. The previous state will be
// restored in `WindowDelegate::window_did_exit_fullscreen`.
if context.not_fullscreen {
let curr_mask = context.ns_window.styleMask();
let required = NSWindowStyleMask::NSTitledWindowMask
| NSWindowStyleMask::NSResizableWindowMask;
if !curr_mask.contains(required) {
set_style_mask(context.ns_window, context.ns_view, required);
if let Some(shared_state) = context.shared_state.upgrade() {
trace!("Locked shared state in `toggle_full_screen_callback`");
let mut shared_state_lock = shared_state.lock().unwrap();
(*shared_state_lock).saved_style = Some(curr_mask);
trace!("Unlocked shared state in `toggle_full_screen_callback`");
}
}
}
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
context.ns_window.setLevel_(0);
context.ns_window.toggleFullScreen_(nil);
}
Box::from_raw(context_ptr);
}
}
// `toggleFullScreen` is thread-safe, but our additional logic to account for
// window styles isn't.
pub unsafe fn toggle_full_screen_async(
@@ -227,91 +95,42 @@ pub unsafe fn toggle_full_screen_async(
not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>,
) {
let context = ToggleFullScreenData::new_ptr(ns_window, ns_view, not_fullscreen, shared_state);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
toggle_full_screen_callback,
);
}
extern "C" fn restore_display_mode_callback(screen: *mut c_void) {
unsafe {
let screen = Box::from_raw(screen as *mut u32);
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(*screen), ffi::kCGErrorSuccess);
}
}
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
dispatch_async_f(
dispatch_get_main_queue(),
Box::into_raw(Box::new(ns_screen)) as *mut _,
restore_display_mode_callback,
);
}
struct SetMaximizedData {
ns_window: id,
is_zoomed: bool,
maximized: bool,
shared_state: Weak<Mutex<SharedState>>,
}
impl SetMaximizedData {
fn new_ptr(
ns_window: id,
is_zoomed: bool,
maximized: bool,
shared_state: Weak<Mutex<SharedState>>,
) -> *mut Self {
Box::into_raw(Box::new(SetMaximizedData {
ns_window,
is_zoomed,
maximized,
shared_state,
}))
}
}
extern "C" fn set_maximized_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetMaximizedData;
{
let context = &*context_ptr;
if let Some(shared_state) = context.shared_state.upgrade() {
trace!("Locked shared state in `set_maximized`");
let mut shared_state_lock = shared_state.lock().unwrap();
// Save the standard frame sized if it is not zoomed
if !context.is_zoomed {
shared_state_lock.standard_frame = Some(NSWindow::frame(context.ns_window));
let ns_window = MainThreadSafe(ns_window);
let ns_view = MainThreadSafe(ns_view);
let shared_state = MainThreadSafe(shared_state);
Queue::main().exec_async(move || {
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
// set a normal style temporarily. The previous state will be
// restored in `WindowDelegate::window_did_exit_fullscreen`.
if not_fullscreen {
let curr_mask = ns_window.styleMask();
let required =
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
if !curr_mask.contains(required) {
set_style_mask(*ns_window, *ns_view, required);
if let Some(shared_state) = shared_state.upgrade() {
trace!("Locked shared state in `toggle_full_screen_callback`");
let mut shared_state_lock = shared_state.lock().unwrap();
(*shared_state_lock).saved_style = Some(curr_mask);
trace!("Unlocked shared state in `toggle_full_screen_callback`");
}
shared_state_lock.maximized = context.maximized;
let curr_mask = context.ns_window.styleMask();
if shared_state_lock.fullscreen.is_some() {
// Handle it in window_did_exit_fullscreen
return;
} else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) {
// Just use the native zoom if resizable
context.ns_window.zoom_(nil);
} else {
// if it's not resizable, we set the frame directly
let new_rect = if context.maximized {
let screen = NSScreen::mainScreen(nil);
NSScreen::visibleFrame(screen)
} else {
shared_state_lock.saved_standard_frame()
};
context.ns_window.setFrame_display_(new_rect, 0);
}
trace!("Unlocked shared state in `set_maximized`");
}
}
Box::from_raw(context_ptr);
}
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
ns_window.setLevel_(0);
ns_window.toggleFullScreen_(nil);
});
}
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
Queue::main().exec_async(move || {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess);
});
}
// `setMaximized` is not thread-safe
pub unsafe fn set_maximized_async(
ns_window: id,
@@ -319,127 +138,79 @@ pub unsafe fn set_maximized_async(
maximized: bool,
shared_state: Weak<Mutex<SharedState>>,
) {
let context = SetMaximizedData::new_ptr(ns_window, is_zoomed, maximized, shared_state);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_maximized_callback,
);
let ns_window = MainThreadSafe(ns_window);
let shared_state = MainThreadSafe(shared_state);
Queue::main().exec_async(move || {
if let Some(shared_state) = shared_state.upgrade() {
trace!("Locked shared state in `set_maximized`");
let mut shared_state_lock = shared_state.lock().unwrap();
// Save the standard frame sized if it is not zoomed
if !is_zoomed {
shared_state_lock.standard_frame = Some(NSWindow::frame(*ns_window));
}
shared_state_lock.maximized = maximized;
let curr_mask = ns_window.styleMask();
if shared_state_lock.fullscreen.is_some() {
// Handle it in window_did_exit_fullscreen
return;
} else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) {
// Just use the native zoom if resizable
ns_window.zoom_(nil);
} else {
// if it's not resizable, we set the frame directly
let new_rect = if maximized {
let screen = NSScreen::mainScreen(nil);
NSScreen::visibleFrame(screen)
} else {
shared_state_lock.saved_standard_frame()
};
ns_window.setFrame_display_(new_rect, 0);
}
trace!("Unlocked shared state in `set_maximized`");
}
});
}
struct OrderOutData {
ns_window: id,
}
impl OrderOutData {
fn new_ptr(ns_window: id) -> *mut Self {
Box::into_raw(Box::new(OrderOutData { ns_window }))
}
}
extern "C" fn order_out_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut OrderOutData;
{
let context = &*context_ptr;
context.ns_window.orderOut_(nil);
}
Box::from_raw(context_ptr);
}
}
// `orderOut:` isn't thread-safe. Calling it from another thread actually works,
// but with an odd delay.
pub unsafe fn order_out_async(ns_window: id) {
let context = OrderOutData::new_ptr(ns_window);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
order_out_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.orderOut_(nil);
});
}
struct MakeKeyAndOrderFrontData {
ns_window: id,
}
impl MakeKeyAndOrderFrontData {
fn new_ptr(ns_window: id) -> *mut Self {
Box::into_raw(Box::new(MakeKeyAndOrderFrontData { ns_window }))
}
}
extern "C" fn make_key_and_order_front_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut MakeKeyAndOrderFrontData;
{
let context = &*context_ptr;
context.ns_window.makeKeyAndOrderFront_(nil);
}
Box::from_raw(context_ptr);
}
}
// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread
// actually works, but with an odd delay.
pub unsafe fn make_key_and_order_front_async(ns_window: id) {
let context = MakeKeyAndOrderFrontData::new_ptr(ns_window);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
make_key_and_order_front_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.makeKeyAndOrderFront_(nil);
});
}
struct SetTitleData {
ns_window: id,
title: String,
}
impl SetTitleData {
fn new_ptr(ns_window: id, title: String) -> *mut Self {
Box::into_raw(Box::new(SetTitleData { ns_window, title }))
}
}
extern "C" fn set_title_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetTitleData;
{
let context = &*context_ptr;
let title = IdRef::new(NSString::alloc(nil).init_str(&context.title));
context.ns_window.setTitle_(*title);
}
Box::from_raw(context_ptr);
}
}
// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the
// window drag regions, which throws an exception when not done in the main
// thread
pub unsafe fn set_title_async(ns_window: id, title: String) {
let context = SetTitleData::new_ptr(ns_window, title);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_title_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
let title = IdRef::new(NSString::alloc(nil).init_str(&title));
ns_window.setTitle_(*title);
});
}
struct CloseData {
ns_window: id,
}
impl CloseData {
fn new_ptr(ns_window: id) -> *mut Self {
Box::into_raw(Box::new(CloseData { ns_window }))
}
}
extern "C" fn close_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut CloseData;
{
let context = &*context_ptr;
let pool = NSAutoreleasePool::new(nil);
context.ns_window.close();
pool.drain();
}
Box::from_raw(context_ptr);
}
}
// `close:` is thread-safe, but we want the event to be triggered from the main
// thread. Though, it's a good idea to look into that more...
pub unsafe fn close_async(ns_window: id) {
let context = CloseData::new_ptr(ns_window);
dispatch_async_f(dispatch_get_main_queue(), context as *mut _, close_callback);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
autoreleasepool(move || {
ns_window.close();
});
});
}

View File

@@ -8,7 +8,7 @@ use std::ops::{BitAnd, Deref};
use cocoa::{
appkit::{NSApp, NSWindowStyleMask},
base::{id, nil},
foundation::{NSAutoreleasePool, NSRect, NSUInteger},
foundation::{NSAutoreleasePool, NSRect, NSString, NSUInteger},
};
use core_graphics::display::CGDisplay;
use objc::runtime::{Class, Object, Sel, BOOL, YES};
@@ -91,6 +91,22 @@ pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
}
pub unsafe fn ns_string_id_ref(s: &str) -> IdRef {
IdRef::new(NSString::alloc(nil).init_str(s))
}
pub unsafe fn app_name() -> Option<id> {
let bundle: id = msg_send![class!(NSBundle), mainBundle];
let dict: id = msg_send![bundle, infoDictionary];
let key = ns_string_id_ref("CFBundleName");
let app_name: id = msg_send![dict, objectForKey:*key];
if app_name != nil {
Some(app_name)
} else {
None
}
}
pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class {
let superclass: id = msg_send![this, superclass];
&*(superclass as *const _)

View File

@@ -17,6 +17,7 @@ use objc::{
};
use crate::{
dpi::LogicalPosition,
event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseButton,
MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent,
@@ -49,16 +50,22 @@ impl Default for CursorState {
}
}
struct ViewState {
pub(super) struct ViewState {
ns_window: id,
pub cursor_state: Arc<Mutex<CursorState>>,
ime_spot: Option<(f64, f64)>,
raw_characters: Option<String>,
is_key_down: bool,
modifiers: ModifiersState,
pub(super) modifiers: ModifiersState,
tracking_rect: Option<NSInteger>,
}
impl ViewState {
fn get_scale_factor(&self) -> f64 {
(unsafe { NSWindow::backingScaleFactor(self.ns_window) }) as f64
}
}
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<CursorState>>) {
let cursor_state = Default::default();
let cursor_access = Arc::downgrade(&cursor_state);
@@ -616,6 +623,19 @@ fn retrieve_keycode(event: id) -> Option<VirtualKeyCode> {
})
}
// Update `state.modifiers` if `event` has something different
fn update_potentially_stale_modifiers(state: &mut ViewState, event: id) {
let event_modifiers = event_mods(event);
if state.modifiers != event_modifiers {
state.modifiers = event_modifiers;
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::ModifiersChanged(state.modifiers),
}));
}
}
extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
trace!("Triggered `keyDown`");
unsafe {
@@ -631,6 +651,9 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
let is_repeat = msg_send![event, isARepeat];
update_potentially_stale_modifiers(state, event);
#[allow(deprecated)]
let window_event = Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
@@ -683,6 +706,9 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) {
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
update_potentially_stale_modifiers(state, event);
#[allow(deprecated)]
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::KeyboardInput {
@@ -746,16 +772,18 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) {
events.push_back(window_event);
}
let window_id = WindowId(get_window_id(state.ns_window));
for event in events {
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
window_id,
event,
}));
}
AppState::queue_event(EventWrapper::StaticEvent(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::ModifiersChanged(state.modifiers),
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(state.modifiers),
}));
}
trace!("Completed `flagsChanged`");
@@ -797,6 +825,9 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
let event: id = msg_send![NSApp(), currentEvent];
update_potentially_stale_modifiers(state, event);
#[allow(deprecated)]
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::KeyboardInput {
@@ -821,6 +852,8 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
update_potentially_stale_modifiers(state, event);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::MouseInput {
@@ -836,26 +869,32 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem
}
extern "C" fn mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Left, ElementState::Pressed);
}
extern "C" fn mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Left, ElementState::Released);
}
extern "C" fn right_mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Right, ElementState::Pressed);
}
extern "C" fn right_mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Right, ElementState::Released);
}
extern "C" fn other_mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Middle, ElementState::Pressed);
}
extern "C" fn other_mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Middle, ElementState::Released);
}
@@ -882,12 +921,15 @@ fn mouse_motion(this: &Object, event: id) {
let x = view_point.x as f64;
let y = view_rect.size.height as f64 - view_point.y as f64;
let logical_position = LogicalPosition::new(x, y);
update_potentially_stale_modifiers(state, event);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
position: logical_position.to_physical(state.get_scale_factor()),
modifiers: event_mods(event),
},
};
@@ -912,7 +954,7 @@ extern "C" fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) {
extern "C" fn mouse_entered(this: &Object, _sel: Sel, _event: id) {
trace!("Triggered `mouseEntered`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
@@ -925,27 +967,7 @@ extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) {
},
};
let move_event = {
let window_point = event.locationInWindow();
let view_point: NSPoint = msg_send![this,
convertPoint:window_point
fromView:nil // convert from window coordinates
];
let view_rect: NSRect = msg_send![this, frame];
let x = view_point.x as f64;
let y = (view_rect.size.height - view_point.y) as f64;
Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
modifiers: event_mods(event),
},
}
};
AppState::queue_event(EventWrapper::StaticEvent(enter_event));
AppState::queue_event(EventWrapper::StaticEvent(move_event));
}
trace!("Completed `mouseEntered`");
}
@@ -970,6 +992,9 @@ extern "C" fn mouse_exited(this: &Object, _sel: Sel, _event: id) {
extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
trace!("Triggered `scrollWheel`");
mouse_motion(this, event);
unsafe {
let delta = {
let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY());
@@ -995,6 +1020,8 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
update_potentially_stale_modifiers(state, event);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::MouseWheel {
@@ -1013,6 +1040,9 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) {
trace!("Triggered `pressureChangeWithEvent`");
mouse_motion(this, event);
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);

View File

@@ -36,7 +36,7 @@ use cocoa::{
NSWindow, NSWindowButton, NSWindowStyleMask,
},
base::{id, nil},
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize},
};
use core_graphics::display::{CGDisplay, CGDisplayMode};
use objc::{
@@ -150,10 +150,14 @@ fn create_window(
let mut masks = if !attrs.decorations && !screen.is_some() {
// Resizable UnownedWindow without a titlebar or borders
// if decorations is set to false, ignore pl_attrs
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
NSWindowStyleMask::NSBorderlessWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
} else if pl_attrs.titlebar_hidden {
// if the titlebar is hidden, ignore other pl_attrs
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
NSWindowStyleMask::NSBorderlessWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
} else {
// default case, resizable window with titlebar and titlebar buttons
NSWindowStyleMask::NSClosableWindowMask
@@ -178,7 +182,7 @@ fn create_window(
NO,
));
let res = ns_window.non_nil().map(|ns_window| {
let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title));
let title = util::ns_string_id_ref(&attrs.title);
ns_window.setReleasedWhenClosed_(NO);
ns_window.setTitle_(*title);
ns_window.setAcceptsMouseMovedEvents_(YES);
@@ -336,7 +340,7 @@ impl UnownedWindow {
let input_context = unsafe { util::create_input_context(*ns_view) };
let dpi_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 };
let scale_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 };
unsafe {
if win_attribs.transparent {
@@ -346,11 +350,11 @@ impl UnownedWindow {
ns_app.activateIgnoringOtherApps_(YES);
win_attribs.min_inner_size.map(|dim| {
let logical_dim = dim.to_logical(dpi_factor);
let logical_dim = dim.to_logical(scale_factor);
set_min_inner_size(*ns_window, logical_dim)
});
win_attribs.max_inner_size.map(|dim| {
let logical_dim = dim.to_logical(dpi_factor);
let logical_dim = dim.to_logical(scale_factor);
set_max_inner_size(*ns_window, logical_dim)
});
@@ -374,7 +378,7 @@ impl UnownedWindow {
let decorations = win_attribs.decorations;
let inner_rect = win_attribs
.inner_size
.map(|size| size.to_physical(dpi_factor));
.map(|size| size.to_physical(scale_factor));
let window = Arc::new(UnownedWindow {
ns_view,
@@ -446,8 +450,8 @@ impl UnownedWindow {
frame_rect.origin.x as f64,
util::bottom_left_to_top_left(frame_rect),
);
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
@@ -458,13 +462,13 @@ impl UnownedWindow {
content_rect.origin.x as f64,
util::bottom_left_to_top_left(content_rect),
);
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
pub fn set_outer_position(&self, position: Position) {
let dpi_factor = self.scale_factor();
let position = position.to_logical(dpi_factor);
let scale_factor = self.scale_factor();
let position = position.to_logical(scale_factor);
let dummy = NSRect::new(
NSPoint::new(
position.x,
@@ -484,8 +488,8 @@ impl UnownedWindow {
let view_frame = unsafe { NSView::frame(*self.ns_view) };
let logical: LogicalSize<f64> =
(view_frame.size.width as f64, view_frame.size.height as f64).into();
let dpi_factor = self.scale_factor();
logical.to_physical(dpi_factor)
let scale_factor = self.scale_factor();
logical.to_physical(scale_factor)
}
#[inline]
@@ -493,15 +497,15 @@ impl UnownedWindow {
let view_frame = unsafe { NSWindow::frame(*self.ns_window) };
let logical: LogicalSize<f64> =
(view_frame.size.width as f64, view_frame.size.height as f64).into();
let dpi_factor = self.scale_factor();
logical.to_physical(dpi_factor)
let scale_factor = self.scale_factor();
logical.to_physical(scale_factor)
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
unsafe {
let dpi_factor = self.scale_factor();
util::set_content_size_async(*self.ns_window, size.to_logical(dpi_factor));
let scale_factor = self.scale_factor();
util::set_content_size_async(*self.ns_window, size.to_logical(scale_factor));
}
}
@@ -511,8 +515,8 @@ impl UnownedWindow {
width: 0.0,
height: 0.0,
}));
let dpi_factor = self.scale_factor();
set_min_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor));
let scale_factor = self.scale_factor();
set_min_inner_size(*self.ns_window, dimensions.to_logical(scale_factor));
}
}
@@ -522,8 +526,8 @@ impl UnownedWindow {
width: std::f32::MAX as f64,
height: std::f32::MAX as f64,
}));
let dpi_factor = self.scale_factor();
set_max_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor));
let scale_factor = self.scale_factor();
set_max_inner_size(*self.ns_window, dimensions.to_logical(scale_factor));
}
}
@@ -590,9 +594,9 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> {
let physical_window_position = self.inner_position().unwrap();
let dpi_factor = self.scale_factor();
let window_position = physical_window_position.to_logical::<CGFloat>(dpi_factor);
let logical_cursor_position = cursor_position.to_logical::<CGFloat>(dpi_factor);
let scale_factor = self.scale_factor();
let window_position = physical_window_position.to_logical::<CGFloat>(scale_factor);
let logical_cursor_position = cursor_position.to_logical::<CGFloat>(scale_factor);
let point = appkit::CGPoint {
x: logical_cursor_position.x + window_position.x,
y: logical_cursor_position.y + window_position.y,
@@ -929,8 +933,8 @@ impl UnownedWindow {
#[inline]
pub fn set_ime_position(&self, spot: Position) {
let dpi_factor = self.scale_factor();
let logical_spot = spot.to_logical(dpi_factor);
let scale_factor = self.scale_factor();
let logical_spot = spot.to_logical(scale_factor);
unsafe {
view::set_ime_position(
*self.ns_view,
@@ -946,7 +950,7 @@ impl UnownedWindow {
unsafe {
let screen: id = msg_send![*self.ns_window, screen];
let desc = NSScreen::deviceDescription(screen);
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
let key = util::ns_string_id_ref("NSScreenNumber");
let value = NSDictionary::valueForKey_(desc, *key);
let display_id = msg_send![value, unsignedIntegerValue];
RootMonitorHandle {
@@ -1024,7 +1028,11 @@ impl WindowExtMacOS for UnownedWindow {
if fullscreen {
// Remember the original window's settings
shared_state_lock.standard_frame = Some(NSWindow::frame(*self.ns_window));
// Exclude title bar
shared_state_lock.standard_frame = Some(NSWindow::contentRectForFrameRect_(
*self.ns_window,
NSWindow::frame(*self.ns_window),
));
shared_state_lock.saved_style = Some(self.ns_window.styleMask());
shared_state_lock.save_presentation_opts = Some(app.presentationOptions_());

View File

@@ -16,11 +16,12 @@ use objc::{
use crate::{
dpi::LogicalSize,
event::{Event, WindowEvent},
event::{Event, ModifiersState, WindowEvent},
platform_impl::platform::{
app_state::AppState,
event::{EventProxy, EventWrapper},
util::{self, IdRef},
view::ViewState,
window::{get_window_id, UnownedWindow},
},
window::{Fullscreen, WindowId},
@@ -43,7 +44,7 @@ pub struct WindowDelegateState {
previous_position: Option<(f64, f64)>,
// Used to prevent redundant events.
previous_dpi_factor: f64,
previous_scale_factor: f64,
}
impl WindowDelegateState {
@@ -55,7 +56,7 @@ impl WindowDelegateState {
window: Arc::downgrade(&window),
initial_fullscreen,
previous_position: None,
previous_dpi_factor: scale_factor,
previous_scale_factor: scale_factor,
};
if scale_factor != 1.0 {
@@ -82,11 +83,11 @@ impl WindowDelegateState {
pub fn emit_static_scale_factor_changed_event(&mut self) {
let scale_factor = self.get_scale_factor();
if scale_factor == self.previous_dpi_factor {
if scale_factor == self.previous_scale_factor {
return ();
};
self.previous_dpi_factor = scale_factor;
self.previous_scale_factor = scale_factor;
let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
ns_window: IdRef::retain(*self.ns_window),
suggested_size: self.view_size(),
@@ -319,6 +320,29 @@ extern "C" fn window_did_become_key(this: &Object, _: Sel, _: id) {
extern "C" fn window_did_resign_key(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidResignKey:`");
with_state(this, |state| {
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
// NSWindowDelegate will receive a didResignKey event despite no event
// being received when the modifiers are released. This is because
// flagsChanged events are received by the NSView instead of the
// NSWindowDelegate, and as a result a tracked modifiers state can quite
// easily fall out of synchrony with reality. This requires us to emit
// a synthetic ModifiersChanged event when we lose focus.
//
// Here we (very unsafely) acquire the winitState (a ViewState) from the
// Object referenced by state.ns_view (an IdRef, which is dereferenced
// to an id)
let view_state: &mut ViewState = unsafe {
let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref");
let state_ptr: *mut c_void = *ns_view.get_ivar("winitState");
&mut *(state_ptr as *mut ViewState)
};
// Both update the state and emit a ModifiersChanged event.
if !view_state.modifiers.is_empty() {
view_state.modifiers = ModifiersState::empty();
state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers));
}
state.emit_event(WindowEvent::Focused(false));
});
trace!("Completed `windowDidResignKey:`");

View File

@@ -8,6 +8,7 @@ use std::{
cell::RefCell,
clone::Clone,
collections::{HashSet, VecDeque},
iter,
rc::Rc,
};
@@ -60,7 +61,7 @@ impl<T: 'static> Shared<T> {
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
) {
self.0.runner.replace(Some(Runner::new(event_handler)));
self.send_event(Event::NewEvents(StartCause::Init));
self.init();
let close_instance = self.clone();
backend::on_unload(move || close_instance.handle_unload());
@@ -79,55 +80,98 @@ impl<T: 'static> Shared<T> {
self.0.redraw_pending.borrow_mut().insert(id);
}
// Add an event to the event loop runner
pub fn init(&self) {
let start_cause = Event::NewEvents(StartCause::Init);
self.run_until_cleared(iter::once(start_cause));
}
// Run the polling logic for the Poll ControlFlow, which involves clearing the queue
pub fn poll(&self) {
let start_cause = Event::NewEvents(StartCause::Poll);
self.run_until_cleared(iter::once(start_cause));
}
// Run the logic for waking from a WaitUntil, which involves clearing the queue
// Generally there shouldn't be events built up when this is called
pub fn resume_time_reached(&self, start: Instant, requested_resume: Instant) {
let start_cause = Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume,
});
self.run_until_cleared(iter::once(start_cause));
}
// Add an event to the event loop runner, from the user or an event handler
//
// It will determine if the event should be immediately sent to the user or buffered for later
pub fn send_event(&self, event: Event<'static, T>) {
self.send_events(iter::once(event));
}
// Add a series of events to the event loop runner
//
// It will determine if the event should be immediately sent to the user or buffered for later
pub fn send_events(&self, events: impl Iterator<Item = Event<'static, T>>) {
// If the event loop is closed, it should discard any new events
if self.is_closed() {
return;
}
// Determine if event handling is in process, and then release the borrow on the runner
let (start_cause, event_is_start) = match *self.0.runner.borrow() {
Some(ref runner) if !runner.is_busy => {
if let Event::NewEvents(cause) = event {
(cause, true)
} else {
(
match runner.state {
State::Init => StartCause::Init,
State::Poll { .. } => StartCause::Poll,
State::Wait { start } => StartCause::WaitCancelled {
start,
requested_resume: None,
},
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
start,
requested_resume: Some(end),
},
State::Exit => {
return;
}
},
false,
)
// If we can run the event processing right now, or need to queue this and wait for later
let mut process_immediately = true;
if let Some(ref runner) = &*self.0.runner.borrow() {
// If we're currently polling, queue this and wait for the poll() method to be called
if let State::Poll { .. } = runner.state {
process_immediately = false;
}
// If the runner is busy, queue this and wait for it to process it later
if runner.is_busy {
process_immediately = false;
}
} else {
// The runner still hasn't been attached: queue this event and wait for it to be
process_immediately = false;
}
if !process_immediately {
// Queue these events to look at later
self.0.events.borrow_mut().extend(events);
return;
}
// At this point, we know this is a fresh set of events
// Now we determine why new events are incoming, and handle the events
let start_cause = if let Some(runner) = &*self.0.runner.borrow() {
match runner.state {
State::Init => StartCause::Init,
State::Poll { .. } => StartCause::Poll,
State::Wait { start } => StartCause::WaitCancelled {
start,
requested_resume: None,
},
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
start,
requested_resume: Some(end),
},
State::Exit => {
// If we're in the exit state, don't do event processing
return;
}
}
_ => {
// Events are currently being handled, so queue this one and don't try to
// double-process the event queue
self.0.events.borrow_mut().push_back(event);
return;
}
} else {
unreachable!("The runner cannot process events when it is not attached");
};
// Take the start event, then the events provided to this function, and run an iteration of
// the event loop
let start_event = Event::NewEvents(start_cause);
let events = iter::once(start_event).chain(events);
self.run_until_cleared(events);
}
// Given the set of new events, run the event loop until the main events and redraw events are
// cleared
//
// This will also process any events that have been queued or that are queued during processing
fn run_until_cleared(&self, events: impl Iterator<Item = Event<'static, T>>) {
let mut control = self.current_control_flow();
// Handle starting a new batch of events
//
// The user is informed via Event::NewEvents that there is a batch of events to process
// However, there is only one of these per batch of events
self.handle_event(Event::NewEvents(start_cause), &mut control);
if !event_is_start {
for event in events {
self.handle_event(event, &mut control);
}
self.handle_event(Event::MainEventsCleared, &mut control);
@@ -158,7 +202,6 @@ impl<T: 'static> Shared<T> {
// It should only ever be called from send_event
fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) {
let is_closed = self.is_closed();
match *self.0.runner.borrow_mut() {
Some(ref mut runner) => {
// An event is being processed, so the runner should be marked busy
@@ -183,7 +226,9 @@ impl<T: 'static> Shared<T> {
// If the runner doesn't exist and this method recurses, it will recurse infinitely
if !is_closed && self.0.runner.borrow().is_some() {
// Take an event out of the queue and handle it
if let Some(event) = self.0.events.borrow_mut().pop_front() {
// Make sure not to let the borrow_mut live during the next handle_event
let event = { self.0.events.borrow_mut().pop_front() };
if let Some(event) = event {
self.handle_event(event, control);
}
}
@@ -196,10 +241,7 @@ impl<T: 'static> Shared<T> {
root::ControlFlow::Poll => {
let cloned = self.clone();
State::Poll {
timeout: backend::Timeout::new(
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
Duration::from_millis(0),
),
request: backend::AnimationFrameRequest::new(move || cloned.poll()),
}
}
root::ControlFlow::Wait => State::Wait {
@@ -220,7 +262,7 @@ impl<T: 'static> Shared<T> {
start,
end,
timeout: backend::Timeout::new(
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
move || cloned.resume_time_reached(start, end),
delay,
),
}

View File

@@ -15,7 +15,7 @@ pub enum State {
start: Instant,
},
Poll {
timeout: backend::Timeout,
request: backend::AnimationFrameRequest,
},
Exit,
}

View File

@@ -2,7 +2,7 @@ use super::{backend, device, proxy::Proxy, runner, window};
use crate::dpi::{PhysicalSize, Size};
use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
use crate::window::{Theme, WindowId};
use std::clone::Clone;
pub struct WindowTarget<T: 'static> {
@@ -57,6 +57,7 @@ impl<T> WindowTarget<T> {
let runner = self.runner.clone();
canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| {
#[allow(deprecated)]
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::KeyboardInput {
@@ -74,6 +75,7 @@ impl<T> WindowTarget<T> {
let runner = self.runner.clone();
canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| {
#[allow(deprecated)]
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::KeyboardInput {
@@ -197,5 +199,18 @@ impl<T> WindowTarget<T> {
});
runner.request_redraw(WindowId(id));
});
let runner = self.runner.clone();
canvas.on_dark_mode(move |is_dark_mode| {
let theme = if is_dark_mode {
Theme::Dark
} else {
Theme::Light
};
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::ThemeChanged(theme),
});
});
}
}

View File

@@ -1,6 +1,21 @@
// TODO: close events (port from old stdweb branch)
// TODO: pointer locking (stdweb PR required)
// TODO: fullscreen API (stdweb PR required)
// Brief introduction to the internals of the web backend:
// Currently, the web backend supports both wasm-bindgen and stdweb as methods of binding to the
// environment. Because they are both supporting the same underlying APIs, the actual web bindings
// are cordoned off into backend abstractions, which present the thinnest unifying layer possible.
//
// When adding support for new events or interactions with the browser, first consult trusted
// documentation (such as MDN) to ensure it is well-standardised and supported across many browsers.
// Once you have decided on the relevant web APIs, add support to both backends.
//
// The backend is used by the rest of the module to implement Winit's business logic, which forms
// the rest of the code. 'device', 'error', 'monitor', and 'window' define web-specific structures
// for winit's cross-platform structures. They are all relatively simple translations.
//
// The event_loop module handles listening for and processing events. 'Proxy' implements
// EventLoopProxy and 'WindowTarget' implements EventLoopWindowTarget. WindowTarget also handles
// registering the event handlers. The 'Execution' struct in the 'runner' module handles taking
// incoming events (from the registered handlers) and ensuring they are passed to the user in a
// compliant way.
mod device;
mod error;
@@ -29,3 +44,5 @@ pub use self::window::{
Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes,
Window,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;

View File

@@ -2,10 +2,11 @@ use super::event;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
use std::cell::RefCell;
use std::rc::Rc;
use stdweb::js;
use stdweb::traits::IPointerEvent;
use stdweb::unstable::TryInto;
use stdweb::web::event::{
@@ -43,12 +44,15 @@ impl Drop for Canvas {
}
impl Canvas {
pub fn create() -> Result<Self, RootOE> {
let canvas: CanvasElement = document()
.create_element("canvas")
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
.try_into()
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
let canvas = match attr.canvas {
Some(canvas) => canvas,
None => document()
.create_element("canvas")
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
.try_into()
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?,
};
// A tabindex is needed in order to capture local keyboard events.
// A "0" value means that the element should be focusable in
@@ -207,7 +211,7 @@ impl Canvas {
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<i32>, ModifiersState),
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
{
// todo
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
@@ -237,6 +241,22 @@ impl Canvas {
self.on_fullscreen_change = Some(self.add_event(move |_: FullscreenChangeEvent| handler()));
}
pub fn on_dark_mode<F>(&mut self, handler: F)
where
F: 'static + FnMut(bool),
{
// TODO: upstream to stdweb
js! {
var handler = @{handler};
if (window.matchMedia) {
window.matchMedia("(prefers-color-scheme: dark)").addListener(function(e) {
handler(event.matches)
});
}
}
}
fn add_event<E, F>(&self, mut handler: F) -> EventListenerHandle
where
E: ConcreteEvent,

View File

@@ -3,13 +3,14 @@ mod event;
mod timeout;
pub use self::canvas::Canvas;
pub use self::timeout::Timeout;
pub use self::timeout::{AnimationFrameRequest, Timeout};
use crate::dpi::{LogicalSize, Size};
use crate::platform::web::WindowExtStdweb;
use crate::window::Window;
use stdweb::js;
use stdweb::unstable::TryInto;
use stdweb::web::event::BeforeUnloadEvent;
use stdweb::web::window;
use stdweb::web::IEventTarget;
@@ -31,6 +32,15 @@ impl WindowExtStdweb for Window {
fn canvas(&self) -> CanvasElement {
self.window.canvas().raw().clone()
}
fn is_dark_mode(&self) -> bool {
// TODO: upstream to stdweb
let is_dark_mode = js! {
return (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches)
};
is_dark_mode.try_into().expect("should return a bool")
}
}
pub fn window_size() -> LogicalSize<f64> {
@@ -72,3 +82,5 @@ pub fn is_fullscreen(canvas: &CanvasElement) -> bool {
None => false,
}
}
pub type RawCanvasType = CanvasElement;

View File

@@ -1,5 +1,7 @@
use std::cell::Cell;
use std::rc::Rc;
use std::time::Duration;
use stdweb::web::{window, IWindowOrWorker, TimeoutHandle};
use stdweb::web::{window, IWindowOrWorker, RequestAnimationFrameHandle, TimeoutHandle};
#[derive(Debug)]
pub struct Timeout {
@@ -23,3 +25,39 @@ impl Drop for Timeout {
handle.clear();
}
}
#[derive(Debug)]
pub struct AnimationFrameRequest {
handle: Option<RequestAnimationFrameHandle>,
// track callback state, because `cancelAnimationFrame` is slow
fired: Rc<Cell<bool>>,
}
impl AnimationFrameRequest {
pub fn new<F>(mut f: F) -> AnimationFrameRequest
where
F: 'static + FnMut(),
{
let fired = Rc::new(Cell::new(false));
let c_fired = fired.clone();
let handle = window().request_animation_frame(move |_| {
(*c_fired).set(true);
f();
});
AnimationFrameRequest {
handle: Some(handle),
fired,
}
}
}
impl Drop for AnimationFrameRequest {
fn drop(&mut self) {
if !(*self.fired).get() {
if let Some(handle) = self.handle.take() {
handle.cancel();
}
}
}
}

View File

@@ -2,13 +2,16 @@ use super::event;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent};
use web_sys::{
Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, PointerEvent,
WheelEvent,
};
pub struct Canvas {
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
@@ -26,6 +29,7 @@ pub struct Canvas {
on_mouse_wheel: Option<Closure<dyn FnMut(WheelEvent)>>,
on_fullscreen_change: Option<Closure<dyn FnMut(Event)>>,
wants_fullscreen: Rc<RefCell<bool>>,
on_dark_mode: Option<Closure<dyn FnMut(MediaQueryListEvent)>>,
}
impl Drop for Canvas {
@@ -35,18 +39,23 @@ impl Drop for Canvas {
}
impl Canvas {
pub fn create() -> Result<Self, RootOE> {
let window =
web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
let canvas = match attr.canvas {
Some(canvas) => canvas,
None => {
let window = web_sys::window()
.ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
let document = window
.document()
.ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?;
let document = window
.document()
.ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?;
let canvas: HtmlCanvasElement = document
.create_element("canvas")
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
.unchecked_into();
document
.create_element("canvas")
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
.unchecked_into()
}
};
// A tabindex is needed in order to capture local keyboard events.
// A "0" value means that the element should be focusable in
@@ -72,6 +81,7 @@ impl Canvas {
on_mouse_wheel: None,
on_fullscreen_change: None,
wants_fullscreen: Rc::new(RefCell::new(false)),
on_dark_mode: None,
})
}
@@ -216,7 +226,7 @@ impl Canvas {
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<i32>, ModifiersState),
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
{
self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| {
handler(
@@ -246,6 +256,28 @@ impl Canvas {
Some(self.add_event("fullscreenchange", move |_: Event| handler()));
}
pub fn on_dark_mode<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(bool),
{
let window = web_sys::window().expect("Failed to obtain window");
self.on_dark_mode = window
.match_media("(prefers-color-scheme: dark)")
.ok()
.flatten()
.and_then(|media| {
let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| {
handler(event.matches())
}) as Box<dyn FnMut(_)>);
media
.add_listener_with_opt_callback(Some(&closure.as_ref().unchecked_ref()))
.map(|_| closure)
.ok()
});
}
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
where
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,

View File

@@ -3,7 +3,7 @@ mod event;
mod timeout;
pub use self::canvas::Canvas;
pub use self::timeout::Timeout;
pub use self::timeout::{AnimationFrameRequest, Timeout};
use crate::dpi::{LogicalSize, Size};
use crate::platform::web::WindowExtWebSys;
@@ -38,6 +38,17 @@ impl WindowExtWebSys for Window {
fn canvas(&self) -> HtmlCanvasElement {
self.window.canvas().raw().clone()
}
fn is_dark_mode(&self) -> bool {
let window = web_sys::window().expect("Failed to obtain window");
window
.match_media("(prefers-color-scheme: dark)")
.ok()
.flatten()
.map(|media| media.matches())
.unwrap_or(false)
}
}
pub fn window_size() -> LogicalSize<f64> {
@@ -91,3 +102,5 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
None => false,
}
}
pub type RawCanvasType = HtmlCanvasElement;

View File

@@ -1,3 +1,5 @@
use std::cell::Cell;
use std::rc::Rc;
use std::time::Duration;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
@@ -38,3 +40,48 @@ impl Drop for Timeout {
window.clear_timeout_with_handle(self.handle);
}
}
#[derive(Debug)]
pub struct AnimationFrameRequest {
handle: i32,
// track callback state, because `cancelAnimationFrame` is slow
fired: Rc<Cell<bool>>,
_closure: Closure<dyn FnMut()>,
}
impl AnimationFrameRequest {
pub fn new<F>(mut f: F) -> AnimationFrameRequest
where
F: 'static + FnMut(),
{
let window = web_sys::window().expect("Failed to obtain window");
let fired = Rc::new(Cell::new(false));
let c_fired = fired.clone();
let closure = Closure::wrap(Box::new(move || {
(*c_fired).set(true);
f();
}) as Box<dyn FnMut()>);
let handle = window
.request_animation_frame(&closure.as_ref().unchecked_ref())
.expect("Failed to request animation frame");
AnimationFrameRequest {
handle,
fired,
_closure: closure,
}
}
}
impl Drop for AnimationFrameRequest {
fn drop(&mut self) {
if !(*self.fired).get() {
let window = web_sys::window().expect("Failed to obtain window");
window
.cancel_animation_frame(self.handle)
.expect("Failed to cancel animation frame");
}
}
}

View File

@@ -23,13 +23,13 @@ impl Window {
pub fn new<T>(
target: &EventLoopWindowTarget<T>,
attr: WindowAttributes,
_: PlatformSpecificBuilderAttributes,
platform_attr: PlatformSpecificBuilderAttributes,
) -> Result<Self, RootOE> {
let runner = target.runner.clone();
let id = target.generate_id();
let mut canvas = backend::Canvas::create()?;
let mut canvas = backend::Canvas::create(platform_attr)?;
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
@@ -281,5 +281,7 @@ impl Id {
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PlatformSpecificBuilderAttributes;
#[derive(Default, Clone)]
pub struct PlatformSpecificBuilderAttributes {
pub(crate) canvas: Option<backend::RawCanvasType>,
}

View File

@@ -6,9 +6,10 @@ use std::os::windows::ffi::OsStrExt;
use winapi::{
shared::{
basetsd::SIZE_T,
minwindef::{BOOL, DWORD, UINT, ULONG, WORD},
minwindef::{BOOL, DWORD, FALSE, UINT, ULONG, WORD},
ntdef::{LPSTR, NTSTATUS, NT_SUCCESS, PVOID, WCHAR},
windef::HWND,
winerror::S_OK,
},
um::{libloaderapi, uxtheme, winuser},
};
@@ -44,9 +45,8 @@ lazy_static! {
};
let status = (rtl_get_version)(&mut vi as _);
assert!(NT_SUCCESS(status));
if vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 {
if NT_SUCCESS(status) && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 {
Some(vi.dwBuildNumber)
} else {
None
@@ -82,22 +82,15 @@ pub fn try_dark_mode(hwnd: HWND) -> bool {
LIGHT_THEME_NAME.as_ptr()
};
unsafe {
assert_eq!(
0,
uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null())
);
let status = unsafe { uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null()) };
set_dark_mode_for_window(hwnd, is_dark_mode)
}
is_dark_mode
status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode)
} else {
false
}
}
fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) {
fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool {
// Uses Windows undocumented API SetWindowCompositionAttribute,
// as seen in win32-darkmode example linked at top of file.
@@ -132,11 +125,12 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) {
cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _,
};
assert_eq!(
1,
set_window_composition_attribute(hwnd, &mut data as *mut _)
);
let status = set_window_composition_attribute(hwnd, &mut data as *mut _);
status != FALSE
}
} else {
false
}
}
@@ -205,7 +199,7 @@ fn is_high_contrast() -> bool {
)
};
(ok > 0) && ((HCF_HIGHCONTRASTON & hc.dwFlags) == 1)
ok != FALSE && (HCF_HIGHCONTRASTON & hc.dwFlags) == 1
}
fn widestring(src: &'static str) -> Vec<u16> {

File diff suppressed because it is too large Load Diff

View File

@@ -1,420 +1,410 @@
use std::{any::Any, cell::RefCell, collections::VecDeque, mem, panic, ptr, rc::Rc, time::Instant};
use std::{
any::Any,
cell::{Cell, RefCell},
collections::{HashSet, VecDeque},
mem, panic, ptr,
rc::Rc,
time::Instant,
};
use winapi::{shared::windef::HWND, um::winuser};
use winapi::{
shared::{minwindef::DWORD, windef::HWND},
um::winuser,
};
use crate::{
event::{Event, StartCause},
dpi::PhysicalSize,
event::{Event, StartCause, WindowEvent},
event_loop::ControlFlow,
platform_impl::platform::event_loop::EventLoop,
platform_impl::platform::util,
window::WindowId,
};
pub(crate) type EventLoopRunnerShared<T> = Rc<ELRShared<T>>;
pub(crate) struct ELRShared<T: 'static> {
runner: RefCell<Option<EventLoopRunner<T>>>,
buffer: RefCell<VecDeque<Event<'static, T>>>,
redraw_buffer: Rc<RefCell<VecDeque<WindowId>>>,
}
struct EventLoopRunner<T: 'static> {
control_flow: ControlFlow,
runner_state: RunnerState,
modal_redraw_window: HWND,
in_modal_loop: bool,
event_handler: Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>,
panic_error: Option<PanicError>,
redraw_buffer: Rc<RefCell<VecDeque<WindowId>>>,
pub(crate) type EventLoopRunnerShared<T> = Rc<EventLoopRunner<T>>;
pub(crate) struct EventLoopRunner<T: 'static> {
// The event loop's win32 handles
thread_msg_target: HWND,
wait_thread_id: DWORD,
control_flow: Cell<ControlFlow>,
runner_state: Cell<RunnerState>,
last_events_cleared: Cell<Instant>,
event_handler: Cell<Option<Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>>>,
event_buffer: RefCell<VecDeque<BufferedEvent<T>>>,
owned_windows: Cell<HashSet<HWND>>,
panic_error: Cell<Option<PanicError>>,
}
pub type PanicError = Box<dyn Any + Send + 'static>;
impl<T> ELRShared<T> {
pub(crate) fn new() -> ELRShared<T> {
ELRShared {
runner: RefCell::new(None),
buffer: RefCell::new(VecDeque::new()),
redraw_buffer: Default::default(),
}
}
pub(crate) unsafe fn set_runner<F>(&self, event_loop: &EventLoop<T>, f: F)
where
F: FnMut(Event<'_, T>, &mut ControlFlow),
{
let mut runner = EventLoopRunner::new(event_loop, self.redraw_buffer.clone(), f);
{
let mut runner_ref = self.runner.borrow_mut();
loop {
let event = self.buffer.borrow_mut().pop_front();
match event {
Some(e) => {
runner.process_event(e);
}
None => break,
}
}
*runner_ref = Some(runner);
}
}
pub(crate) fn destroy_runner(&self) {
*self.runner.borrow_mut() = None;
}
pub(crate) fn new_events(&self) {
let mut runner_ref = self.runner.borrow_mut();
if let Some(ref mut runner) = *runner_ref {
runner.new_events();
}
}
pub(crate) unsafe fn send_event(&self, event: Event<'static, T>) {
if let Err(event) = self.send_event_unbuffered(event) {
// If the runner is already borrowed, we're in the middle of an event loop invocation. Add
// the event to a buffer to be processed later.
self.buffer_event(event);
}
}
pub(crate) unsafe fn send_event_unbuffered<'e>(
&self,
event: Event<'e, T>,
) -> Result<(), Event<'e, T>> {
if let Ok(mut runner_ref) = self.runner.try_borrow_mut() {
if let Some(ref mut runner) = *runner_ref {
runner.process_event(event);
// Dispatch any events that were buffered during the call to `process_event`.
loop {
// We do this instead of using a `while let` loop because if we use a `while let`
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
// of the loop's body and attempts to add events to the event buffer while in
// `process_event` will fail.
let buffered_event_opt = self.buffer.borrow_mut().pop_front();
match buffered_event_opt {
Some(event) => runner.process_event(event),
None => break,
}
}
return Ok(());
}
}
Err(event)
}
pub(crate) unsafe fn call_event_handler(&self, event: Event<'static, T>) {
if let Ok(mut runner_ref) = self.runner.try_borrow_mut() {
if let Some(ref mut runner) = *runner_ref {
runner.call_event_handler(event);
return;
}
}
}
pub(crate) fn events_cleared(&self) {
let mut runner_ref = self.runner.borrow_mut();
if let Some(ref mut runner) = *runner_ref {
runner.events_cleared();
}
}
pub(crate) fn take_panic_error(&self) -> Result<(), PanicError> {
let mut runner_ref = self.runner.borrow_mut();
if let Some(ref mut runner) = *runner_ref {
runner.take_panic_error()
} else {
Ok(())
}
}
pub(crate) fn set_modal_loop(&self, in_modal_loop: bool) {
let mut runner_ref = self.runner.borrow_mut();
if let Some(ref mut runner) = *runner_ref {
runner.in_modal_loop = in_modal_loop;
}
}
pub(crate) fn in_modal_loop(&self) -> bool {
let runner = self.runner.borrow();
if let Some(ref runner) = *runner {
runner.in_modal_loop
} else {
false
}
}
pub fn control_flow(&self) -> ControlFlow {
let runner_ref = self.runner.borrow();
if let Some(ref runner) = *runner_ref {
runner.control_flow
} else {
ControlFlow::Exit
}
}
fn buffer_event(&self, event: Event<'static, T>) {
match event {
Event::RedrawRequested(window_id) => {
self.redraw_buffer.borrow_mut().push_back(window_id)
}
_ => self.buffer.borrow_mut().push_back(event),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// See `move_state_to` function for details on how the state loop works.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum RunnerState {
/// The event loop has just been created, and an `Init` event must be sent.
New,
/// The event loop is idling, and began idling at the given instant.
Idle(Instant),
/// The event loop has received a signal from the OS that the loop may resume, but no winit
/// events have been generated yet. We're waiting for an event to be processed or the events
/// to be marked as cleared to send `NewEvents`, depending on the current `ControlFlow`.
DeferredNewEvents(Instant),
Uninitialized,
/// The event loop is idling.
Idle,
/// The event loop is handling the OS's events and sending them to the user's callback.
/// `NewEvents` has been sent, and `EventsCleared` hasn't.
HandlingEvents,
HandlingRedraw,
/// `NewEvents` has been sent, and `MainEventsCleared` hasn't.
HandlingMainEvents,
/// The event loop is handling the redraw events and sending them to the user's callback.
/// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't.
HandlingRedrawEvents,
}
enum BufferedEvent<T: 'static> {
Event(Event<'static, T>),
ScaleFactorChanged(WindowId, f64, PhysicalSize<u32>),
}
impl<T> EventLoopRunner<T> {
unsafe fn new<F>(
event_loop: &EventLoop<T>,
redraw_buffer: Rc<RefCell<VecDeque<WindowId>>>,
f: F,
) -> EventLoopRunner<T>
where
F: FnMut(Event<'_, T>, &mut ControlFlow),
{
pub(crate) fn new(thread_msg_target: HWND, wait_thread_id: DWORD) -> EventLoopRunner<T> {
EventLoopRunner {
control_flow: ControlFlow::default(),
runner_state: RunnerState::New,
in_modal_loop: false,
modal_redraw_window: event_loop.window_target.p.thread_msg_target,
event_handler: mem::transmute::<
Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>,
Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>,
>(Box::new(f)),
panic_error: None,
redraw_buffer,
thread_msg_target,
wait_thread_id,
runner_state: Cell::new(RunnerState::Uninitialized),
control_flow: Cell::new(ControlFlow::Poll),
panic_error: Cell::new(None),
last_events_cleared: Cell::new(Instant::now()),
event_handler: Cell::new(None),
event_buffer: RefCell::new(VecDeque::new()),
owned_windows: Cell::new(HashSet::new()),
}
}
fn take_panic_error(&mut self) -> Result<(), PanicError> {
pub(crate) unsafe fn set_event_handler<F>(&self, f: F)
where
F: FnMut(Event<'_, T>, &mut ControlFlow),
{
let old_event_handler = self.event_handler.replace(mem::transmute::<
Option<Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>>,
Option<Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>>,
>(Some(Box::new(f))));
assert!(old_event_handler.is_none());
}
pub(crate) fn reset_runner(&self) {
let EventLoopRunner {
thread_msg_target: _,
wait_thread_id: _,
runner_state,
panic_error,
control_flow,
last_events_cleared: _,
event_handler,
event_buffer: _,
owned_windows: _,
} = self;
runner_state.set(RunnerState::Uninitialized);
panic_error.set(None);
control_flow.set(ControlFlow::Poll);
event_handler.set(None);
}
}
/// State retrieval functions.
impl<T> EventLoopRunner<T> {
pub fn thread_msg_target(&self) -> HWND {
self.thread_msg_target
}
pub fn wait_thread_id(&self) -> DWORD {
self.wait_thread_id
}
pub fn redrawing(&self) -> bool {
self.runner_state.get() == RunnerState::HandlingRedrawEvents
}
pub fn take_panic_error(&self) -> Result<(), PanicError> {
match self.panic_error.take() {
Some(err) => Err(err),
None => Ok(()),
}
}
fn new_events(&mut self) {
self.runner_state = match self.runner_state {
// If we're already handling events or have deferred `NewEvents`, we don't need to do
// do any processing.
RunnerState::HandlingEvents
| RunnerState::HandlingRedraw
| RunnerState::DeferredNewEvents(..) => self.runner_state,
// Send the `Init` `NewEvents` and immediately move into event processing.
RunnerState::New => {
self.call_event_handler(Event::NewEvents(StartCause::Init));
RunnerState::HandlingEvents
}
// When `NewEvents` gets sent after an idle depends on the control flow...
RunnerState::Idle(wait_start) => {
match self.control_flow {
// If we're polling, send `NewEvents` and immediately move into event processing.
ControlFlow::Poll => {
self.call_event_handler(Event::NewEvents(StartCause::Poll));
RunnerState::HandlingEvents
},
// If the user was waiting until a specific time, the `NewEvents` call gets sent
// at varying times depending on the current time.
ControlFlow::WaitUntil(resume_time) => {
match Instant::now() >= resume_time {
// If the current time is later than the requested resume time, we can tell the
// user that the resume time has been reached with `NewEvents` and immdiately move
// into event processing.
true => {
self.call_event_handler(Event::NewEvents(StartCause::ResumeTimeReached {
start: wait_start,
requested_resume: resume_time,
}));
RunnerState::HandlingEvents
},
// However, if the current time is EARLIER than the requested resume time, we
// don't want to send the `WaitCancelled` event until we know an event is being
// sent. Defer.
false => RunnerState::DeferredNewEvents(wait_start)
}
},
// If we're waiting, `NewEvents` doesn't get sent until winit gets an event, so
// we defer.
ControlFlow::Wait |
// `Exit` shouldn't really ever get sent here, but if it does do something somewhat sane.
ControlFlow::Exit => RunnerState::DeferredNewEvents(wait_start),
}
}
};
pub fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
fn process_event(&mut self, event: Event<'_, T>) {
// If we're in the modal loop, we need to have some mechanism for finding when the event
// queue has been cleared so we can call `events_cleared`. Windows doesn't give any utilities
// for doing this, but it DOES guarantee that WM_PAINT will only occur after input events have
// been processed. So, we send WM_PAINT to a dummy window which calls `events_cleared` when
// the events queue has been emptied.
if self.in_modal_loop {
unsafe {
winuser::RedrawWindow(
self.modal_redraw_window,
ptr::null(),
ptr::null_mut(),
winuser::RDW_INTERNALPAINT,
);
}
}
pub fn handling_events(&self) -> bool {
self.runner_state.get() != RunnerState::Idle
}
// If new event processing has to be done (i.e. call NewEvents or defer), do it. If we're
// already in processing nothing happens with this call.
self.new_events();
pub fn should_buffer(&self) -> bool {
let handler = self.event_handler.take();
let should_buffer = handler.is_none();
self.event_handler.set(handler);
should_buffer
}
}
// Now that an event has been received, we have to send any `NewEvents` calls that were
// deferred.
if let RunnerState::DeferredNewEvents(wait_start) = self.runner_state {
match self.control_flow {
ControlFlow::Exit | ControlFlow::Wait => {
self.call_event_handler(Event::NewEvents(StartCause::WaitCancelled {
start: wait_start,
requested_resume: None,
}))
/// Misc. functions
impl<T> EventLoopRunner<T> {
pub fn catch_unwind<R>(&self, f: impl FnOnce() -> R) -> Option<R> {
let panic_error = self.panic_error.take();
if panic_error.is_none() {
let result = panic::catch_unwind(panic::AssertUnwindSafe(f));
// Check to see if the panic error was set in a re-entrant call to catch_unwind inside
// of `f`. If it was, that error takes priority. If it wasn't, check if our call to
// catch_unwind caught any panics and set panic_error appropriately.
match self.panic_error.take() {
None => match result {
Ok(r) => Some(r),
Err(e) => {
self.panic_error.set(Some(e));
None
}
},
Some(e) => {
self.panic_error.set(Some(e));
None
}
ControlFlow::WaitUntil(resume_time) => {
let start_cause = match Instant::now() >= resume_time {
// If the current time is later than the requested resume time, the resume time
// has been reached.
true => StartCause::ResumeTimeReached {
start: wait_start,
requested_resume: resume_time,
},
// Otherwise, the requested resume time HASN'T been reached and we send a WaitCancelled.
false => StartCause::WaitCancelled {
start: wait_start,
requested_resume: Some(resume_time),
},
};
self.call_event_handler(Event::NewEvents(start_cause));
}
// This can be reached if the control flow is changed to poll during a `RedrawRequested`
// that was sent after `EventsCleared`.
ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)),
}
} else {
self.panic_error.set(panic_error);
None
}
}
pub fn register_window(&self, window: HWND) {
let mut owned_windows = self.owned_windows.take();
owned_windows.insert(window);
self.owned_windows.set(owned_windows);
}
match (self.runner_state, &event) {
(RunnerState::HandlingRedraw, Event::RedrawRequested(_)) => {
self.call_event_handler(event)
pub fn remove_window(&self, window: HWND) {
let mut owned_windows = self.owned_windows.take();
owned_windows.remove(&window);
self.owned_windows.set(owned_windows);
}
pub fn owned_windows(&self, mut f: impl FnMut(HWND)) {
let mut owned_windows = self.owned_windows.take();
for hwnd in &owned_windows {
f(*hwnd);
}
let new_owned_windows = self.owned_windows.take();
owned_windows.extend(&new_owned_windows);
self.owned_windows.set(owned_windows);
}
}
/// Event dispatch functions.
impl<T> EventLoopRunner<T> {
pub(crate) unsafe fn poll(&self) {
self.move_state_to(RunnerState::HandlingMainEvents);
}
pub(crate) unsafe fn send_event(&self, event: Event<'_, T>) {
if let Event::RedrawRequested(_) = event {
if self.runner_state.get() != RunnerState::HandlingRedrawEvents {
warn!("RedrawRequested dispatched without explicit MainEventsCleared");
self.move_state_to(RunnerState::HandlingRedrawEvents);
}
(_, Event::RedrawRequested(_)) => {
self.call_event_handler(Event::MainEventsCleared);
self.runner_state = RunnerState::HandlingRedraw;
self.call_event_handler(event);
}
(RunnerState::HandlingRedraw, _) => {
warn!("Non-redraw event dispatched durning redraw phase");
self.events_cleared();
self.new_events();
self.call_event_handler(event);
}
(_, _) => {
self.runner_state = RunnerState::HandlingEvents;
self.call_event_handler(event);
} else {
if self.should_buffer() {
// If the runner is already borrowed, we're in the middle of an event loop invocation. Add
// the event to a buffer to be processed later.
self.event_buffer
.borrow_mut()
.push_back(BufferedEvent::from_event(event))
} else {
self.move_state_to(RunnerState::HandlingMainEvents);
self.call_event_handler(event);
self.dispatch_buffered_events();
}
}
}
fn flush_redraws(&mut self) {
pub(crate) unsafe fn main_events_cleared(&self) {
self.move_state_to(RunnerState::HandlingRedrawEvents);
}
pub(crate) unsafe fn redraw_events_cleared(&self) {
self.move_state_to(RunnerState::Idle);
}
pub(crate) unsafe fn call_event_handler(&self, event: Event<'_, T>) {
self.catch_unwind(|| {
let mut control_flow = self.control_flow.take();
let mut event_handler = self.event_handler.take()
.expect("either event handler is re-entrant (likely), or no event handler is registered (very unlikely)");
if control_flow != ControlFlow::Exit {
event_handler(event, &mut control_flow);
} else {
event_handler(event, &mut ControlFlow::Exit);
}
assert!(self.event_handler.replace(Some(event_handler)).is_none());
self.control_flow.set(control_flow);
});
}
unsafe fn dispatch_buffered_events(&self) {
loop {
let redraw_window_opt = self.redraw_buffer.borrow_mut().pop_front();
match redraw_window_opt {
Some(window_id) => self.process_event(Event::RedrawRequested(window_id)),
// We do this instead of using a `while let` loop because if we use a `while let`
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
// of the loop's body and attempts to add events to the event buffer while in
// `process_event` will fail.
let buffered_event_opt = self.event_buffer.borrow_mut().pop_front();
match buffered_event_opt {
Some(e) => e.dispatch_event(|e| self.call_event_handler(e)),
None => break,
}
}
}
fn events_cleared(&mut self) {
match self.runner_state {
// If we were handling events, send the EventsCleared message.
RunnerState::HandlingEvents => {
/// Dispatch control flow events (`NewEvents`, `MainEventsCleared`, and `RedrawEventsCleared`) as
/// necessary to bring the internal `RunnerState` to the new runner state.
///
/// The state transitions are defined as follows:
///
/// ```text
/// Uninitialized
/// |
/// V
/// HandlingMainEvents
/// ^ |
/// | V
/// Idle <--- HandlingRedrawEvents
/// ```
///
/// Attempting to transition back to `Uninitialized` will result in a panic. Transitioning to
/// the current state is a no-op. Even if the `new_runner_state` isn't the immediate next state
/// in the runner state machine (e.g. `self.runner_state == HandlingMainEvents` and
/// `new_runner_state == Idle`), the intermediate state transitions will still be executed.
unsafe fn move_state_to(&self, new_runner_state: RunnerState) {
use RunnerState::{HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized};
match (
self.runner_state.replace(new_runner_state),
new_runner_state,
) {
(Uninitialized, Uninitialized)
| (Idle, Idle)
| (HandlingMainEvents, HandlingMainEvents)
| (HandlingRedrawEvents, HandlingRedrawEvents) => (),
// State transitions that initialize the event loop.
(Uninitialized, HandlingMainEvents) => {
self.call_new_events(true);
}
(Uninitialized, HandlingRedrawEvents) => {
self.call_new_events(true);
self.call_event_handler(Event::MainEventsCleared);
}
(Uninitialized, Idle) => {
self.call_new_events(true);
self.call_event_handler(Event::MainEventsCleared);
self.call_redraw_events_cleared();
}
(_, Uninitialized) => panic!("cannot move state to Uninitialized"),
// State transitions that start the event handling process.
(Idle, HandlingMainEvents) => {
self.call_new_events(false);
}
(Idle, HandlingRedrawEvents) => {
self.call_new_events(false);
self.call_event_handler(Event::MainEventsCleared);
self.flush_redraws();
self.call_event_handler(Event::RedrawEventsCleared);
self.runner_state = RunnerState::Idle(Instant::now());
}
RunnerState::HandlingRedraw => {
self.call_event_handler(Event::RedrawEventsCleared);
self.runner_state = RunnerState::Idle(Instant::now());
(HandlingMainEvents, HandlingRedrawEvents) => {
self.call_event_handler(Event::MainEventsCleared);
}
(HandlingMainEvents, Idle) => {
warn!("RedrawEventsCleared emitted without explicit MainEventsCleared");
self.call_event_handler(Event::MainEventsCleared);
self.call_redraw_events_cleared();
}
// If we *weren't* handling events, we don't have to do anything.
RunnerState::New | RunnerState::Idle(..) => (),
// Some control flows require a NewEvents call even if no events were received. This
// branch handles those.
RunnerState::DeferredNewEvents(wait_start) => {
match self.control_flow {
// If we had deferred a Poll, send the Poll NewEvents and EventsCleared.
ControlFlow::Poll => {
self.call_event_handler(Event::NewEvents(StartCause::Poll));
self.call_event_handler(Event::MainEventsCleared);
self.flush_redraws();
self.call_event_handler(Event::RedrawEventsCleared);
}
// If we had deferred a WaitUntil and the resume time has since been reached,
// send the resume notification and EventsCleared event.
ControlFlow::WaitUntil(resume_time) => {
if Instant::now() >= resume_time {
self.call_event_handler(Event::NewEvents(
StartCause::ResumeTimeReached {
start: wait_start,
requested_resume: resume_time,
},
));
self.call_event_handler(Event::MainEventsCleared);
self.flush_redraws();
self.call_event_handler(Event::RedrawEventsCleared);
}
}
// If we deferred a wait and no events were received, the user doesn't have to
// get an event.
ControlFlow::Wait | ControlFlow::Exit => (),
}
// Mark that we've entered an idle state.
self.runner_state = RunnerState::Idle(wait_start)
(HandlingRedrawEvents, Idle) => {
self.call_redraw_events_cleared();
}
(HandlingRedrawEvents, HandlingMainEvents) => {
warn!("NewEvents emitted without explicit RedrawEventsCleared");
self.call_redraw_events_cleared();
self.call_new_events(false);
}
}
}
fn call_event_handler(&mut self, event: Event<'_, T>) {
if self.panic_error.is_none() {
let EventLoopRunner {
ref mut panic_error,
ref mut event_handler,
ref mut control_flow,
..
} = self;
*panic_error = panic::catch_unwind(panic::AssertUnwindSafe(|| {
if *control_flow != ControlFlow::Exit {
(*event_handler)(event, control_flow);
unsafe fn call_new_events(&self, init: bool) {
let start_cause = match (init, self.control_flow()) {
(true, _) => StartCause::Init,
(false, ControlFlow::Poll) => StartCause::Poll,
(false, ControlFlow::Exit) | (false, ControlFlow::Wait) => StartCause::WaitCancelled {
requested_resume: None,
start: self.last_events_cleared.get(),
},
(false, ControlFlow::WaitUntil(requested_resume)) => {
if Instant::now() < requested_resume {
StartCause::WaitCancelled {
requested_resume: Some(requested_resume),
start: self.last_events_cleared.get(),
}
} else {
(*event_handler)(event, &mut ControlFlow::Exit);
StartCause::ResumeTimeReached {
requested_resume,
start: self.last_events_cleared.get(),
}
}
}))
.err();
}
};
self.call_event_handler(Event::NewEvents(start_cause));
self.dispatch_buffered_events();
winuser::RedrawWindow(
self.thread_msg_target,
ptr::null(),
ptr::null_mut(),
winuser::RDW_INTERNALPAINT,
);
}
unsafe fn call_redraw_events_cleared(&self) {
self.call_event_handler(Event::RedrawEventsCleared);
self.last_events_cleared.set(Instant::now());
}
}
impl<T> BufferedEvent<T> {
pub fn from_event(event: Event<'_, T>) -> BufferedEvent<T> {
match event {
Event::WindowEvent {
event:
WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
},
window_id,
} => BufferedEvent::ScaleFactorChanged(window_id, scale_factor, *new_inner_size),
event => BufferedEvent::Event(event.to_static().unwrap()),
}
}
pub fn dispatch_event(self, dispatch: impl FnOnce(Event<'_, T>)) {
match self {
Self::Event(event) => dispatch(event),
Self::ScaleFactorChanged(window_id, scale_factor, mut new_inner_size) => {
dispatch(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size: &mut new_inner_size,
},
});
util::set_inner_size_physical(
(window_id.0).0,
new_inner_size.width as _,
new_inner_size.height as _,
);
}
}
}
}

View File

@@ -1,15 +1,17 @@
use std::{io, mem, os::windows::ffi::OsStrExt, path::Path, ptr};
use std::{fmt, io, iter::once, mem, os::windows::ffi::OsStrExt, path::Path, ptr, sync::Arc};
use winapi::{
ctypes::{c_int, wchar_t},
shared::{
minwindef::{BYTE, LPARAM, WPARAM},
minwindef::{BYTE, LPARAM, WORD, WPARAM},
windef::{HICON, HWND},
},
um::libloaderapi,
um::winuser,
};
use crate::icon::{Icon, Pixel, PIXEL_SIZE};
use crate::dpi::PhysicalSize;
use crate::icon::*;
impl Pixel {
fn to_bgra(&mut self) {
@@ -17,92 +19,149 @@ impl Pixel {
}
}
impl RgbaIcon {
fn into_windows_icon(self) -> Result<WinIcon, BadIcon> {
let mut rgba = self.rgba;
let pixel_count = rgba.len() / PIXEL_SIZE;
let mut and_mask = Vec::with_capacity(pixel_count);
let pixels =
unsafe { std::slice::from_raw_parts_mut(rgba.as_mut_ptr() as *mut Pixel, pixel_count) };
for pixel in pixels {
and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel
pixel.to_bgra();
}
assert_eq!(and_mask.len(), pixel_count);
let handle = unsafe {
winuser::CreateIcon(
ptr::null_mut(),
self.width as c_int,
self.height as c_int,
1,
(PIXEL_SIZE * 8) as BYTE,
and_mask.as_ptr() as *const BYTE,
rgba.as_ptr() as *const BYTE,
) as HICON
};
if !handle.is_null() {
Ok(WinIcon::from_handle(handle))
} else {
Err(BadIcon::OsError(io::Error::last_os_error()))
}
}
}
#[derive(Debug)]
pub enum IconType {
Small = winuser::ICON_SMALL as isize,
Big = winuser::ICON_BIG as isize,
}
#[derive(Clone, Debug)]
#[derive(Debug)]
struct RaiiIcon {
handle: HICON,
}
#[derive(Clone)]
pub struct WinIcon {
pub handle: HICON,
inner: Arc<RaiiIcon>,
}
unsafe impl Send for WinIcon {}
impl WinIcon {
#[allow(dead_code)]
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
let wide_path: Vec<u16> = path.as_ref().as_os_str().encode_wide().collect();
pub fn as_raw_handle(&self) -> HICON {
self.inner.handle
}
pub fn from_path<P: AsRef<Path>>(
path: P,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
let wide_path: Vec<u16> = path
.as_ref()
.as_os_str()
.encode_wide()
.chain(once(0))
.collect();
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
let (width, height) = size.map(Into::into).unwrap_or((0, 0));
let handle = unsafe {
winuser::LoadImageW(
ptr::null_mut(),
wide_path.as_ptr() as *const wchar_t,
winuser::IMAGE_ICON,
0, // 0 indicates that we want to use the actual width
0, // and height
winuser::LR_LOADFROMFILE,
) as HICON
};
if !handle.is_null() {
Ok(WinIcon { handle })
} else {
Err(io::Error::last_os_error())
}
}
pub fn from_icon(icon: Icon) -> Result<Self, io::Error> {
Self::from_rgba(icon.rgba, icon.width, icon.height)
}
pub fn from_rgba(mut rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, io::Error> {
assert_eq!(rgba.len() % PIXEL_SIZE, 0);
let pixel_count = rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (width * height) as usize);
let mut and_mask = Vec::with_capacity(pixel_count);
let pixels = rgba.as_mut_ptr() as *mut Pixel; // how not to write idiomatic Rust
for pixel_index in 0..pixel_count {
let pixel = unsafe { &mut *pixels.offset(pixel_index as isize) };
and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel
pixel.to_bgra();
}
assert_eq!(and_mask.len(), pixel_count);
let handle = unsafe {
winuser::CreateIcon(
ptr::null_mut(),
width as c_int,
height as c_int,
1,
(PIXEL_SIZE * 8) as BYTE,
and_mask.as_ptr() as *const BYTE,
rgba.as_ptr() as *const BYTE,
winuser::LR_DEFAULTSIZE | winuser::LR_LOADFROMFILE,
) as HICON
};
if !handle.is_null() {
Ok(WinIcon { handle })
Ok(WinIcon::from_handle(handle))
} else {
Err(io::Error::last_os_error())
Err(BadIcon::OsError(io::Error::last_os_error()))
}
}
pub fn from_resource(
resource_id: WORD,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
let (width, height) = size.map(Into::into).unwrap_or((0, 0));
let handle = unsafe {
winuser::LoadImageW(
libloaderapi::GetModuleHandleW(ptr::null_mut()),
winuser::MAKEINTRESOURCEW(resource_id),
winuser::IMAGE_ICON,
width as c_int,
height as c_int,
winuser::LR_DEFAULTSIZE,
) as HICON
};
if !handle.is_null() {
Ok(WinIcon::from_handle(handle))
} else {
Err(BadIcon::OsError(io::Error::last_os_error()))
}
}
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
let rgba_icon = RgbaIcon::from_rgba(rgba, width, height)?;
rgba_icon.into_windows_icon()
}
pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) {
unsafe {
winuser::SendMessageW(
hwnd,
winuser::WM_SETICON,
icon_type as WPARAM,
self.handle as LPARAM,
self.as_raw_handle() as LPARAM,
);
}
}
fn from_handle(handle: HICON) -> Self {
Self {
inner: Arc::new(RaiiIcon { handle }),
}
}
}
impl Drop for WinIcon {
impl Drop for RaiiIcon {
fn drop(&mut self) {
unsafe { winuser::DestroyIcon(self.handle) };
}
}
impl fmt::Debug for WinIcon {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
(*self.inner).fmt(formatter)
}
}
pub fn unset_for_window(hwnd: HWND, icon_type: IconType) {
unsafe {
winuser::SendMessageW(hwnd, winuser::WM_SETICON, icon_type as WPARAM, 0 as LPARAM);

View File

@@ -4,11 +4,15 @@ use winapi::{self, shared::windef::HWND};
pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
icon::WinIcon,
monitor::{MonitorHandle, VideoMode},
window::Window,
};
use crate::{event::DeviceId as RootDeviceId, window::Icon};
pub use self::icon::WinIcon as PlatformIcon;
use crate::event::DeviceId as RootDeviceId;
use crate::icon::Icon;
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes {

View File

@@ -88,6 +88,38 @@ pub fn adjust_size(hwnd: HWND, size: PhysicalSize<u32>) -> PhysicalSize<u32> {
PhysicalSize::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _)
}
pub(crate) fn set_inner_size_physical(window: HWND, x: u32, y: u32) {
unsafe {
let rect = adjust_window_rect(
window,
RECT {
top: 0,
left: 0,
bottom: y as LONG,
right: x as LONG,
},
)
.expect("adjust_window_rect failed");
let outer_x = (rect.right - rect.left).abs() as _;
let outer_y = (rect.top - rect.bottom).abs() as _;
winuser::SetWindowPos(
window,
ptr::null_mut(),
0,
0,
outer_x,
outer_y,
winuser::SWP_ASYNCWINDOWPOS
| winuser::SWP_NOZORDER
| winuser::SWP_NOREPOSITION
| winuser::SWP_NOMOVE
| winuser::SWP_NOACTIVATE,
);
winuser::InvalidateRgn(window, ptr::null_mut(), 0);
}
}
pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option<RECT> {
unsafe {
let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE);

View File

@@ -24,7 +24,7 @@ use winapi::{
oleidl::LPDROPTARGET,
shobjidl_core::{CLSID_TaskbarList, ITaskbarList2},
wingdi::{CreateRectRgn, DeleteObject},
winnt::{LONG, LPCWSTR},
winnt::LPCWSTR,
winuser,
},
};
@@ -32,18 +32,19 @@ use winapi::{
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
monitor::MonitorHandle as RootMonitorHandle,
platform_impl::platform::{
dark_mode::try_dark_mode,
dpi::{dpi_to_scale_factor, hwnd_dpi},
drop_handler::FileDropHandler,
event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID},
icon::{self, IconType, WinIcon},
icon::{self, IconType},
monitor, util,
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
PlatformSpecificWindowBuilderAttributes, WindowId,
},
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
window::{CursorIcon, Fullscreen, WindowAttributes},
};
/// The Win32 implementation of the main `Window` object.
@@ -187,7 +188,7 @@ impl Window {
| winuser::SWP_NOSIZE
| winuser::SWP_NOACTIVATE,
);
winuser::UpdateWindow(self.window.0);
winuser::InvalidateRgn(self.window.0, ptr::null_mut(), 0);
}
}
@@ -215,42 +216,10 @@ impl Window {
.unwrap()
}
pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) {
unsafe {
let rect = util::adjust_window_rect(
self.window.0,
RECT {
top: 0,
left: 0,
bottom: y as LONG,
right: x as LONG,
},
)
.expect("adjust_window_rect failed");
let outer_x = (rect.right - rect.left).abs() as c_int;
let outer_y = (rect.top - rect.bottom).abs() as c_int;
winuser::SetWindowPos(
self.window.0,
ptr::null_mut(),
0,
0,
outer_x,
outer_y,
winuser::SWP_ASYNCWINDOWPOS
| winuser::SWP_NOZORDER
| winuser::SWP_NOREPOSITION
| winuser::SWP_NOMOVE
| winuser::SWP_NOACTIVATE,
);
winuser::UpdateWindow(self.window.0);
}
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
let dpi_factor = self.scale_factor();
let (width, height) = size.to_physical::<u32>(dpi_factor).into();
let scale_factor = self.scale_factor();
let (width, height) = size.to_physical::<u32>(scale_factor).into();
let window_state = Arc::clone(&self.window_state);
let window = self.window.clone();
@@ -260,7 +229,7 @@ impl Window {
});
});
self.set_inner_size_physical(width, height);
util::set_inner_size_physical(self.window.0, width, height);
}
#[inline]
@@ -357,13 +326,13 @@ impl Window {
#[inline]
pub fn scale_factor(&self) -> f64 {
self.window_state.lock().dpi_factor
self.window_state.lock().scale_factor
}
#[inline]
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
let dpi_factor = self.scale_factor();
let (x, y) = position.to_physical::<i32>(dpi_factor).into();
let scale_factor = self.scale_factor();
let (x, y) = position.to_physical::<i32>(scale_factor).into();
let mut point = POINT { x, y };
unsafe {
@@ -434,7 +403,7 @@ impl Window {
let client_rect = util::get_client_rect(window.0).unwrap();
window_state_lock.saved_window = Some(SavedWindow {
client_rect,
dpi_factor: window_state_lock.dpi_factor,
scale_factor: window_state_lock.scale_factor,
});
}
_ => (),
@@ -538,17 +507,17 @@ impl Window {
size.1 as i32,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
);
winuser::UpdateWindow(window.0);
winuser::InvalidateRgn(window.0, ptr::null_mut(), 0);
}
}
None => {
let mut window_state_lock = window_state.lock();
if let Some(SavedWindow {
client_rect,
dpi_factor,
scale_factor,
}) = window_state_lock.saved_window.take()
{
window_state_lock.dpi_factor = dpi_factor;
window_state_lock.scale_factor = scale_factor;
drop(window_state_lock);
let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap();
@@ -564,7 +533,7 @@ impl Window {
| winuser::SWP_NOZORDER
| winuser::SWP_NOACTIVATE,
);
winuser::UpdateWindow(window.0);
winuser::InvalidateRgn(window.0, ptr::null_mut(), 0);
}
}
}
@@ -608,12 +577,11 @@ impl Window {
}
#[inline]
pub fn set_window_icon(&self, mut window_icon: Option<Icon>) {
let window_icon = window_icon
.take()
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_SMALL`"));
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
if let Some(ref window_icon) = window_icon {
window_icon.set_for_window(self.window.0, IconType::Small);
window_icon
.inner
.set_for_window(self.window.0, IconType::Small);
} else {
icon::unset_for_window(self.window.0, IconType::Small);
}
@@ -621,12 +589,11 @@ impl Window {
}
#[inline]
pub fn set_taskbar_icon(&self, mut taskbar_icon: Option<Icon>) {
let taskbar_icon = taskbar_icon
.take()
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_BIG`"));
pub fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
if let Some(ref taskbar_icon) = taskbar_icon {
taskbar_icon.set_for_window(self.window.0, IconType::Big);
taskbar_icon
.inner
.set_for_window(self.window.0, IconType::Big);
} else {
icon::unset_for_window(self.window.0, IconType::Big);
}
@@ -668,7 +635,7 @@ unsafe impl Sync for WindowWrapper {}
unsafe impl Send for WindowWrapper {}
unsafe fn init<T: 'static>(
mut attributes: WindowAttributes,
attributes: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
event_loop: &EventLoopWindowTarget<T>,
) -> Result<Window, RootOsError> {
@@ -677,25 +644,8 @@ unsafe fn init<T: 'static>(
.chain(Some(0).into_iter())
.collect::<Vec<_>>();
let window_icon = {
let icon = attributes.window_icon.take().map(WinIcon::from_icon);
if let Some(icon) = icon {
Some(icon.map_err(|e| os_error!(e))?)
} else {
None
}
};
let taskbar_icon = {
let icon = attributes.window_icon.take().map(WinIcon::from_icon);
if let Some(icon) = icon {
Some(icon.map_err(|e| os_error!(e))?)
} else {
None
}
};
// registering the window class
let class_name = register_window_class(&window_icon, &taskbar_icon);
let class_name = register_window_class(&attributes.window_icon, &pl_attribs.taskbar_icon);
let mut window_flags = WindowFlags::empty();
window_flags.set(WindowFlags::DECORATIONS, attributes.decorations);
@@ -744,7 +694,7 @@ unsafe fn init<T: 'static>(
}
let dpi = hwnd_dpi(real_window.0);
let dpi_factor = dpi_to_scale_factor(dpi);
let scale_factor = dpi_to_scale_factor(dpi);
// making the window transparent
if attributes.transparent && !pl_attribs.no_redirection_bitmap {
@@ -778,8 +728,6 @@ unsafe fn init<T: 'static>(
}
}
window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized);
// If the system theme is dark, we need to set the window theme now
// before we update the window flags (and possibly show the
// window for the first time).
@@ -788,9 +736,8 @@ unsafe fn init<T: 'static>(
let window_state = {
let window_state = WindowState::new(
&attributes,
window_icon,
taskbar_icon,
dpi_factor,
pl_attribs.taskbar_icon,
scale_factor,
dark_mode,
);
let window_state = Arc::new(Mutex::new(window_state));
@@ -808,6 +755,11 @@ unsafe fn init<T: 'static>(
.inner_size
.unwrap_or_else(|| PhysicalSize::new(1024, 768).into());
win.set_inner_size(dimensions);
if attributes.maximized {
// Need to set MAXIMIZED after setting `inner_size` as
// `Window::set_inner_size` changes MAXIMIZED to false.
win.set_maximized(true);
}
win.set_visible(attributes.visible);
if let Some(_) = attributes.fullscreen {
@@ -819,8 +771,8 @@ unsafe fn init<T: 'static>(
}
unsafe fn register_window_class(
window_icon: &Option<WinIcon>,
taskbar_icon: &Option<WinIcon>,
window_icon: &Option<Icon>,
taskbar_icon: &Option<Icon>,
) -> Vec<u16> {
let class_name: Vec<_> = OsStr::new("Window Class")
.encode_wide()
@@ -829,11 +781,11 @@ unsafe fn register_window_class(
let h_icon = taskbar_icon
.as_ref()
.map(|icon| icon.handle)
.map(|icon| icon.inner.as_raw_handle())
.unwrap_or(ptr::null_mut());
let h_icon_small = window_icon
.as_ref()
.map(|icon| icon.handle)
.map(|icon| icon.inner.as_raw_handle())
.unwrap_or(ptr::null_mut());
let class = winuser::WNDCLASSEXW {

View File

@@ -1,6 +1,8 @@
use crate::{
dpi::Size,
platform_impl::platform::{event_loop, icon::WinIcon, util},
dpi::{PhysicalPosition, Size},
event::ModifiersState,
icon::Icon,
platform_impl::platform::{event_loop, util},
window::{CursorIcon, Fullscreen, WindowAttributes},
};
use parking_lot::MutexGuard;
@@ -14,7 +16,6 @@ use winapi::{
};
/// Contains information about states and the window that the callback is going to use.
#[derive(Clone)]
pub struct WindowState {
pub mouse: MouseProperties,
@@ -22,16 +23,14 @@ pub struct WindowState {
pub min_size: Option<Size>,
pub max_size: Option<Size>,
pub window_icon: Option<WinIcon>,
pub taskbar_icon: Option<WinIcon>,
pub window_icon: Option<Icon>,
pub taskbar_icon: Option<Icon>,
pub saved_window: Option<SavedWindow>,
pub dpi_factor: f64,
pub scale_factor: f64,
pub modifiers_state: ModifiersState,
pub fullscreen: Option<Fullscreen>,
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
/// times in `EventsCleared`.
pub queued_out_of_band_redraw: bool,
pub is_dark_mode: bool,
pub high_surrogate: Option<u16>,
window_flags: WindowFlags,
@@ -40,7 +39,7 @@ pub struct WindowState {
#[derive(Clone)]
pub struct SavedWindow {
pub client_rect: RECT,
pub dpi_factor: f64,
pub scale_factor: f64,
}
#[derive(Clone)]
@@ -48,6 +47,7 @@ pub struct MouseProperties {
pub cursor: CursorIcon,
pub buttons_down: u32,
cursor_flags: CursorFlags,
pub last_position: Option<PhysicalPosition<f64>>,
}
bitflags! {
@@ -80,7 +80,9 @@ bitflags! {
/// window's state to match our stored state. This controls whether to accept those changes.
const MARKER_RETAIN_STATE_ON_SIZE = 1 << 10;
const MINIMIZED = 1 << 11;
const MARKER_IN_SIZE_MOVE = 1 << 11;
const MINIMIZED = 1 << 12;
const FULLSCREEN_AND_MASK = !(
WindowFlags::DECORATIONS.bits |
@@ -96,9 +98,8 @@ bitflags! {
impl WindowState {
pub fn new(
attributes: &WindowAttributes,
window_icon: Option<WinIcon>,
taskbar_icon: Option<WinIcon>,
dpi_factor: f64,
taskbar_icon: Option<Icon>,
scale_factor: f64,
is_dark_mode: bool,
) -> WindowState {
WindowState {
@@ -106,19 +107,20 @@ impl WindowState {
cursor: CursorIcon::default(),
buttons_down: 0,
cursor_flags: CursorFlags::empty(),
last_position: None,
},
min_size: attributes.min_inner_size,
max_size: attributes.max_inner_size,
window_icon,
window_icon: attributes.window_icon.clone(),
taskbar_icon,
saved_window: None,
dpi_factor,
scale_factor,
modifiers_state: ModifiersState::default(),
fullscreen: None,
queued_out_of_band_redraw: false,
is_dark_mode,
high_surrogate: None,
window_flags: WindowFlags::empty(),
@@ -268,7 +270,7 @@ impl WindowFlags {
| winuser::SWP_NOSIZE
| winuser::SWP_NOACTIVATE,
);
winuser::UpdateWindow(window);
winuser::InvalidateRgn(window, ptr::null_mut(), 0);
}
}

View File

@@ -9,7 +9,7 @@ use crate::{
platform_impl,
};
pub use crate::icon::*;
pub use crate::icon::{BadIcon, Icon};
/// Represents a window.
///
@@ -26,12 +26,14 @@ pub use crate::icon::*;
/// let window = Window::new(&event_loop).unwrap();
///
/// event_loop.run(move |event, _, control_flow| {
/// *control_flow = ControlFlow::Wait;
///
/// match event {
/// Event::WindowEvent {
/// event: WindowEvent::CloseRequested,
/// ..
/// } => *control_flow = ControlFlow::Exit,
/// _ => *control_flow = ControlFlow::Wait,
/// _ => (),
/// }
/// });
/// ```
@@ -78,7 +80,7 @@ impl WindowId {
}
/// Object that allows you to build windows.
#[derive(Clone)]
#[derive(Clone, Default)]
pub struct WindowBuilder {
/// The attributes to use to create the window.
pub window: WindowAttributes,
@@ -185,10 +187,7 @@ impl WindowBuilder {
/// Initializes a new `WindowBuilder` with default values.
#[inline]
pub fn new() -> Self {
WindowBuilder {
window: Default::default(),
platform_specific: Default::default(),
}
Default::default()
}
/// Requests the window to be of specific dimensions.
@@ -393,10 +392,10 @@ impl Window {
/// This is the **strongly encouraged** method of redrawing windows, as it can integrate with
/// OS-requested redraws (e.g. when a window gets resized).
///
/// This function can cause `RedrawRequested` events to be emitted after `Event::EventsCleared`
/// This function can cause `RedrawRequested` events to be emitted after `Event::MainEventsCleared`
/// but before `Event::NewEvents` if called in the following circumstances:
/// * While processing `EventsCleared`.
/// * While processing a `RedrawRequested` event that was sent during `EventsCleared` or any
/// * While processing `MainEventsCleared`.
/// * While processing a `RedrawRequested` event that was sent during `MainEventsCleared` or any
/// directly subsequent `RedrawRequested` event.
///
/// ## Platform-specific